Skip to main content
Learn how Spark wallets derive cryptographic keys using a hierarchical deterministic (HD) key derivation scheme. This guide covers the derivation paths, account structure, and implementation details. This hierarchical deterministic (HD) key derivation scheme is purpose-built for the Spark Wallet and follows the general principles of BIP43. It uses a custom purpose field (8797555’), derived from the last 3 bytes of the SHA256 hash of the string “spark” (0x863d73), to define a new domain of application-specific key derivations.
The derivation path is our recommendation to client wallets so that users can easily migrate between different wallet apps. The scheme is just a recommendation - you could implement a different scheme, but it will lose the ability to migrate to/from other apps.

Derivation Scheme

Spark uses a structured derivation path based on BIP43 principles:

Base Derivation Path

m/8797555'/accountNumber'/keyType'
Where:
  • 8797555’ - Custom purpose field (derived from SHA256(“spark”))
  • accountNumber’ - Account index (hardened derivation)
  • keyType’ - Specific key type (hardened derivation)

Key Types

Identity Key

Path: m/8797555'/accountNumber'/0'
Purpose: Primary wallet identifier and authentication

Base Signing Key

Path: m/8797555'/accountNumber'/1'
Purpose: Base key for leaf key derivation

Temporary Signing Key

Path: m/8797555'/accountNumber'/2'
Purpose: Deposit key for receiving Bitcoin

Static Deposit Key

Path: m/8797555'/accountNumber'/3'
Purpose: Reusable deposit address key

Account Structure

Account Number

The accountNumber should start from 0 and use hardened derivation for security.
// Account 0 (first account)
const account0 = 0;

// Account 1 (second account)  
const account1 = 1;

// Account derivation uses hardened derivation
const derivationPath = `m/8797555'/${accountNumber}'/keyType'`;
If no account number is provided, our JS-SDK defaults accountNumber to 1 to support backwards compatibility for mainnet wallets created with earlier versions of the SDK.

Leaf Key Derivation

For accepting transfers, there will always be a leaf ID provided by the Spark entity. The derivation path for a given leaf ID is calculated as follows:

Derivation Formula

// Calculate leaf key derivation
const leafId = "your-leaf-id";
const hash = sha256(leafId);
const leafIndex = (hash % 0x80000000) + 0x80000000;

// Final derivation path
const leafPath = `m/8797555'/${accountNumber}'/1'/${leafIndex}'`;

Step-by-Step Process

  1. Get Leaf ID - Provided by Spark entity
  2. Calculate Hash - hash = sha256(leaf_id)
  3. Derive Index - hash(leaf_id) % 0x80000000 + 0x80000000
  4. Build Path - m/8797555'/accountNumber'/1'/derivedIndex'

Example Implementation

import { createHash } from 'crypto';

function deriveLeafKey(leafId: string, accountNumber: number): string {
  // Step 1: Calculate SHA256 hash of leaf ID
  const hash = createHash('sha256').update(leafId).digest();
  
  // Step 2: Convert to bigint and calculate index
  const hashBigInt = BigInt('0x' + hash.toString('hex'));
  const leafIndex = Number(hashBigInt % BigInt(0x80000000)) + 0x80000000;
  
  // Step 3: Build derivation path
  return `m/8797555'/${accountNumber}'/1'/${leafIndex}'`;
}

// Usage
const leafPath = deriveLeafKey("leaf-123", 0);
console.log("Leaf derivation path:", leafPath);

Deposit Address Key Derivation

For user deposits, there’s no leaf ID because the leaf is generated after the tree is created. User deposits should always use the temporary signing key path.

Deposit Key Path

m/8797555'/accountNumber'/2'

Deposit Process

  1. Generate Deposit Key - Use temporary signing key path
  2. Create Deposit Address - Generate Bitcoin address from key
  3. Receive Deposit - User sends Bitcoin to address
  4. Self Transfer - Perform self transfer to leaf key after tree creation

Example

// Generate deposit key
const depositKeyPath = `m/8797555'/${accountNumber}'/2'`;
const depositKey = await deriveKeyFromPath(depositKeyPath);

// Create Bitcoin address
const depositAddress = createBitcoinAddress(depositKey);

// After deposit is received and tree is created
// Perform self transfer to leaf key
const leafKeyPath = deriveLeafKey(leafId, accountNumber);

Usage in Spark SDK

Wallet Initialization

initialize(params) Initializes your Spark wallet with the specified derivation parameters.
import { SparkWallet } from "@buildonspark/spark-sdk";

// Initialize a new wallet instance
const { wallet, mnemonic } = await SparkWallet.initialize({
  mnemonicOrSeed: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
  accountNumber: 0, // Account index
  options: {
    network: "REGTEST",
  },
});

console.log("Wallet initialized successfully:", mnemonic);
console.log("Identity key:", await wallet.getIdentityPublicKey());

Key Derivation Examples

Multiple Accounts

// Create multiple accounts from same mnemonic
const mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";

// Account 0
const { wallet: account0 } = await SparkWallet.initialize({
  mnemonicOrSeed: mnemonic,
  accountNumber: 0
});

// Account 1  
const { wallet: account1 } = await SparkWallet.initialize({
  mnemonicOrSeed: mnemonic,
  accountNumber: 1
});

console.log("Account 0 identity:", await account0.getIdentityPublicKey());
console.log("Account 1 identity:", await account1.getIdentityPublicKey());

Custom Derivation Paths

// Custom signer implementation
class CustomSparkSigner extends SparkSigner {
  async deriveKey(path: string): Promise<Uint8Array> {
    // Implement custom derivation logic
    return this.deriveFromPath(path);
  }
}

// Use custom signer
const customSigner = new CustomSparkSigner();
const { wallet } = await SparkWallet.initialize({
  signer: customSigner,
  accountNumber: 0
});

Migration Between Wallets

The standardized derivation scheme enables easy migration between different wallet implementations:

Export Wallet

// Export mnemonic and account number
const mnemonic = "your-12-word-mnemonic-phrase";
const accountNumber = 0;

// Save these values securely
const walletData = {
  mnemonic: mnemonic,
  accountNumber: accountNumber
};

Import Wallet

// Import in different wallet app
const { wallet } = await SparkWallet.initialize({
  mnemonicOrSeed: walletData.mnemonic,
  accountNumber: walletData.accountNumber,
  options: { network: "MAINNET" }
});

// Same identity key will be derived
const identityKey = await wallet.getIdentityPublicKey();

Security Considerations

Hardened Derivation

All derivation paths use hardened derivation (indicated by the ' suffix) to prevent key leakage:
  • Identity Key: m/8797555'/accountNumber'/0'
  • Base Signing Key: m/8797555'/accountNumber'/1'
  • Temporary Signing Key: m/8797555'/accountNumber'/2'
  • Static Deposit Key: m/8797555'/accountNumber'/3'

Key Isolation

Each account number creates a completely isolated set of keys:
// These are completely separate key spaces
const account0Keys = await deriveAccountKeys(0);
const account1Keys = await deriveAccountKeys(1);

// No relationship between account keys
console.log(account0Keys.identity !== account1Keys.identity); // true

Leaf Key Security

Leaf keys are derived deterministically but securely:
  • Uses SHA256 hash of leaf ID
  • Modulo operation ensures valid derivation index
  • Hardened derivation prevents key leakage

Best Practices

  • Use standard derivation paths - Stick to the recommended scheme for compatibility
  • Start with account 0 - Use account numbers starting from 0
  • Secure mnemonic storage - Store mnemonic phrases securely offline
  • Account separation - Use different account numbers for different purposes
  • Test migration - Verify wallet migration works with your implementation

Complete Example

import { SparkWallet } from "@buildonspark/spark-sdk";
import { createHash } from 'crypto';

async function demonstrateKeyDerivation() {
  try {
    // 1. Initialize wallet with specific account
    const { wallet, mnemonic } = await SparkWallet.initialize({
      mnemonicOrSeed: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
      accountNumber: 0,
      options: { network: "REGTEST" }
    });

    console.log("Mnemonic:", mnemonic);
    console.log("Account number: 0");

    // 2. Get identity key (m/8797555'/0'/0')
    const identityKey = await wallet.getIdentityPublicKey();
    console.log("Identity key:", identityKey);

    // 3. Get deposit key (m/8797555'/0'/2')
    const depositKey = await wallet.getDepositSigningKey();
    console.log("Deposit key:", depositKey);

    // 4. Demonstrate leaf key derivation
    const leafId = "example-leaf-123";
    const leafPath = deriveLeafKey(leafId, 0);
    console.log("Leaf derivation path:", leafPath);

    // 5. Create second account from same mnemonic
    const { wallet: account1 } = await SparkWallet.initialize({
      mnemonicOrSeed: mnemonic,
      accountNumber: 1,
      options: { network: "REGTEST" }
    });

    const account1Identity = await account1.getIdentityPublicKey();
    console.log("Account 1 identity:", account1Identity);
    console.log("Different from account 0:", identityKey !== account1Identity);

  } catch (error) {
    console.error("Key derivation failed:", error);
  }
}

function deriveLeafKey(leafId: string, accountNumber: number): string {
  const hash = createHash('sha256').update(leafId).digest();
  const hashBigInt = BigInt('0x' + hash.toString('hex'));
  const leafIndex = Number(hashBigInt % BigInt(0x80000000)) + 0x80000000;
  return `m/8797555'/${accountNumber}'/1'/${leafIndex}'`;
}

demonstrateKeyDerivation().catch(console.error);