bip32.mx raw
1 // Package bip32 implements BIP-32 hierarchical deterministic key derivation
2 // for secp256k1. https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
3 //
4 // Only the secret-key derivation path is exposed (DerivePath, DeriveChild).
5 // Public-key-only derivation (xpub) is not implemented since the smesh
6 // signer always derives from the master seed with the secret key in hand.
7 //
8 // Lives in its own package so callers of crypto/secp256k1 do not transitively
9 // link in crypto/hmac and crypto/sha512 (and a non-trivial amount of dead code
10 // when the moxie linker is run without dead-code elimination).
11 package bip32
12
13 import (
14 "crypto/hmac"
15 "crypto/secp256k1"
16 "crypto/sha512"
17 )
18
19 // HardenedBit is the BIP-32 hardened-derivation marker (index high bit).
20 const HardenedBit = uint32(0x80000000)
21
22 // MasterSeed is the BIP-32 master key separator ("Bitcoin seed").
23 const masterKey = "Bitcoin seed"
24
25 // DeriveChild derives a child key + chain code from a parent key + chain code
26 // at the given index. Returns (nil, nil) on failure (parent key invalid for
27 // non-hardened derivation, child scalar at zero, etc.).
28 func DeriveChild(parentKey, chainCode []byte, index uint32) (childKey, childChain []byte) {
29 if len(parentKey) != 32 || len(chainCode) != 32 {
30 return nil, nil
31 }
32 data := []byte{:37}
33 if index&HardenedBit != 0 {
34 data[0] = 0x00
35 copy(data[1:], parentKey)
36 } else {
37 var sk [32]byte
38 copy(sk[:], parentKey)
39 cpk, ok := secp256k1.CompressedPubKey(sk)
40 if !ok {
41 return nil, nil
42 }
43 copy(data, cpk[:])
44 }
45 data[33] = byte(index >> 24)
46 data[34] = byte(index >> 16)
47 data[35] = byte(index >> 8)
48 data[36] = byte(index)
49 mac := hmac.New(sha512.New, chainCode)
50 mac.Write(data)
51 result := mac.Sum(nil)
52 if len(result) != 64 {
53 return nil, nil
54 }
55 il := result[:32]
56 childChain = result[32:]
57 var ilArr, parentArr [32]byte
58 copy(ilArr[:], il)
59 copy(parentArr[:], parentKey)
60 r, ok := secp256k1.ScalarAddModN(parentArr, ilArr)
61 if !ok {
62 return nil, nil
63 }
64 childKey = r[:]
65 return childKey, childChain
66 }
67
68 // DerivePath derives a key + chain code from a 64-byte master seed along the
69 // given BIP-32 path. Empty path returns the master key + chain code as-is.
70 // Returns (nil, nil) on any derivation failure.
71 func DerivePath(seed []byte, path []uint32) (key, chain []byte) {
72 mac := hmac.New(sha512.New, masterKey)
73 mac.Write(seed)
74 master := mac.Sum(nil)
75 if len(master) != 64 {
76 return nil, nil
77 }
78 key = master[:32]
79 chain = master[32:]
80 for _, idx := range path {
81 key, chain = DeriveChild(key, chain, idx)
82 if key == nil {
83 return nil, nil
84 }
85 }
86 return key, chain
87 }
88
89 // NostrPath returns the BIP-32 path for a Nostr key at the given account index.
90 // NIP-06: m/44'/1237'/account'/0/0
91 func NostrPath(account int) []uint32 {
92 return []uint32{
93 44 | HardenedBit,
94 1237 | HardenedBit,
95 uint32(account) | HardenedBit,
96 0,
97 0,
98 }
99 }
100