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