1
+ import * as descriptors from '@bitcoinerlab/descriptors' ;
2
+ import * as secp256k1 from '@bitcoinerlab/secp256k1' ;
3
+ const { Descriptor } = descriptors . DescriptorsFactory ( secp256k1 ) ;
1
4
import Transport from '@ledgerhq/hw-transport' ;
5
+ import { networks } from 'bitcoinjs-lib' ;
2
6
3
7
import { pathElementsToBuffer , pathStringToArray } from './bip32' ;
4
8
import { ClientCommandInterpreter } from './clientCommands' ;
@@ -184,8 +188,6 @@ export class AppClient {
184
188
walletPolicy : WalletPolicy
185
189
) : Promise < readonly [ Buffer , Buffer ] > {
186
190
187
- await this . validatePolicy ( walletPolicy ) ;
188
-
189
191
const clientInterpreter = new ClientCommandInterpreter ( ) ;
190
192
191
193
clientInterpreter . addKnownWalletPolicy ( walletPolicy ) ;
@@ -236,8 +238,6 @@ export class AppClient {
236
238
throw new Error ( 'Invalid HMAC length' ) ;
237
239
}
238
240
239
- await this . validatePolicy ( walletPolicy ) ;
240
-
241
241
const clientInterpreter = new ClientCommandInterpreter ( ) ;
242
242
243
243
clientInterpreter . addKnownWalletPolicy ( walletPolicy ) ;
@@ -257,7 +257,9 @@ export class AppClient {
257
257
clientInterpreter
258
258
) ;
259
259
260
- return response . toString ( 'ascii' ) ;
260
+ const address = response . toString ( 'ascii' ) ;
261
+ await this . validateAddress ( address , walletPolicy , change , addressIndex ) ;
262
+ return address ;
261
263
}
262
264
263
265
/**
@@ -279,7 +281,6 @@ export class AppClient {
279
281
walletHMAC : Buffer | null ,
280
282
progressCallback ?: ( ) => void
281
283
) : Promise < [ number , PartialSignature ] [ ] > {
282
- await this . validatePolicy ( walletPolicy ) ;
283
284
284
285
if ( typeof psbt === 'string' ) {
285
286
psbt = Buffer . from ( psbt , "base64" ) ;
@@ -402,12 +403,69 @@ export class AppClient {
402
403
return result . toString ( 'base64' ) ;
403
404
}
404
405
406
+ /* Performs any additional check on the genetated address before returning it.*/
407
+ private async validateAddress (
408
+ address : string ,
409
+ walletPolicy : WalletPolicy ,
410
+ change : number ,
411
+ addressIndex : number
412
+ ) {
413
+ if ( change !== 0 && change !== 1 )
414
+ throw new Error ( 'Change can only be 0 or 1' ) ;
415
+ if ( addressIndex < 0 || ! Number . isInteger ( addressIndex ) )
416
+ throw new Error ( 'Invalid address index' ) ;
417
+ const appAndVer = await this . getAppAndVersion ( ) ;
418
+ let network ;
419
+ if ( appAndVer . name === 'Bitcoin Test' ) {
420
+ network = networks . testnet ;
421
+ } else if ( appAndVer . name === 'Bitcoin' ) {
422
+ network = networks . bitcoin ;
423
+ } else {
424
+ throw new Error (
425
+ `Invalid network: ${ appAndVer . name } . Expected 'Bitcoin Test' or 'Bitcoin'.`
426
+ ) ;
427
+ }
428
+ let expression = walletPolicy . descriptorTemplate ;
429
+ for ( let i = 0 ; i < walletPolicy . keys . length ; i ++ ) {
430
+ const keyPath = walletPolicy . keys [ i ] + '/' + change + '/' + addressIndex ;
431
+ expression = expression
432
+ . replace ( '@' + i + '/**' , keyPath )
433
+ . replace ( '@' + i + '/<0;1>' , keyPath ) ;
434
+ }
435
+ let thirdPartyValidationApplicable = true ;
436
+ let thirdPartyGeneratedAddress : string ;
437
+ try {
438
+ thirdPartyGeneratedAddress = new Descriptor ( {
439
+ expression,
440
+ network
441
+ } ) . getAddress ( ) ;
442
+ } catch ( err ) {
443
+ // Note: @bitcoinerlab /[email protected] does not support Tapscript yet.
444
+ // These are the supported descriptors:
445
+ // - pkh(KEY)
446
+ // - wpkh(KEY)
447
+ // - sh(wpkh(KEY))
448
+ // - sh(SCRIPT)
449
+ // - wsh(SCRIPT)
450
+ // - sh(wsh(SCRIPT)), where
451
+ // SCRIPT is any of the (non-tapscript) fragments in: https://bitcoin.sipa.be/miniscript/
452
+ //
453
+ // Other expressions are not supported and third party validation would not be applicable:
454
+ thirdPartyValidationApplicable = false ;
455
+ }
456
+ if ( thirdPartyValidationApplicable ) {
457
+ if ( address !== thirdPartyGeneratedAddress ) {
458
+ throw new Error (
459
+ `Third party address validation mismatch: ${ address } != ${ thirdPartyGeneratedAddress } `
460
+ ) ;
461
+ }
462
+ } else {
463
+ await this . validatePolicy ( walletPolicy ) ;
464
+ }
465
+ }
466
+
405
467
/* Performs any additional checks on the policy before using it.*/
406
468
private async validatePolicy ( walletPolicy : WalletPolicy ) {
407
- // TODO: Once an independent implementation of miniscript in JavaScript is available,
408
- // we will replace the checks in this section with a generic comparison between the
409
- // address produced by the app and the one computed locally (like the python and Rust
410
- // clients). Until then, we filter for any known bug.
411
469
412
470
let appAndVer = undefined ;
413
471
0 commit comments