@@ -30,6 +30,7 @@ function modN(a: bigint, b: bigint = secp.CURVE.n): bigint {
3030const MASTER_SECRET = utf8ToBytes ( "Bitcoin seed" ) ;
3131// Bitcoin hardcoded by default
3232const BITCOIN_VERSIONS : Versions = { private : 0x0488ade4 , public : 0x0488b21e } ;
33+ export const HARDENED_OFFSET : number = 0x80000000 ;
3334
3435export interface Versions {
3536 private : number ;
@@ -44,55 +45,17 @@ const toU32 = (n: number) => {
4445 return buf ;
4546} ;
4647
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+ }
9257
93- constructor ( versions ?: Versions ) {
94- this . versions = versions || BITCOIN_VERSIONS ;
95- }
58+ export class HDKey {
9659 get fingerprint ( ) : number {
9760 if ( ! this . pubHash ) {
9861 throw new Error ( "No publicKey set!" ) ;
@@ -108,34 +71,9 @@ export class HDKey {
10871 get privateKey ( ) : Uint8Array | null {
10972 return this . privKeyBytes || null ;
11073 }
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- }
12474 get publicKey ( ) : Uint8Array | null {
12575 return this . pubKey || null ;
12676 }
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-
13977 get privateExtendedKey ( ) : string {
14078 const priv = this . privateKey ;
14179 if ( ! priv ) {
@@ -155,6 +93,88 @@ export class HDKey {
15593 return base58c . encode ( this . serialize ( this . versions . public , this . pubKey ) ) ;
15694 }
15795
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+
158178 public derive ( path : string ) : HDKey {
159179 if ( ! / ^ [ m M ] ' ? / . test ( path ) ) {
160180 throw new Error ( 'Path must start with "m" or "M"' ) ;
@@ -171,17 +191,18 @@ export class HDKey {
171191 throw new Error ( `Invalid child index: ${ c } ` ) ;
172192 }
173193 let idx = + m [ 1 ] ;
174- if ( ! Number . isSafeInteger ( idx ) || idx >= HDKey . HARDENED_OFFSET ) {
194+ if ( ! Number . isSafeInteger ( idx ) || idx >= HARDENED_OFFSET ) {
175195 throw new Error ( "Invalid index" ) ;
176196 }
177197 // hardened key
178198 if ( m [ 2 ] === "'" ) {
179- idx += HDKey . HARDENED_OFFSET ;
199+ idx += HARDENED_OFFSET ;
180200 }
181201 child = child . deriveChild ( idx ) ;
182202 }
183203 return child ;
184204 }
205+
185206 public deriveChild ( index : number ) : HDKey {
186207 if ( ! Number . isSafeInteger ( index ) || index < 0 || index >= 2 ** 33 ) {
187208 throw new Error (
@@ -193,7 +214,7 @@ export class HDKey {
193214 }
194215 let data = new Uint8Array ( 4 ) ;
195216 createView ( data ) . setUint32 ( 0 , index , false ) ;
196- if ( index >= HDKey . HARDENED_OFFSET ) {
217+ if ( index >= HARDENED_OFFSET ) {
197218 // Hardened
198219 const priv = this . privateKey ;
199220 if ( ! priv ) {
@@ -211,7 +232,13 @@ export class HDKey {
211232 if ( ! secp . utils . isValidPrivateKey ( childTweak ) ) {
212233 throw new Error ( "Tweak bigger than curve order" ) ;
213234 }
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+ } ;
215242 try {
216243 // Private parent key -> private child key
217244 if ( this . privateKey ) {
@@ -221,28 +248,29 @@ export class HDKey {
221248 "The tweak was out of range or the resulted private key is invalid"
222249 ) ;
223250 }
224- child . privateKey = added ;
251+ opt . privateKey = added ;
225252 } else {
226- child . publicKey = secp . Point . fromHex ( this . pubKey )
253+ opt . publicKey = secp . Point . fromHex ( this . pubKey )
227254 . add ( secp . Point . fromPrivateKey ( childTweak ) )
228255 . toRawBytes ( true ) ;
229256 }
257+ return new HDKey ( opt ) ;
230258 } catch ( err ) {
231259 return this . deriveChild ( index + 1 ) ;
232260 }
233- child . chainCode = chainCode ;
234- child . depth = this . depth + 1 ;
235- child . parentFingerprint = this . fingerprint ;
236- child . index = index ;
237- return child ;
238261 }
262+
239263 public sign ( hash : Uint8Array ) : Uint8Array {
240264 if ( ! this . privateKey ) {
241265 throw new Error ( "No privateKey set!" ) ;
242266 }
243267 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+ } ) ;
245272 }
273+
246274 public verify ( hash : Uint8Array , signature : Uint8Array ) : boolean {
247275 assertBytes ( hash , 32 ) ;
248276 assertBytes ( signature , 64 ) ;
@@ -257,13 +285,13 @@ export class HDKey {
257285 }
258286 return secp . verify ( sig , hash , this . publicKey ) ;
259287 }
288+
260289 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 ) ;
264293 this . privKeyBytes = undefined ;
265294 }
266- this . privKey = undefined ;
267295 return this ;
268296 }
269297 public toJSON ( ) : { xpriv : string ; xpub : string } {
0 commit comments