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:
// 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
Get Leaf ID - Provided by Spark entity
Calculate Hash - hash = sha256(leaf_id)
Derive Index - hash(leaf_id) % 0x80000000 + 0x80000000
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
Generate Deposit Key - Use temporary signing key path
Create Deposit Address - Generate Bitcoin address from key
Receive Deposit - User sends Bitcoin to address
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 ());
BIP39 mnemonic phrase or raw seed. Leave blank to generate a new wallet.
Account index for key derivation (0-based)
Wallet configuration options including network selection
The initialized SparkWallet instance
The 12-word mnemonic seed phrase for wallet recovery
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 );