main
go 212 lines 5.75 KB
Raw
1 package identity
2
3 import (
4 "crypto/hmac"
5 "crypto/sha512"
6 "encoding/binary"
7 "encoding/hex"
8 "errors"
9 "fmt"
10 "math/big"
11 "strconv"
12 "strings"
13
14 "github.com/decred/dcrd/dcrec/secp256k1/v4"
15 "github.com/tyler-smith/go-bip39"
16 )
17
18 const (
19 DefaultEVMIdentityDerivationPath = "m/44'/60'/0'/0/0"
20
21 bip32HardenedOffset = uint32(0x80000000)
22 )
23
24 type derivationPath []uint32
25
26 var defaultEVMRootDerivationPath = derivationPath{
27 bip32HardenedOffset + 44,
28 bip32HardenedOffset + 60,
29 bip32HardenedOffset,
30 0,
31 }
32
33 func deriveSecp256k1PrivateKeyFromMnemonic(rawMnemonic, rawDerivationPath string) (string, string, error) {
34 mnemonic := normalizeMnemonic(rawMnemonic)
35 if mnemonic == "" {
36 return "", "", errors.New("identity mnemonic is required")
37 }
38
39 derivationPath := strings.TrimSpace(rawDerivationPath)
40 if derivationPath == "" {
41 derivationPath = DefaultEVMIdentityDerivationPath
42 }
43 path, err := parseDerivationPath(derivationPath)
44 if err != nil {
45 return "", "", fmt.Errorf("parse identity derivation path: %w", err)
46 }
47
48 seed, err := bip39.NewSeedWithErrorChecking(mnemonic, "")
49 if err != nil {
50 return "", "", fmt.Errorf("validate identity mnemonic: %w", err)
51 }
52 privateKey, err := deriveBIP32Secp256k1PrivateKey(seed, path)
53 if err != nil {
54 return "", "", err
55 }
56 return hex.EncodeToString(privateKey), path.String(), nil
57 }
58
59 func normalizeMnemonic(raw string) string {
60 return strings.ToLower(strings.Join(strings.Fields(raw), " "))
61 }
62
63 func parseDerivationPath(raw string) (derivationPath, error) {
64 components := strings.Split(raw, "/")
65 if len(components) == 0 {
66 return nil, errors.New("empty derivation path")
67 }
68
69 var path derivationPath
70 switch strings.TrimSpace(components[0]) {
71 case "":
72 return nil, errors.New("ambiguous path: use 'm/' prefix for absolute paths, or no leading '/' for relative ones")
73 case "m":
74 components = components[1:]
75 default:
76 path = append(path, defaultEVMRootDerivationPath...)
77 }
78 if len(components) == 0 {
79 return nil, errors.New("empty derivation path")
80 }
81
82 for _, component := range components {
83 component = strings.TrimSpace(component)
84 hardened := strings.HasSuffix(component, "'")
85 if hardened {
86 component = strings.TrimSpace(strings.TrimSuffix(component, "'"))
87 }
88 value, err := strconv.ParseUint(component, 0, 32)
89 if err != nil {
90 return nil, fmt.Errorf("invalid component: %s", component)
91 }
92 if hardened {
93 if value >= uint64(bip32HardenedOffset) {
94 return nil, fmt.Errorf("component %d out of allowed hardened range [0, %d]", value, bip32HardenedOffset-1)
95 }
96 value += uint64(bip32HardenedOffset)
97 }
98 path = append(path, uint32(value))
99 }
100 return path, nil
101 }
102
103 func (path derivationPath) String() string {
104 var builder strings.Builder
105 builder.WriteByte('m')
106 for _, component := range path {
107 builder.WriteByte('/')
108 hardened := component >= bip32HardenedOffset
109 if hardened {
110 component -= bip32HardenedOffset
111 }
112 builder.WriteString(strconv.FormatUint(uint64(component), 10))
113 if hardened {
114 builder.WriteByte('\'')
115 }
116 }
117 return builder.String()
118 }
119
120 func deriveBIP32Secp256k1PrivateKey(seed []byte, path derivationPath) ([]byte, error) {
121 if len(seed) == 0 {
122 return nil, errors.New("identity mnemonic seed is required")
123 }
124 if len(path) == 0 {
125 return nil, errors.New("identity derivation path is required")
126 }
127
128 mac := hmac.New(sha512.New, []byte("Bitcoin seed"))
129 _, _ = mac.Write(seed)
130 digest := mac.Sum(nil)
131
132 privateKey, err := normalizeBIP32PrivateKey(digest[:32])
133 if err != nil {
134 return nil, fmt.Errorf("derive identity master key: %w", err)
135 }
136 chainCode := append([]byte(nil), digest[32:]...)
137
138 for _, child := range path {
139 privateKey, chainCode, err = deriveBIP32Secp256k1ChildPrivateKey(privateKey, chainCode, child)
140 if err != nil {
141 return nil, fmt.Errorf("derive identity child key %d: %w", child, err)
142 }
143 }
144 return privateKey, nil
145 }
146
147 func deriveBIP32Secp256k1ChildPrivateKey(parentPrivateKey, parentChainCode []byte, child uint32) ([]byte, []byte, error) {
148 if len(parentChainCode) != 32 {
149 return nil, nil, errors.New("parent chain code must be 32 bytes")
150 }
151 parentKey, err := normalizeBIP32PrivateKey(parentPrivateKey)
152 if err != nil {
153 return nil, nil, fmt.Errorf("parent private key: %w", err)
154 }
155
156 data := make([]byte, 0, 37)
157 if child >= bip32HardenedOffset {
158 data = append(data, 0)
159 data = append(data, parentKey...)
160 } else {
161 data = append(data, secp256k1.PrivKeyFromBytes(parentKey).PubKey().SerializeCompressed()...)
162 }
163 var childBytes [4]byte
164 binary.BigEndian.PutUint32(childBytes[:], child)
165 data = append(data, childBytes[:]...)
166
167 mac := hmac.New(sha512.New, parentChainCode)
168 _, _ = mac.Write(data)
169 digest := mac.Sum(nil)
170
171 childKey, err := addBIP32PrivateKeys(digest[:32], parentKey)
172 if err != nil {
173 return nil, nil, err
174 }
175 return childKey, append([]byte(nil), digest[32:]...), nil
176 }
177
178 func addBIP32PrivateKeys(left, right []byte) ([]byte, error) {
179 order := secp256k1.Params().N
180 leftInt := new(big.Int).SetBytes(left)
181 if leftInt.Sign() == 0 || leftInt.Cmp(order) >= 0 {
182 return nil, errors.New("child key offset is outside the secp256k1 order")
183 }
184 rightInt := new(big.Int).SetBytes(right)
185 if rightInt.Sign() == 0 || rightInt.Cmp(order) >= 0 {
186 return nil, errors.New("parent private key is outside the secp256k1 order")
187 }
188
189 child := leftInt.Add(leftInt, rightInt)
190 child.Mod(child, order)
191 if child.Sign() == 0 {
192 return nil, errors.New("derived private key is zero")
193 }
194 return padded32(child), nil
195 }
196
197 func normalizeBIP32PrivateKey(raw []byte) ([]byte, error) {
198 key := new(big.Int).SetBytes(raw)
199 if key.Sign() == 0 {
200 return nil, errors.New("private key is zero")
201 }
202 if key.Cmp(secp256k1.Params().N) >= 0 {
203 return nil, errors.New("private key is outside the secp256k1 order")
204 }
205 return padded32(key), nil
206 }
207
208 func padded32(value *big.Int) []byte {
209 out := make([]byte, 32)
210 value.FillBytes(out)
211 return out
212 }