Skip to main content
Spark wallets use a hierarchical deterministic (HD) key derivation scheme purpose-built for the Spark protocol. This follows the general principles of BIP43, using a custom purpose field derived from the SHA256 hash of “spark” to define application-specific key derivations.
This derivation scheme is our recommendation so users can easily migrate between different wallet apps. You can implement a different scheme, but it will lose the ability to migrate to/from other Spark wallets.

Derivation Scheme

Base Derivation Path

m/8797555'/accountNumber'/keyType'
Where:
  • 8797555’: Custom purpose field (derived from last 3 bytes of SHA256(“spark”) = 0x863d73)
  • accountNumber’: Account index (hardened derivation)
  • keyType’: Specific key type (hardened derivation)

Key Types

Spark derives 5 key types from the master seed:
PathKey TypePurpose
m/8797555'/n'/0'Identity KeyPrimary wallet identifier and Spark Address generation
m/8797555'/n'/1'Signing HD KeyBase key for leaf key derivation
m/8797555'/n'/2'Deposit KeyReceiving L1 Bitcoin deposits
m/8797555'/n'/3'Static Deposit HD KeyReusable deposit addresses (SSP integration)
m/8797555'/n'/4'HTLC Preimage HD KeyLightning HTLC preimage generation

Identity Key

Path: m/8797555'/n'/0'Primary wallet identifier. Used for authentication, Spark Address generation, and message signing.

Signing HD Key

Path: m/8797555'/n'/1'HD key used as the base for deriving leaf-specific signing keys. Each Spark leaf gets its own derived key.

Deposit Key

Path: m/8797555'/n'/2'Used for receiving L1 Bitcoin deposits into Spark. Single key, not HD-derived.

Static Deposit HD Key

Path: m/8797555'/n'/3'HD key for generating reusable static deposit addresses. Each index produces a different deposit address.

HTLC Preimage HD Key

Path: m/8797555'/n'/4'HD key for generating HTLC preimages in Lightning payments. The htlcHMAC() method uses this key.

Account Number

Default Behavior

The accountNumber parameter controls which account’s keys are derived. The default value differs by network:
NetworkDefault Account Number
REGTEST0
MAINNET1
MAINNET defaults to 1 for backwards compatibility with legacy wallets created before multi-account support. If you’re integrating with the SDK and using account 0 internally, account for this off-by-one behavior on mainnet.

Using Account Numbers

// Account 0 (first account)
const { wallet: account0 } = await SparkWallet.initialize({
  mnemonicOrSeed: mnemonic,
  accountNumber: 0,
  options: { network: "REGTEST" }
});

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

// These are completely separate wallets
const address0 = await account0.getSparkAddress();
const address1 = await account1.getSparkAddress();
console.log(address0 !== address1); // true

Explicit Account Numbers

When building wallet software, always specify the account number explicitly to avoid confusion:
// Explicit is better than implicit
const { wallet } = await SparkWallet.initialize({
  mnemonicOrSeed: mnemonic,
  accountNumber: 0,  // Always specify
  options: { network: "MAINNET" }
});

Leaf Key Derivation

When accepting transfers, Spark assigns a unique leaf ID to each UTXO. The signing key for that leaf is derived from the Signing HD Key using the leaf ID.

Derivation Formula

// 1. Hash the leaf ID
const hash = sha256(leafId);

// 2. Extract first 4 bytes as unsigned 32-bit integer (big-endian)
const hashValue = hash.slice(0, 4).readUInt32BE();

// 3. Calculate derivation index (hardened)
const leafIndex = (hashValue % 0x80000000) + 0x80000000;

// 4. Derive child key
const leafKey = signingHDKey.deriveChild(leafIndex);

Full Derivation Path

m/8797555'/{accountNumber}'/1'/{leafIndex}'
Where leafIndex is calculated from the leaf ID as shown above.

Implementation

import { createHash } from 'crypto';

function getLeafDerivationPath(leafId: string, accountNumber: number): string {
  // Step 1: Calculate SHA256 hash of leaf ID
  const hash = createHash('sha256').update(leafId).digest();
  
  // Step 2: Convert first 4 bytes to number
  const hashValue = hash.readUInt32BE(0);
  
  // Step 3: Calculate hardened index
  const leafIndex = (hashValue % 0x80000000) + 0x80000000;
  
  // Step 4: Build derivation path
  return `m/8797555'/${accountNumber}'/1'/${leafIndex}'`;
}

// Example
const path = getLeafDerivationPath("leaf-abc-123", 0);
// Returns something like: m/8797555'/0'/1'/2147483747'

Using KeyDerivation

In the SDK, you typically don’t derive leaf keys manually. Instead, use the KeyDerivation type:
import { KeyDerivationType } from "@buildonspark/spark-sdk";

// The signer handles derivation internally
const publicKey = await signer.getPublicKeyFromDerivation({
  type: KeyDerivationType.LEAF,
  path: leafId  // The leaf ID, not the derivation path
});

Static Deposit Key Derivation

Static deposit keys are derived from the Static Deposit HD Key at path /3':
// Derive static deposit key at index 5
const staticDepositPath = `m/8797555'/${accountNumber}'/3'/${index + 0x80000000}'`;
In the SDK:
// Get static deposit key at index 5
const publicKey = await signer.getStaticDepositSigningKey(5);
const privateKey = await signer.getStaticDepositSecretKey(5);

Deposit Address Flow

For L1 Bitcoin deposits, the flow is:
  1. Generate Deposit Key: Uses the Deposit Key at path m/8797555'/n'/2'
  2. Create Deposit Address: Generate Bitcoin address from the key
  3. Receive Deposit: User sends Bitcoin to the address
  4. Assign Leaf: After tree creation, funds are assigned to a leaf with its own derived key
// Get the deposit signing key
const depositKey = await signer.getDepositSigningKey();

// The SDK handles address generation
const depositAddress = await wallet.getSingleUseDepositAddress();

Usage in SparkWallet

Initialize with Specific Account

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

const { wallet, mnemonic } = await SparkWallet.initialize({
  mnemonicOrSeed: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
  accountNumber: 0,
  options: {
    network: "REGTEST",
  },
});

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

Multiple Accounts from Same Mnemonic

const mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";

// Create two separate accounts
const { wallet: wallet0 } = await SparkWallet.initialize({
  mnemonicOrSeed: mnemonic,
  accountNumber: 0,
  options: { network: "REGTEST" }
});

const { wallet: wallet1 } = await SparkWallet.initialize({
  mnemonicOrSeed: mnemonic,
  accountNumber: 1,
  options: { network: "REGTEST" }
});

// Each account has completely isolated keys
console.log(await wallet0.getSparkAddress()); // Different address
console.log(await wallet1.getSparkAddress()); // Different address

Custom Derivation Paths

For non-standard use cases, you can provide a custom key generator:
import { 
  DefaultSparkSigner, 
  DerivationPathKeysGenerator 
} from "@buildonspark/spark-sdk";

// Custom derivation template (? is replaced with account number)
const customGenerator = new DerivationPathKeysGenerator("m/44'/0'/?'/0'");

const signer = new DefaultSparkSigner({
  sparkKeysGenerator: customGenerator
});

// The keys will be derived from the custom path:
// Identity:       m/44'/0'/n'/0'
// Signing HD:     m/44'/0'/n'/0'/1'
// Deposit:        m/44'/0'/n'/0'/2'
// Static Deposit: m/44'/0'/n'/0'/3'
// HTLC Preimage:  m/44'/0'/n'/0'/4'
Custom derivation paths will not be compatible with other Spark wallets. Only use this if you have specific requirements that justify breaking compatibility.

Migration Between Wallets

The standardized derivation scheme enables wallet migration:

Export

// Save these securely
const walletBackup = {
  mnemonic: "your 12 or 24 word phrase",
  accountNumber: 0
};

Import

const { wallet } = await SparkWallet.initialize({
  mnemonicOrSeed: walletBackup.mnemonic,
  accountNumber: walletBackup.accountNumber,
  options: { network: "MAINNET" }
});

// Same identity key, same Spark address
const address = await wallet.getSparkAddress();

Security Considerations

Hardened Derivation

All derivation paths use hardened derivation (indicated by the ' suffix). This prevents:
  • Key leakage: Child public keys cannot be used to derive parent keys
  • Chain code exposure: Compromising a child key doesn’t compromise siblings

Key Isolation

Each account number creates a completely isolated set of keys:
// Account 0 and Account 1 share no key material
const account0Keys = deriveAccountKeys(mnemonic, 0);
const account1Keys = deriveAccountKeys(mnemonic, 1);

// No cryptographic relationship between them
assert(account0Keys.identity !== account1Keys.identity);

Mnemonic Security

  • Store mnemonic phrases securely (encrypted, offline if possible)
  • Never transmit mnemonics over the network
  • Consider using hardware wallets or secure enclaves for production

Quick Reference

Key TypePathSDK Method
Identitym/8797555'/n'/0'signer.getIdentityPublicKey()
Signing HDm/8797555'/n'/1'Internal (leaf derivation base)
Depositm/8797555'/n'/2'signer.getDepositSigningKey()
Static Depositm/8797555'/n'/3'/i'signer.getStaticDepositSigningKey(i)
HTLC Preimagem/8797555'/n'/4'signer.htlcHMAC(transferId)
Leafm/8797555'/n'/1'/hash(leafId)'signer.getPublicKeyFromDerivation({type: "leaf", path: leafId})