+++ /dev/null
-// SPDX-FileCopyrightText: 2024 Chris Duncan <chris@zoso.dev>\r
-// SPDX-License-Identifier: GPL-3.0-or-later\r
-\r
-import { BIP44_COIN_NANO, BIP44_PURPOSE, HARDENED_OFFSET, SLIP10_ED25519 } from './constants.js'\r
-import { bytes, dec, hex, utf8 } from './convert.js'\r
-\r
-type ExtendedKey = {\r
- privateKey: string\r
- chainCode: string\r
-}\r
-\r
-/**\r
-* Derives a private child key following the BIP-32 and BIP-44 derivation path\r
-* registered to the Nano block lattice. Only hardened child keys are defined.\r
-*\r
-* @param {string} seed - Hexadecimal seed derived from mnemonic phrase\r
-* @param {number} index - Account number between 0 and 2^31-1\r
-* @returns {Promise<string>} Private child key for the account\r
-*/\r
-export async function nanoCKD (seed: string, index: number): Promise<string> {\r
- if (!Number.isSafeInteger(index) || index < 0 || index > 0x7fffffff) {\r
- throw new RangeError(`Invalid child key index 0x${index.toString(16)}`)\r
- }\r
- return await ckd(seed, BIP44_COIN_NANO, index)\r
-}\r
-\r
-/**\r
-* Derives a private child key for a coin by following the specified BIP-32 and\r
-* BIP-44 derivation path. Purpose is always 44'. Only hardened child keys are\r
-* defined.\r
-*\r
-* @param {string} seed - Hexadecimal seed derived from mnemonic phrase\r
-* @param {number} coin - Number registered to a specific coin in SLIP-044\r
-* @param {number} index - Account number between 0 and 2^31-1\r
-* @returns {Promise<string>} Private child key for the account\r
-*/\r
-export async function ckd (seed: string, coin: number, index: number): Promise<string> {\r
- if (!Number.isSafeInteger(index) || index < 0 || index > 0x7fffffff) {\r
- throw new RangeError(`Invalid child key index 0x${index.toString(16)}`)\r
- }\r
- const masterKey = await slip10(SLIP10_ED25519, seed)\r
- const purposeKey = await CKDpriv(masterKey, BIP44_PURPOSE + HARDENED_OFFSET)\r
- const coinKey = await CKDpriv(purposeKey, coin + HARDENED_OFFSET)\r
- const accountKey = await CKDpriv(coinKey, index + HARDENED_OFFSET)\r
- return accountKey.privateKey\r
-}\r
-\r
-async function slip10 (curve: string, S: string): Promise<ExtendedKey> {\r
- const key = utf8.toBytes(curve)\r
- const data = hex.toBytes(S)\r
- const I = await hmac(key, data)\r
- const IL = I.slice(0, I.length / 2)\r
- const IR = I.slice(I.length / 2)\r
- return ({ privateKey: IL, chainCode: IR })\r
-}\r
-\r
-async function CKDpriv ({ privateKey, chainCode }: ExtendedKey, index: number): Promise<ExtendedKey> {\r
- const key = hex.toBytes(chainCode)\r
- const data = hex.toBytes(`00${bytes.toHex(ser256(privateKey))}${bytes.toHex(ser32(index))}`)\r
- const I = await hmac(key, data)\r
- const IL = I.slice(0, I.length / 2)\r
- const IR = I.slice(I.length / 2)\r
- return ({ privateKey: IL, chainCode: IR })\r
-}\r
-\r
-function ser32 (integer: number): Uint8Array {\r
- if (typeof integer !== 'number') {\r
- throw new TypeError(`Expected a number, received ${typeof integer}`)\r
- }\r
- const bits = dec.toBin(integer)\r
- if (bits.length > 32) {\r
- throw new RangeError(`Expected 32-bit integer, received ${bits.length}-bit value: ${integer}`)\r
- }\r
- return dec.toBytes(integer, 4)\r
-}\r
-\r
-function ser256 (integer: string): Uint8Array {\r
- if (typeof integer !== 'string') {\r
- throw new TypeError(`Expected string, received ${typeof integer}`)\r
- }\r
- const bits = hex.toBin(integer)\r
- if (bits.length > 256) {\r
- throw new RangeError(`Expected 256-bit integer, received ${bits.length}-bit value: ${integer}`)\r
- }\r
- return hex.toBytes(integer, 32)\r
-}\r
-\r
-async function hmac (key: Uint8Array, data: Uint8Array): Promise<string> {\r
- const { subtle } = globalThis.crypto\r
- const pk = await subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-512' }, false, ['sign'])\r
- const signature = await subtle.sign('HMAC', pk, data)\r
- return bytes.toHex(new Uint8Array(signature))\r
-}\r