@@ -30,6 +30,7 @@ function modN(a: bigint, b: bigint = secp.CURVE.n): bigint {
30
30
const MASTER_SECRET = utf8ToBytes ( "Bitcoin seed" ) ;
31
31
// Bitcoin hardcoded by default
32
32
const BITCOIN_VERSIONS : Versions = { private : 0x0488ade4 , public : 0x0488b21e } ;
33
+ export const HARDENED_OFFSET : number = 0x80000000 ;
33
34
34
35
export interface Versions {
35
36
private : number ;
@@ -44,55 +45,17 @@ const toU32 = (n: number) => {
44
45
return buf ;
45
46
} ;
46
47
47
- export class HDKey {
48
- public static HARDENED_OFFSET : number = 0x80000000 ;
49
- public static fromMasterSeed ( seed : Uint8Array , versions ?: Versions ) : HDKey {
50
- const I = hmac ( sha512 , MASTER_SECRET , seed ) ;
51
- const hdkey = new HDKey ( versions ) ;
52
- hdkey . chainCode = I . slice ( 32 ) ;
53
- hdkey . privateKey = I . slice ( 0 , 32 ) ;
54
- return hdkey ;
55
- }
56
- public static fromExtendedKey ( base58key : string , versions ?: Versions ) : HDKey {
57
- // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33)
58
- const hdkey = new HDKey ( versions ) ;
59
- const keyBuffer : Uint8Array = base58c . decode ( base58key ) ;
60
- const keyView = createView ( keyBuffer ) ;
61
- const version = keyView . getUint32 ( 0 , false ) ;
62
- hdkey . depth = keyBuffer [ 4 ] ;
63
- hdkey . parentFingerprint = keyView . getUint32 ( 5 , false ) ;
64
- hdkey . index = keyView . getUint32 ( 9 , false ) ;
65
- hdkey . chainCode = keyBuffer . slice ( 13 , 45 ) ;
66
- const key = keyBuffer . slice ( 45 ) ;
67
- const isPriv = key [ 0 ] === 0 ;
68
- if ( version !== hdkey . versions [ isPriv ? "private" : "public" ] ) {
69
- throw new Error ( "Version mismatch" ) ;
70
- }
71
- if ( isPriv ) {
72
- hdkey . privateKey = key . slice ( 1 ) ;
73
- } else {
74
- hdkey . publicKey = key ;
75
- }
76
- return hdkey ;
77
- }
78
-
79
- public static fromJSON ( json : { xpriv : string } ) : HDKey {
80
- return HDKey . fromExtendedKey ( json . xpriv ) ;
81
- }
82
-
83
- public versions : Versions ;
84
- public depth : number = 0 ;
85
- public index : number = 0 ;
86
- public chainCode : Uint8Array | null = null ;
87
- public parentFingerprint : number = 0 ;
88
- private privKey ?: bigint ;
89
- private privKeyBytes ?: Uint8Array ;
90
- private pubKey ?: Uint8Array ;
91
- private pubHash : Uint8Array | undefined ;
48
+ interface HDKeyOpt {
49
+ versions : Versions ;
50
+ depth ?: number ;
51
+ index ?: number ;
52
+ parentFingerprint ?: number ;
53
+ chainCode : Uint8Array ;
54
+ publicKey ?: Uint8Array ;
55
+ privateKey ?: Uint8Array | bigint ;
56
+ }
92
57
93
- constructor ( versions ?: Versions ) {
94
- this . versions = versions || BITCOIN_VERSIONS ;
95
- }
58
+ export class HDKey {
96
59
get fingerprint ( ) : number {
97
60
if ( ! this . pubHash ) {
98
61
throw new Error ( "No publicKey set!" ) ;
@@ -108,34 +71,9 @@ export class HDKey {
108
71
get privateKey ( ) : Uint8Array | null {
109
72
return this . privKeyBytes || null ;
110
73
}
111
- set privateKey ( value : Uint8Array | bigint | null ) {
112
- if ( value == null ) {
113
- this . wipePrivateData ( ) ;
114
- return ;
115
- }
116
- if ( ! secp . utils . isValidPrivateKey ( value ) ) {
117
- throw new Error ( "Invalid private key" ) ;
118
- }
119
- this . privKey = typeof value === "bigint" ? value : bytesToNumber ( value ) ;
120
- this . privKeyBytes = numberToBytes ( this . privKey ) ;
121
- this . pubKey = secp . getPublicKey ( value , true ) ;
122
- this . pubHash = hash160 ( this . pubKey ) ;
123
- }
124
74
get publicKey ( ) : Uint8Array | null {
125
75
return this . pubKey || null ;
126
76
}
127
- set publicKey ( value : Uint8Array | null ) {
128
- let hex ;
129
- try {
130
- hex = secp . Point . fromHex ( value ! ) ;
131
- } catch ( error ) {
132
- throw new Error ( "Invalid public key" ) ;
133
- }
134
- this . pubKey = hex . toRawBytes ( true ) ; // force compressed point
135
- this . pubHash = hash160 ( this . pubKey ) ;
136
- this . privKey = undefined ;
137
- }
138
-
139
77
get privateExtendedKey ( ) : string {
140
78
const priv = this . privateKey ;
141
79
if ( ! priv ) {
@@ -155,6 +93,88 @@ export class HDKey {
155
93
return base58c . encode ( this . serialize ( this . versions . public , this . pubKey ) ) ;
156
94
}
157
95
96
+ public static fromMasterSeed (
97
+ seed : Uint8Array ,
98
+ versions : Versions = BITCOIN_VERSIONS
99
+ ) : HDKey {
100
+ const I = hmac ( sha512 , MASTER_SECRET , seed ) ;
101
+ return new HDKey ( {
102
+ versions,
103
+ chainCode : I . slice ( 32 ) ,
104
+ privateKey : I . slice ( 0 , 32 )
105
+ } ) ;
106
+ }
107
+
108
+ public static fromExtendedKey (
109
+ base58key : string ,
110
+ versions : Versions = BITCOIN_VERSIONS
111
+ ) : HDKey {
112
+ // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33)
113
+ const keyBuffer : Uint8Array = base58c . decode ( base58key ) ;
114
+ const keyView = createView ( keyBuffer ) ;
115
+ const version = keyView . getUint32 ( 0 , false ) ;
116
+ const opt = {
117
+ versions,
118
+ depth : keyBuffer [ 4 ] ,
119
+ parentFingerprint : keyView . getUint32 ( 5 , false ) ,
120
+ index : keyView . getUint32 ( 9 , false ) ,
121
+ chainCode : keyBuffer . slice ( 13 , 45 )
122
+ } ;
123
+ const key = keyBuffer . slice ( 45 ) ;
124
+ const isPriv = key [ 0 ] === 0 ;
125
+ if ( version !== versions [ isPriv ? "private" : "public" ] ) {
126
+ throw new Error ( "Version mismatch" ) ;
127
+ }
128
+ if ( isPriv ) {
129
+ return new HDKey ( { ...opt , privateKey : key . slice ( 1 ) } ) ;
130
+ } else {
131
+ return new HDKey ( { ...opt , publicKey : key } ) ;
132
+ }
133
+ }
134
+
135
+ public static fromJSON ( json : { xpriv : string } ) : HDKey {
136
+ return HDKey . fromExtendedKey ( json . xpriv ) ;
137
+ }
138
+ public readonly versions : Versions ;
139
+ public readonly depth : number = 0 ;
140
+ public readonly index : number = 0 ;
141
+ public readonly chainCode : Uint8Array | null = null ;
142
+ public readonly parentFingerprint : number = 0 ;
143
+ private privKey ?: bigint ;
144
+ private privKeyBytes ?: Uint8Array ;
145
+ private pubKey ?: Uint8Array ;
146
+ private pubHash : Uint8Array | undefined ;
147
+
148
+ constructor ( opt : HDKeyOpt ) {
149
+ if ( ! opt || typeof opt !== "object" ) {
150
+ throw new Error ( "HDKey.constructor must not be called directly" ) ;
151
+ }
152
+ this . versions = opt . versions || BITCOIN_VERSIONS ;
153
+ this . depth = opt . depth || 0 ;
154
+ this . chainCode = opt . chainCode ;
155
+ this . index = opt . index || 0 ;
156
+ this . parentFingerprint = opt . parentFingerprint || 0 ;
157
+ if ( opt . publicKey && opt . privateKey ) {
158
+ throw new Error ( "HDKey: publicKey and privateKey at same time." ) ;
159
+ }
160
+ if ( opt . privateKey ) {
161
+ if ( ! secp . utils . isValidPrivateKey ( opt . privateKey ) ) {
162
+ throw new Error ( "Invalid private key" ) ;
163
+ }
164
+ this . privKey =
165
+ typeof opt . privateKey === "bigint"
166
+ ? opt . privateKey
167
+ : bytesToNumber ( opt . privateKey ) ;
168
+ this . privKeyBytes = numberToBytes ( this . privKey ) ;
169
+ this . pubKey = secp . getPublicKey ( opt . privateKey , true ) ;
170
+ } else if ( opt . publicKey ) {
171
+ this . pubKey = secp . Point . fromHex ( opt . publicKey ) . toRawBytes ( true ) ; // force compressed point
172
+ } else {
173
+ throw new Error ( "HDKey: no public or private key provided" ) ;
174
+ }
175
+ this . pubHash = hash160 ( this . pubKey ) ;
176
+ }
177
+
158
178
public derive ( path : string ) : HDKey {
159
179
if ( ! / ^ [ m M ] ' ? / . test ( path ) ) {
160
180
throw new Error ( 'Path must start with "m" or "M"' ) ;
@@ -171,17 +191,18 @@ export class HDKey {
171
191
throw new Error ( `Invalid child index: ${ c } ` ) ;
172
192
}
173
193
let idx = + m [ 1 ] ;
174
- if ( ! Number . isSafeInteger ( idx ) || idx >= HDKey . HARDENED_OFFSET ) {
194
+ if ( ! Number . isSafeInteger ( idx ) || idx >= HARDENED_OFFSET ) {
175
195
throw new Error ( "Invalid index" ) ;
176
196
}
177
197
// hardened key
178
198
if ( m [ 2 ] === "'" ) {
179
- idx += HDKey . HARDENED_OFFSET ;
199
+ idx += HARDENED_OFFSET ;
180
200
}
181
201
child = child . deriveChild ( idx ) ;
182
202
}
183
203
return child ;
184
204
}
205
+
185
206
public deriveChild ( index : number ) : HDKey {
186
207
if ( ! Number . isSafeInteger ( index ) || index < 0 || index >= 2 ** 33 ) {
187
208
throw new Error (
@@ -193,7 +214,7 @@ export class HDKey {
193
214
}
194
215
let data = new Uint8Array ( 4 ) ;
195
216
createView ( data ) . setUint32 ( 0 , index , false ) ;
196
- if ( index >= HDKey . HARDENED_OFFSET ) {
217
+ if ( index >= HARDENED_OFFSET ) {
197
218
// Hardened
198
219
const priv = this . privateKey ;
199
220
if ( ! priv ) {
@@ -211,7 +232,13 @@ export class HDKey {
211
232
if ( ! secp . utils . isValidPrivateKey ( childTweak ) ) {
212
233
throw new Error ( "Tweak bigger than curve order" ) ;
213
234
}
214
- const child = new HDKey ( this . versions ) ;
235
+ const opt : HDKeyOpt = {
236
+ versions : this . versions ,
237
+ chainCode,
238
+ depth : this . depth + 1 ,
239
+ parentFingerprint : this . fingerprint ,
240
+ index
241
+ } ;
215
242
try {
216
243
// Private parent key -> private child key
217
244
if ( this . privateKey ) {
@@ -221,28 +248,29 @@ export class HDKey {
221
248
"The tweak was out of range or the resulted private key is invalid"
222
249
) ;
223
250
}
224
- child . privateKey = added ;
251
+ opt . privateKey = added ;
225
252
} else {
226
- child . publicKey = secp . Point . fromHex ( this . pubKey )
253
+ opt . publicKey = secp . Point . fromHex ( this . pubKey )
227
254
. add ( secp . Point . fromPrivateKey ( childTweak ) )
228
255
. toRawBytes ( true ) ;
229
256
}
257
+ return new HDKey ( opt ) ;
230
258
} catch ( err ) {
231
259
return this . deriveChild ( index + 1 ) ;
232
260
}
233
- child . chainCode = chainCode ;
234
- child . depth = this . depth + 1 ;
235
- child . parentFingerprint = this . fingerprint ;
236
- child . index = index ;
237
- return child ;
238
261
}
262
+
239
263
public sign ( hash : Uint8Array ) : Uint8Array {
240
264
if ( ! this . privateKey ) {
241
265
throw new Error ( "No privateKey set!" ) ;
242
266
}
243
267
assertBytes ( hash , 32 ) ;
244
- return secp . signSync ( hash , this . privKey ! , { canonical : true , der : false } ) ;
268
+ return secp . signSync ( hash , this . privKey ! , {
269
+ canonical : true ,
270
+ der : false
271
+ } ) ;
245
272
}
273
+
246
274
public verify ( hash : Uint8Array , signature : Uint8Array ) : boolean {
247
275
assertBytes ( hash , 32 ) ;
248
276
assertBytes ( signature , 64 ) ;
@@ -257,13 +285,13 @@ export class HDKey {
257
285
}
258
286
return secp . verify ( sig , hash , this . publicKey ) ;
259
287
}
288
+
260
289
public wipePrivateData ( ) : this {
261
- if ( this . privKey ) {
262
- this . privKey = undefined ;
263
- this . privKeyBytes ! . fill ( 0 ) ;
290
+ this . privKey = undefined ;
291
+ if ( this . privKeyBytes ) {
292
+ this . privKeyBytes . fill ( 0 ) ;
264
293
this . privKeyBytes = undefined ;
265
294
}
266
- this . privKey = undefined ;
267
295
return this ;
268
296
}
269
297
public toJSON ( ) : { xpriv : string ; xpub : string } {
0 commit comments