From 9f5984d78d78944cb4e7bd6ba86622e4882b4446 Mon Sep 17 00:00:00 2001 From: Anderson Juhasc Date: Sat, 25 May 2024 10:59:45 -0300 Subject: [PATCH] added functions accountFromSeedWords, extendedKeysFromSeedWords and accountFromExtendedKey to nip06 --- nip06.test.ts | 40 +++++++++++++++++++++++++++++++++++++++- nip06.ts | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/nip06.test.ts b/nip06.test.ts index 9792afe..4cf4620 100644 --- a/nip06.test.ts +++ b/nip06.test.ts @@ -1,5 +1,10 @@ import { test, expect } from 'bun:test' -import { privateKeyFromSeedWords } from './nip06.ts' +import { + privateKeyFromSeedWords, + accountFromSeedWords, + extendedKeysFromSeedWords, + accountFromExtendedKey +} from './nip06.ts' test('generate private key from a mnemonic', async () => { const mnemonic = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong' @@ -26,3 +31,36 @@ test('generate private key for account 1 from a mnemonic and passphrase', async const privateKey = privateKeyFromSeedWords(mnemonic, passphrase, 1) expect(privateKey).toEqual('2e0f7bd9e3c3ebcdff1a90fb49c913477e7c055eba1a415d571b6a8c714c7135') }) + +test('generate private and public key for account 1 from a mnemonic and passphrase', async () => { + const mnemonic = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong' + const passphrase = '123' + const { privateKey, publicKey } = accountFromSeedWords(mnemonic, passphrase, 1) + expect(privateKey).toEqual('2e0f7bd9e3c3ebcdff1a90fb49c913477e7c055eba1a415d571b6a8c714c7135') + expect(publicKey).toEqual('13f55f4f01576570ea342eb7d2b611f9dc78f8dc601aeb512011e4e73b90cf0a') +}) + +test('generate extended keys from mnemonic', () => { + const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' + const passphrase = '' + const extendedAccountIndex = 0 + const { privateExtendedKey, publicExtendedKey } = extendedKeysFromSeedWords(mnemonic, passphrase, extendedAccountIndex) + + expect(privateExtendedKey).toBe('xprv9z78fizET65qsCaRr1MSutTSGk1fcKfSt1sBqmuWShtkjRJJ4WCKcSnha6EmgNzFSsyom3MWtydHyPtJtSLZQUtictVQtM2vkPcguh6TQCH') + expect(publicExtendedKey).toBe('xpub6D6V5EX8HTe95getx2tTH2QApmrA1nPJFEnneAK813RjcDdSc3WaAF7BRNpTF7o7zXjVm3DD3VMX66jhQ7wLaZ9sS6NzyfiwfzqDZbxvpDN') +}) + +test('generate account from extended private key', () => { + const xprv = 'xprv9z78fizET65qsCaRr1MSutTSGk1fcKfSt1sBqmuWShtkjRJJ4WCKcSnha6EmgNzFSsyom3MWtydHyPtJtSLZQUtictVQtM2vkPcguh6TQCH' + const { privateKey, publicKey } = accountFromExtendedKey(xprv) + + expect(privateKey).toBe('5f29af3b9676180290e77a4efad265c4c2ff28a5302461f73597fda26bb25731') + expect(publicKey).toBe('e8bcf3823669444d0b49ad45d65088635d9fd8500a75b5f20b59abefa56a144f') +}) + +test('generate account from extended public key', () => { + const xpub = 'xpub6D6V5EX8HTe95getx2tTH2QApmrA1nPJFEnneAK813RjcDdSc3WaAF7BRNpTF7o7zXjVm3DD3VMX66jhQ7wLaZ9sS6NzyfiwfzqDZbxvpDN' + const { publicKey } = accountFromExtendedKey(xpub) + + expect(publicKey).toBe('e8bcf3823669444d0b49ad45d65088635d9fd8500a75b5f20b59abefa56a144f') +}) diff --git a/nip06.ts b/nip06.ts index 9a80253..3909bb7 100644 --- a/nip06.ts +++ b/nip06.ts @@ -3,13 +3,58 @@ import { wordlist } from '@scure/bip39/wordlists/english' import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from '@scure/bip39' import { HDKey } from '@scure/bip32' +const DERIVATION_PATH = `m/44'/1237'` + export function privateKeyFromSeedWords(mnemonic: string, passphrase?: string, accountIndex = 0): string { let root = HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic, passphrase)) - let privateKey = root.derive(`m/44'/1237'/${accountIndex}'/0/0`).privateKey + let privateKey = root.derive(`${DERIVATION_PATH}/${accountIndex}'/0/0`).privateKey if (!privateKey) throw new Error('could not derive private key') return bytesToHex(privateKey) } +export function accountFromSeedWords(mnemonic: string, passphrase?: string, accountIndex = 0): { + privateKey: string, + publicKey: string, +} { + const root = HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic, passphrase)) + const seed = root.derive(`${DERIVATION_PATH}/${accountIndex}'/0/0`) + const privateKey = bytesToHex(seed.privateKey!) + const publicKey = bytesToHex(seed.publicKey!.slice(1)) + if (!privateKey && !publicKey) { + throw new Error('could not derive key pair') + } + return { privateKey, publicKey } +} + +export function extendedKeysFromSeedWords(mnemonic: string, passphrase?: string, extendedAccountIndex = 0): { + privateExtendedKey: string, + publicExtendedKey: string +} { + let root = HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic, passphrase)) + let seed = root.derive(`${DERIVATION_PATH}/${extendedAccountIndex}'`) + let privateExtendedKey = seed.privateExtendedKey + let publicExtendedKey = seed.publicExtendedKey + if (!privateExtendedKey && !publicExtendedKey) throw new Error('could not derive extended key pair') + return { privateExtendedKey, publicExtendedKey } +} + +export function accountFromExtendedKey(base58key: string, accountIndex = 0): { + privateKey?: string, + publicKey: string +} { + let extendedKey = HDKey.fromExtendedKey(base58key) + let version = base58key.slice(0, 4) + let child = extendedKey.deriveChild(0).deriveChild(accountIndex) + let publicKey = bytesToHex(child.publicKey!.slice(1)) + if (!publicKey) throw new Error('could not derive public key') + if (version === 'xprv') { + let privateKey = bytesToHex(child.privateKey!) + if (!privateKey) throw new Error('could not derive private key') + return { privateKey, publicKey } + } + return { publicKey } +} + export function generateSeedWords(): string { return generateMnemonic(wordlist) }