Skip to content

Commit 5a083f7

Browse files
authored
Merge pull request #8 from Anderson-Juhasc/master
Improved functions and README examples
2 parents 78e736b + 963e56c commit 5a083f7

13 files changed

+245
-117
lines changed

README.md

+47-8
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,62 @@ npm i nip06
1414

1515
```js
1616
import {
17-
privateKeyFromSeedWords,
18-
getPublicKey,
19-
getBech32PrivateKey,
20-
getBech32PublicKey
17+
accountFromSeedWords,
2118
} from 'nip06'
2219

2320
const mnemonic = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong'
2421
const passphrase = 'your super secure passphrase' // optional
22+
const accountIndex = 0
23+
24+
const { privateKey, publicKey } = accountFromSeedWords({ mnemonic, passphrase, accountIndex })
25+
```
26+
27+
```js
28+
import {
29+
accountFromRandomKey,
30+
} from 'nip06'
31+
32+
const { privateKey, publicKey } = accountFromRandomKey()
33+
```
34+
35+
```js
36+
import {
37+
getPublicKey,
38+
getBech32PrivateKey,
39+
getBech32PublicKey,
40+
} from 'nip06'
41+
42+
const privateKey = '5f29af3b9676180290e77a4efad265c4c2ff28a5302461f73597fda26bb25731'
2543

26-
const { privateKey } = privateKeyFromSeedWords({ mnemonic, passphrase })
2744
const { publicKey } = getPublicKey({ privateKey })
2845
const { bech32PrivateKey } = getBech32PrivateKey({ privateKey })
29-
const { bech32PublicKey } = getBech32PublicKey({ publicKey })
46+
const { bech32PublicKey } = getBech32PublicKey({ publicKey.hex })
47+
// or
48+
const bech32PublicKey2 = publicKey.bech32
49+
```
50+
51+
```js
52+
import {
53+
extendedKeysFromSeedWords,
54+
accountFromExtendedKey
55+
} from 'nip06'
3056

57+
const mnemonic = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong'
58+
const passphrase = 'your super secure passphrase' // optional
3159
const extendedAccountIndex = 0
3260
const accountIndex = 0
3361

34-
const { privateExtendedKey, publicExtendedKey } = extendedPairFromSeedWords(mnemonic, passphrase, extendedAccountIndex)
35-
const { privateKey, publicKey } = accountFromExtendedKey(privateExtendedKey, accountIndex)
62+
const { privateExtendedKey, publicExtendedKey } = extendedKeysFromSeedWords({ mnemonic, passphrase, extendedAccountIndex })
63+
const { privateKey, publicKey } = accountFromExtendedKey({ extendedKey: privateExtendedKey, accountIndex })
64+
```
65+
66+
```js
67+
import {
68+
accountFromExtendedKey
69+
} from 'nip06'
70+
71+
const publicExtendedKey = 'xpub6C2FTj1fmB2GES9CSxbXYtrve372NjoHLLQxYRGb9qXbMWBLdDH5qQ7pm29LQuYaF4HzFUsdkcj4jurBU3ebF7xkVNbVTY3MCp9mEiX4Te5'
72+
const accountIndex = 0
73+
74+
const { publicKey } = accountFromExtendedKey({ extendedKey: publicExtendedKey, accountIndex })
3675
```

package.json

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
"author": "jaonoctus <jaonoctus@protonmail.com>",
66
"license": "MIT",
77
"main": "lib/index.js",
8+
"repository": {
9+
"type": "git",
10+
"url": "https://github.com/jaonoctus/nip06"
11+
},
812
"module": "lib/index.mjs",
913
"types": "lib/index.d.ts",
1014
"scripts": {

src/index.ts

+90-46
Original file line numberDiff line numberDiff line change
@@ -8,85 +8,129 @@ import {
88
} from '@scure/bip39'
99
import { bech32 } from 'bech32'
1010
import { wordlist } from '@scure/bip39/wordlists/english'
11-
import { DERIVATION_PATH, PUBLIC_KEY_PREFIX, SECRET_KEY_PREFIX } from './constants'
11+
import {
12+
DERIVATION_PATH,
13+
PUBLIC_KEY_PREFIX,
14+
SECRET_KEY_PREFIX
15+
} from './constants'
1216

13-
export function privateKeyFromSeedWords(
14-
{
15-
mnemonic,
16-
passphrase
17-
}: { mnemonic: string, passphrase?: string }
18-
): { privateKey: string } {
17+
import {
18+
ExtendedKeys,
19+
Key,
20+
Account,
21+
} from './types'
22+
23+
export function accountFromSeedWords({
24+
mnemonic,
25+
passphrase,
26+
accountIndex = 0
27+
} : {
28+
mnemonic: string,
29+
passphrase?: string,
30+
accountIndex?: number
31+
}): Account {
1932
const root = HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic, passphrase))
20-
const { privateKey } = root.derive(`${DERIVATION_PATH}/0'/0/0`)
21-
if (!privateKey) {
22-
throw new Error('could not derive private key')
33+
const seed = root.derive(`${DERIVATION_PATH}/${accountIndex}'/0/0`)
34+
const privateKeyHex = bytesToHex(seed.privateKey!)
35+
const publicKeyHex = bytesToHex(seed.publicKey!.slice(1))
36+
if (!privateKeyHex && !publicKeyHex) {
37+
throw new Error('could not derive key pair')
2338
}
39+
const { bech32PrivateKey } = getBech32PrivateKey({ privateKey: privateKeyHex })
40+
const { bech32PublicKey } = getBech32PublicKey({ publicKey: publicKeyHex })
2441
return {
25-
privateKey: bytesToHex(privateKey)
42+
privateKey: { hex: privateKeyHex, bech32: bech32PrivateKey },
43+
publicKey: { hex: publicKeyHex, bech32: bech32PublicKey }
2644
}
2745
}
2846

29-
export function generatePrivateKey(): { privateKey: string } {
30-
return {
31-
privateKey: bytesToHex(secp256k1.utils.randomPrivateKey())
47+
export function accountFromRandomKey(): Account {
48+
const privateKeyHex = bytesToHex(secp256k1.utils.randomPrivateKey())
49+
const publicKeyHex = bytesToHex(schnorr.getPublicKey(privateKeyHex))
50+
if (!privateKeyHex && !publicKeyHex) {
51+
throw new Error('could not derive key pair')
3252
}
33-
}
34-
35-
export function getPublicKey({ privateKey }: { privateKey: string }): { publicKey: string } {
53+
const { bech32PrivateKey } = getBech32PrivateKey({ privateKey: privateKeyHex })
54+
const { bech32PublicKey } = getBech32PublicKey({ publicKey: publicKeyHex })
3655
return {
37-
publicKey: bytesToHex(schnorr.getPublicKey(privateKey))
56+
privateKey: { hex: privateKeyHex, bech32: bech32PrivateKey },
57+
publicKey: { hex: publicKeyHex, bech32: bech32PublicKey }
3858
}
3959
}
4060

41-
function hexToBech32(key: string, prefix: string) {
42-
const words = bech32.toWords(hexToBytes(key))
43-
return bech32.encode(prefix, words)
44-
}
45-
46-
export function getBech32PrivateKey({ privateKey }: { privateKey: string }): { bech32PrivateKey: string } {
47-
return {
48-
bech32PrivateKey: hexToBech32(privateKey, SECRET_KEY_PREFIX)
61+
export function getPublicKey({ privateKey }: { privateKey: string }): {
62+
publicKey: Key
63+
} {
64+
const publicKeyHex = bytesToHex(schnorr.getPublicKey(privateKey))
65+
if (!publicKeyHex) {
66+
throw new Error('could not generate public key')
4967
}
50-
}
51-
52-
export function getBech32PublicKey({ publicKey }: { publicKey: string }): { bech32PublicKey: string } {
68+
const { bech32PublicKey } = getBech32PublicKey({ publicKey: publicKeyHex })
5369
return {
54-
bech32PublicKey: hexToBech32(publicKey, PUBLIC_KEY_PREFIX)
70+
publicKey: { hex: publicKeyHex, bech32: bech32PublicKey }
5571
}
5672
}
5773

58-
export function extendedPairFromSeedWords(mnemonic: string, passphrase?: string, extendedAccountIndex = 0): {
59-
privateExtendedKey: string,
60-
publicExtendedKey: string
61-
} {
74+
export function extendedKeysFromSeedWords({
75+
mnemonic,
76+
passphrase,
77+
extendedAccountIndex = 0
78+
} : {
79+
mnemonic: string,
80+
passphrase?: string,
81+
extendedAccountIndex?: number
82+
}): ExtendedKeys {
6283
let root = HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic, passphrase))
6384
let seed = root.derive(`${DERIVATION_PATH}/${extendedAccountIndex}'`)
6485
let privateExtendedKey = seed.privateExtendedKey
6586
let publicExtendedKey = seed.publicExtendedKey
66-
if (!privateExtendedKey) throw new Error('could not derive private extended key')
87+
if (!privateExtendedKey && !publicExtendedKey) throw new Error('could not derive extended key pair')
6788
return { privateExtendedKey, publicExtendedKey }
6889
}
6990

70-
export function accountFromExtendedKey(base58key: string, accountIndex = 0): {
71-
privateKey?: { hex: string, bech32: string },
72-
publicKey: { hex: string, bech32: string }
91+
export function accountFromExtendedKey({
92+
extendedKey,
93+
accountIndex = 0
94+
} : {
95+
extendedKey: string,
96+
accountIndex?: number
97+
}): {
98+
privateKey?: Key,
99+
publicKey: Key
73100
} {
74-
let extendedKey = HDKey.fromExtendedKey(base58key)
75-
let version = base58key.slice(0, 4)
76-
let child = extendedKey.deriveChild(0).deriveChild(accountIndex)
101+
let seed = HDKey.fromExtendedKey(extendedKey)
102+
let version = extendedKey.slice(0, 4)
103+
let child = seed.deriveChild(0).deriveChild(accountIndex)
77104
let publicKeyHex = bytesToHex(child.publicKey!.slice(1))
78105
if (!publicKeyHex) throw new Error('could not derive public key')
79-
let publicKeyBech32 = hexToBech32(publicKeyHex, 'npub')
106+
const { bech32PublicKey } = getBech32PublicKey({ publicKey: publicKeyHex })
80107
if (version === 'xprv') {
81108
let privateKeyHex = bytesToHex(child.privateKey!)
82109
if (!privateKeyHex) throw new Error('could not derive private key')
83-
let privateKeyBech32 = hexToBech32(privateKeyHex, 'nsec')
110+
const { bech32PrivateKey } = getBech32PrivateKey({ privateKey: privateKeyHex })
84111
return {
85-
privateKey: { hex: privateKeyHex, bech32: privateKeyBech32 },
86-
publicKey: { hex: publicKeyHex, bech32: publicKeyBech32 }
112+
privateKey: { hex: privateKeyHex, bech32: bech32PrivateKey },
113+
publicKey: { hex: publicKeyHex, bech32: bech32PublicKey }
87114
}
88115
}
89-
return { publicKey: { hex: publicKeyHex, bech32: publicKeyBech32 } }
116+
return { publicKey: { hex: publicKeyHex, bech32: bech32PublicKey } }
117+
}
118+
119+
function hexToBech32(key: string, prefix: string) {
120+
const words = bech32.toWords(hexToBytes(key))
121+
return bech32.encode(prefix, words)
122+
}
123+
124+
export function getBech32PrivateKey({ privateKey }: { privateKey: string }): { bech32PrivateKey: string } {
125+
return {
126+
bech32PrivateKey: hexToBech32(privateKey, SECRET_KEY_PREFIX)
127+
}
128+
}
129+
130+
export function getBech32PublicKey({ publicKey }: { publicKey: string }): { bech32PublicKey: string } {
131+
return {
132+
bech32PublicKey: hexToBech32(publicKey, PUBLIC_KEY_PREFIX)
133+
}
90134
}
91135

92136
export function generateSeedWords(): { mnemonic: string } {

src/types.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export type Key = {
2+
hex: string,
3+
bech32: string
4+
}
5+
export type Account = {
6+
privateKey: Key,
7+
publicKey: Key
8+
}
9+
export type ExtendedKeys = {
10+
privateExtendedKey: string,
11+
publicExtendedKey: string
12+
}

tests/accountFromExtendedKey.test.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { accountFromExtendedKey } from '../src/index'
33

44
describe('accountFromExtendedKey', () => {
55
it('should get account from extended private key', () => {
6-
const { privateKey, publicKey } = accountFromExtendedKey('xprv9z78fizET65qsCaRr1MSutTSGk1fcKfSt1sBqmuWShtkjRJJ4WCKcSnha6EmgNzFSsyom3MWtydHyPtJtSLZQUtictVQtM2vkPcguh6TQCH')
6+
const extendedKey = 'xprv9z78fizET65qsCaRr1MSutTSGk1fcKfSt1sBqmuWShtkjRJJ4WCKcSnha6EmgNzFSsyom3MWtydHyPtJtSLZQUtictVQtM2vkPcguh6TQCH'
7+
const { privateKey, publicKey } = accountFromExtendedKey({ extendedKey })
78

89
expect(privateKey?.hex).toBe('5f29af3b9676180290e77a4efad265c4c2ff28a5302461f73597fda26bb25731')
910
expect(privateKey?.bech32).toBe('nsec1tu567wukwcvq9y880f8045n9cnp07299xqjxrae4jl76y6aj2ucs2mkupq')
@@ -12,7 +13,8 @@ describe('accountFromExtendedKey', () => {
1213
})
1314

1415
it('should get account from extended public key', () => {
15-
const { privateKey, publicKey } = accountFromExtendedKey('xpub6D6V5EX8HTe95getx2tTH2QApmrA1nPJFEnneAK813RjcDdSc3WaAF7BRNpTF7o7zXjVm3DD3VMX66jhQ7wLaZ9sS6NzyfiwfzqDZbxvpDN')
16+
const extendedKey = 'xpub6D6V5EX8HTe95getx2tTH2QApmrA1nPJFEnneAK813RjcDdSc3WaAF7BRNpTF7o7zXjVm3DD3VMX66jhQ7wLaZ9sS6NzyfiwfzqDZbxvpDN'
17+
const { publicKey } = accountFromExtendedKey({ extendedKey })
1618

1719
expect(publicKey.hex).toBe('e8bcf3823669444d0b49ad45d65088635d9fd8500a75b5f20b59abefa56a144f')
1820
expect(publicKey.bech32).toBe('npub1az708q3kd9zy6z6f44zav5ygvdwelkzspf6mtusttx47lft2z38sghk0w7')

tests/accountFromRandomKey.test.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { accountFromRandomKey } from '../src/index'
3+
4+
describe('generatePrivateKey', () => {
5+
it('should generate a 64 bytes hex', () => {
6+
const { privateKey } = accountFromRandomKey()
7+
expect(privateKey.hex).toHaveLength(64)
8+
})
9+
})

tests/accountFromSeedWords.test.ts

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { accountFromSeedWords } from '../src/index'
3+
import { DERIVATION_PATH } from '../src/constants'
4+
5+
describe('accountFromSeedWords', () => {
6+
it('should use the NIP-06 derivation path', () => {
7+
expect(`${DERIVATION_PATH}/0'/0/0`).toBe(`m/44'/1237'/0'/0/0`)
8+
})
9+
10+
it('should get a 64 bytes hex private key from mnemonic', () => {
11+
const { privateKey: sk1 } = accountFromSeedWords({
12+
mnemonic: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
13+
accountIndex: 0
14+
})
15+
const { privateKey: sk2 } = accountFromSeedWords({
16+
mnemonic: 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong',
17+
accountIndex: 0
18+
})
19+
const { privateKey: sk3 } = accountFromSeedWords({
20+
mnemonic: 'bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon',
21+
accountIndex: 0
22+
})
23+
24+
expect(sk1.hex).toBe('5f29af3b9676180290e77a4efad265c4c2ff28a5302461f73597fda26bb25731')
25+
expect(sk2.hex).toBe('c26cf31d8ba425b555ca27d00ca71b5008004f2f662470f8c8131822ec129fe2')
26+
expect(sk3.hex).toBe('45049983ec1e38aa8a4a7d76b0203a4484d9a1d3120017cfd455e80aac2d52e4')
27+
})
28+
29+
it('should get a 64 bytes hex private key from mnemonic and passphrase', () => {
30+
const { privateKey } = accountFromSeedWords({
31+
mnemonic: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
32+
passphrase: '123'
33+
})
34+
expect(privateKey.hex).toBe('60ecd7942071530818b4f3cf42e402b5c946b3ce387606d134c4613e59fa0196')
35+
})
36+
37+
it('should thow an error when a invalid mnemonic is provided', () => {
38+
const fn = () => {
39+
accountFromSeedWords({ mnemonic: 'invalid words' })
40+
}
41+
expect(fn).toThrowError('Invalid mnemonic')
42+
})
43+
})

tests/bech32.test.ts

+13-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, it } from 'vitest'
2-
import { getBech32PrivateKey, privateKeyFromSeedWords, getPublicKey, getBech32PublicKey } from '../src/index'
2+
import { getBech32PrivateKey, accountFromSeedWords, getPublicKey, getBech32PublicKey } from '../src/index'
33
import { PUBLIC_KEY_PREFIX, SECRET_KEY_PREFIX } from '../src/constants'
44

55
describe('bech32 formats', () => {
@@ -12,19 +12,23 @@ describe('bech32 formats', () => {
1212
})
1313

1414
it('should generate a valid bech32 private and public key', () => {
15-
const { privateKey } = privateKeyFromSeedWords({ mnemonic: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' })
16-
const { publicKey } = getPublicKey({ privateKey })
17-
const { bech32PrivateKey } = getBech32PrivateKey({ privateKey })
18-
const { bech32PublicKey } = getBech32PublicKey({ publicKey })
15+
const { privateKey } = accountFromSeedWords({
16+
mnemonic: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
17+
})
18+
const { publicKey } = getPublicKey({ privateKey: privateKey.hex })
19+
const { bech32PrivateKey } = getBech32PrivateKey({ privateKey: privateKey.hex })
20+
const { bech32PublicKey } = getBech32PublicKey({ publicKey: publicKey.hex })
1921
expect(bech32PrivateKey).toBe('nsec1tu567wukwcvq9y880f8045n9cnp07299xqjxrae4jl76y6aj2ucs2mkupq')
2022
expect(bech32PublicKey).toBe('npub1az708q3kd9zy6z6f44zav5ygvdwelkzspf6mtusttx47lft2z38sghk0w7')
2123
})
2224

2325
it('should generate a valid bech32 private and public key', () => {
24-
const { privateKey } = privateKeyFromSeedWords({ mnemonic: 'bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon' })
25-
const { publicKey } = getPublicKey({ privateKey })
26-
const { bech32PrivateKey } = getBech32PrivateKey({ privateKey })
27-
const { bech32PublicKey } = getBech32PublicKey({ publicKey })
26+
const { privateKey } = accountFromSeedWords({
27+
mnemonic: 'bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon'
28+
})
29+
const { publicKey } = getPublicKey({ privateKey: privateKey.hex })
30+
const { bech32PrivateKey } = getBech32PrivateKey({ privateKey: privateKey.hex })
31+
const { bech32PublicKey } = getBech32PublicKey({ publicKey: publicKey.hex })
2832
expect(bech32PrivateKey).toBe('nsec1g5zfnqlvrcu24zj204mtqgp6gjzdngwnzgqp0n752h5q4tpd2tjq5khznp')
2933
expect(bech32PublicKey).toBe('npub1s566ayhl06xkt4k3fk5ttvygjjhtkj8gndfflf3qmv2qpq7xhd2sy3ukkd')
3034
})

tests/extendedPairFromSeedWords.test.ts tests/extendedKeysFromSeedWords.test.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { describe, expect, it } from 'vitest'
2-
import { extendedPairFromSeedWords } from '../src/index'
2+
import { extendedKeysFromSeedWords } from '../src/index'
33

44
describe('extendedPairFromSeedWords', () => {
55
it('should get extended keys pair from mnemonic', () => {
6-
const { privateExtendedKey, publicExtendedKey } = extendedPairFromSeedWords('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about')
6+
const { privateExtendedKey, publicExtendedKey } = extendedKeysFromSeedWords({
7+
mnemonic: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
8+
})
79

810
expect(privateExtendedKey).toBe('xprv9z78fizET65qsCaRr1MSutTSGk1fcKfSt1sBqmuWShtkjRJJ4WCKcSnha6EmgNzFSsyom3MWtydHyPtJtSLZQUtictVQtM2vkPcguh6TQCH')
911
expect(publicExtendedKey).toBe('xpub6D6V5EX8HTe95getx2tTH2QApmrA1nPJFEnneAK813RjcDdSc3WaAF7BRNpTF7o7zXjVm3DD3VMX66jhQ7wLaZ9sS6NzyfiwfzqDZbxvpDN')

0 commit comments

Comments
 (0)