]> git.codecow.com Git - libnemo.git/commitdiff
Refactor BIP-44 to allow additional chain and address parameters, allowing Nano which...
authorChris Duncan <chris@zoso.dev>
Mon, 4 Aug 2025 21:55:24 +0000 (14:55 -0700)
committerChris Duncan <chris@zoso.dev>
Mon, 4 Aug 2025 21:55:24 +0000 (14:55 -0700)
src/lib/bip44-ckd.ts
src/lib/safe.ts

index 5377a649166e245a5054c00bdde9fef166ed5e2a..82bfdc342d189d245f44889ff7c7fb4a543bea3e 100644 (file)
@@ -1,39 +1,40 @@
 //! 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> {
index 9838fc9340ce828d69125e15e73dd71e3eabc0da..81e7516ac5df386edf3c6af5dcb09bf926ef0794 100644 (file)
@@ -145,7 +145,7 @@ export class Safe {
                                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 }
@@ -233,7 +233,7 @@ export class Safe {
                        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) {