//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
//! SPDX-License-Identifier: GPL-3.0-or-later
+import { BIP44_PURPOSE, HARDENED_OFFSET, SLIP10_ED25519 } from './constants'
+
type ExtendedKey = {
privateKey: DataView<ArrayBuffer>
chainCode: DataView
}
export class Bip44Ckd {
- static BIP44_COIN_NANO = 165
- static BIP44_PURPOSE = 44
- static HARDENED_OFFSET = 0x80000000
- static SLIP10_ED25519 = 'ed25519 seed'
-
/**
* Derives a private child key for a coin by following the specified BIP-32 and
* BIP-44 derivation path. Purpose is always 44'. Only hardened child keys are
* defined.
*
* @param {ArrayBuffer} seed - Hexadecimal seed derived from mnemonic phrase
- * @param {number} index - Account number between 0 and 2^31-1
- * @param {number} coin - Number registered to a specific coin in SLIP-044. Default: 165 (Nano)
+ * @param {number} coin - Number registered to a specific coin in SLIP-044
+ * @param {number} account - Account number between 0 and 2^31-1
* @returns {Promise<ArrayBuffer>} Private child key for the account
*/
- static async ckd (seed: ArrayBuffer, index: number, coin: number = this.BIP44_COIN_NANO): Promise<ArrayBuffer> {
+ static async ckd (seed: ArrayBuffer, coin: number, account: number, chain?: number, address?: number): Promise<ArrayBuffer> {
if (seed.byteLength < 16 || seed.byteLength > 64) {
throw new RangeError(`Invalid seed length`)
}
- if (!Number.isSafeInteger(index) || index < 0 || index > 0x7fffffff) {
- throw new RangeError(`Invalid child key index 0x${index.toString(16)}`)
+ if (!Number.isSafeInteger(account) || account < 0 || account > 0x7fffffff) {
+ throw new RangeError(`Invalid child key index 0x${account.toString(16)}`)
}
- const masterKey = await this.slip10(this.SLIP10_ED25519, seed)
- const purposeKey = await this.CKDpriv(masterKey, this.BIP44_PURPOSE + this.HARDENED_OFFSET)
- const coinKey = await this.CKDpriv(purposeKey, coin + this.HARDENED_OFFSET)
- const accountKey = await this.CKDpriv(coinKey, index + this.HARDENED_OFFSET)
- return accountKey.privateKey.buffer
+ const masterKey = await this.slip10(SLIP10_ED25519, seed)
+ const purposeKey = await this.CKDpriv(masterKey, BIP44_PURPOSE + HARDENED_OFFSET)
+ const coinKey = await this.CKDpriv(purposeKey, coin + HARDENED_OFFSET)
+ const accountKey = await this.CKDpriv(coinKey, account + HARDENED_OFFSET)
+ if (chain == null) return accountKey.privateKey.buffer
+ const chainKey = await this.CKDpriv(accountKey, chain)
+ if (address == null) return chainKey.privateKey.buffer
+ const addressKey = await this.CKDpriv(chainKey, address)
+ return addressKey.privateKey.buffer
}
static async slip10 (curve: string, S: ArrayBuffer): Promise<ExtendedKey> {
throw new Error('Invalid wallet account index')
}
const prv = this.#type === 'BIP-44'
- ? await Bip44Ckd.ckd(this.#seed, index)
+ ? await Bip44Ckd.ckd(this.#seed, BIP44_COIN_NANO, index)
: await Blake2bCkd.ckd(this.#seed, index)
const pub = await NanoNaCl.convert(new Uint8Array(prv))
return { index, publicKey: pub.buffer }
if (data == null) {
throw new Error('Data to sign not found')
}
- const prv = await Bip44Ckd.ckd(this.#seed, index)
+ const prv = await Bip44Ckd.ckd(this.#seed, BIP44_COIN_NANO, index)
const sig = await NanoNaCl.detached(new Uint8Array(data), new Uint8Array(prv))
return { signature: sig.buffer }
} catch (err) {