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:
Path Key Type Purpose m/8797555'/n'/0'Identity Key Primary wallet identifier and Spark Address generation m/8797555'/n'/1'Signing HD Key Base key for leaf key derivation m/8797555'/n'/2'Deposit Key Receiving L1 Bitcoin deposits m/8797555'/n'/3'Static Deposit HD Key Reusable deposit addresses (SSP integration) m/8797555'/n'/4'HTLC Preimage HD Key Lightning 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:
Network Default Account Number REGTEST 0MAINNET 1
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.
// 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:
Generate Deposit Key : Uses the Deposit Key at path m/8797555'/n'/2'
Create Deposit Address : Generate Bitcoin address from the key
Receive Deposit : User sends Bitcoin to the address
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 Type Path SDK Method Identity m/8797555'/n'/0'signer.getIdentityPublicKey()Signing HD m/8797555'/n'/1'Internal (leaf derivation base) Deposit m/8797555'/n'/2'signer.getDepositSigningKey()Static Deposit m/8797555'/n'/3'/i'signer.getStaticDepositSigningKey(i)HTLC Preimage m/8797555'/n'/4'signer.htlcHMAC(transferId)Leaf m/8797555'/n'/1'/hash(leafId)'signer.getPublicKeyFromDerivation({type: "leaf", path: leafId})