1
1
import assert from 'assert' ;
2
2
3
- import { Transaction } from '@bitgo/utxo-lib' ;
3
+ import * as utxolib from '@bitgo/utxo-lib' ;
4
4
5
5
import * as bip322 from '../../src/bip322' ;
6
6
7
7
import { BIP322_PAYMENT_P2WPKH_FIXTURE , BIP322_PRV_FIXTURE as prv } from './bip322.utils' ;
8
+
8
9
describe ( 'BIP322 toSign' , function ( ) {
9
10
describe ( 'buildToSignPsbt' , function ( ) {
10
11
const scriptPubKey = BIP322_PAYMENT_P2WPKH_FIXTURE . output as Buffer ;
@@ -28,12 +29,122 @@ describe('BIP322 toSign', function () {
28
29
} ;
29
30
const result = bip322 . buildToSignPsbt ( toSpendTx , addressDetails ) ;
30
31
const computedTxid = result
31
- . signAllInputs ( prv , [ Transaction . SIGHASH_ALL ] )
32
+ . signAllInputs ( prv , [ utxolib . Transaction . SIGHASH_ALL ] )
32
33
. finalizeAllInputs ( )
33
34
. extractTransaction ( )
34
35
. getId ( ) ;
35
36
assert . strictEqual ( computedTxid , txid , `Transaction ID for message "${ message } " does not match expected value` ) ;
36
37
} ) ;
37
38
} ) ;
38
39
} ) ;
40
+
41
+ describe ( 'buildToSignPsbtForChainAndIndex' , function ( ) {
42
+ const rootWalletKeys = utxolib . testutil . getDefaultWalletKeys ( ) ;
43
+
44
+ it ( 'should fail when scriptPubKey of to_spend is different than to_sign' , function ( ) {
45
+ const toSpendTx = bip322 . buildToSpendTransaction ( BIP322_PAYMENT_P2WPKH_FIXTURE . output as Buffer , 'Hello World' ) ;
46
+ assert . throws ( ( ) => {
47
+ bip322 . buildToSignPsbtForChainAndIndex ( toSpendTx , rootWalletKeys , 0 , 0 ) ;
48
+ } , / O u t p u t s c r i p t P u b K e y d o e s n o t m a t c h t h e e x p e c t e d o u t p u t s c r i p t f o r t h e c h a i n a n d i n d e x ./ ) ;
49
+ } ) ;
50
+
51
+ function run ( chain : utxolib . bitgo . ChainCode , shouldFail : boolean , index : number ) {
52
+ it ( `should${
53
+ shouldFail ? ' fail to' : ''
54
+ } build and sign a to_sign PSBT for chain ${ chain } , index ${ index } `, function ( ) {
55
+ const message = 'I can believe it is not butter' ;
56
+ if ( shouldFail ) {
57
+ assert . throws ( ( ) => {
58
+ bip322 . buildToSpendTransactionFromChainAndIndex ( rootWalletKeys , chain , index , message ) ;
59
+ } , / B I P 3 2 2 i s n o t s u p p o r t e d f o r T a p r o o t s c r i p t t y p e s ./ ) ;
60
+ return ;
61
+ }
62
+ const toSpendTx = bip322 . buildToSpendTransactionFromChainAndIndex ( rootWalletKeys , chain , index , message ) ;
63
+ const toSignPsbt = bip322 . buildToSignPsbtForChainAndIndex ( toSpendTx , rootWalletKeys , chain , index ) ;
64
+
65
+ const derivedKeys = rootWalletKeys . deriveForChainAndIndex ( chain , index ) ;
66
+ const prv1 = derivedKeys . triple [ 0 ] ;
67
+ const prv2 = derivedKeys . triple [ 1 ] ;
68
+ assert . ok ( prv1 ) ;
69
+ assert . ok ( prv2 ) ;
70
+
71
+ // Can sign the PSBT with the keys
72
+ toSignPsbt . signAllInputs ( prv1 , [ utxolib . Transaction . SIGHASH_ALL ] ) ;
73
+ toSignPsbt . signAllInputs ( prv2 , [ utxolib . Transaction . SIGHASH_ALL ] ) ;
74
+
75
+ // Wrap the PSBT as a UtxoPsbt so that we can use the validateSignaturesOfInputCommon method
76
+ const utxopsbt = utxolib . bitgo . createPsbtFromBuffer ( toSignPsbt . toBuffer ( ) , utxolib . networks . bitcoin ) ;
77
+ derivedKeys . publicKeys . forEach ( ( pubkey , i ) => {
78
+ assert . deepStrictEqual (
79
+ utxopsbt . validateSignaturesOfInputCommon ( 0 , pubkey ) ,
80
+ i !== 2 ,
81
+ `Signature validation failed for public key at index ${ i } `
82
+ ) ;
83
+ } ) ;
84
+
85
+ // finalize and extract
86
+ const tx = toSignPsbt . finalizeAllInputs ( ) . extractTransaction ( ) ;
87
+ assert . ok ( tx ) ;
88
+
89
+ // Check that the transaction matches the full BIP322 format
90
+ // Source: https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#full
91
+ // For the to_spend transaction, verify that all of the properties are set correctly,
92
+ // then get the txid and make sure that it matches the value in the `to_sign` tx
93
+ assert . deepStrictEqual ( toSpendTx . version , 0 , 'version must be 0' ) ;
94
+ assert . deepStrictEqual ( toSpendTx . locktime , 0 , 'locktime must be 0' ) ;
95
+ assert . deepStrictEqual (
96
+ toSpendTx . ins [ 0 ] . hash . toString ( 'hex' ) ,
97
+ '0000000000000000000000000000000000000000000000000000000000000000' ,
98
+ 'input hash must be a 32 byte zero buffer'
99
+ ) ;
100
+ assert . deepStrictEqual ( toSpendTx . ins [ 0 ] . index , 0xffffffff , 'input index must be 0xFFFFFFFF' ) ;
101
+ assert . deepStrictEqual ( toSpendTx . ins [ 0 ] . sequence , 0 , 'input sequence must be 0' ) ;
102
+ assert . deepStrictEqual (
103
+ toSpendTx . ins [ 0 ] . script . toString ( 'hex' ) ,
104
+ Buffer . concat ( [ Buffer . from ( [ 0x00 , 0x20 ] ) , bip322 . hashMessageWithTag ( message ) ] ) . toString ( 'hex' ) ,
105
+ 'input script must be OP_0 PUSH32[ message_hash ]'
106
+ ) ;
107
+ assert . ok ( Array . isArray ( toSpendTx . ins [ 0 ] . witness ) , 'input witness must be an array' ) ;
108
+ assert . deepStrictEqual ( toSpendTx . ins [ 0 ] . witness . length , 0 , 'input witness must be empty' ) ;
109
+ assert . deepStrictEqual ( toSpendTx . ins . length , 1 , 'to_spend transaction must have one input' ) ;
110
+ assert . deepStrictEqual ( toSpendTx . outs . length , 1 , 'to_spend transaction must have one output' ) ;
111
+ assert . deepStrictEqual ( toSpendTx . outs [ 0 ] . value , BigInt ( 0 ) , 'output value must be 0' ) ;
112
+ assert . deepStrictEqual (
113
+ toSpendTx . outs [ 0 ] . script . toString ( 'hex' ) ,
114
+ utxolib . bitgo . outputScripts
115
+ . createOutputScript2of3 (
116
+ derivedKeys . publicKeys ,
117
+ utxolib . bitgo . scriptTypeForChain ( chain ) ,
118
+ utxolib . networks . bitcoin
119
+ )
120
+ . scriptPubKey . toString ( 'hex' ) ,
121
+ 'the script pubkey of the to_spend output must be the scriptPubKey of the address we are proving ownership of'
122
+ ) ;
123
+ assert . deepStrictEqual ( tx . ins . length , 1 , 'to_sign transaction must have one input' ) ;
124
+ assert . deepStrictEqual ( tx . version , 0 , 'to_sign transaction version must be 0' ) ;
125
+ assert . deepStrictEqual ( tx . locktime , 0 , 'to_sign transaction locktime must be 0' ) ;
126
+ assert . deepStrictEqual (
127
+ utxolib . bitgo . getOutputIdForInput ( tx . ins [ 0 ] ) . txid ,
128
+ toSpendTx . getId ( ) ,
129
+ 'to_sign transaction input must reference the to_spend transaction'
130
+ ) ;
131
+ assert . deepStrictEqual ( tx . ins [ 0 ] . index , 0 , 'to_sign transaction input index must be 0' ) ;
132
+ assert . deepStrictEqual ( tx . ins [ 0 ] . sequence , 0 , 'to_sign transaction input sequence must be 0' ) ;
133
+ // We are not going to explicitly check the script witness on this transaction because we already verified the
134
+ // signatures on the PSBT for the respective public keys. All that would be verified here is that we can assemble
135
+ // the script witness correctly, which must be true orelse we would have a much bigger problem.
136
+ assert . deepStrictEqual ( tx . outs . length , 1 , 'to_sign transaction must have one output' ) ;
137
+ assert . deepStrictEqual ( tx . outs [ 0 ] . value , BigInt ( 0 ) , 'to_sign transaction output value must be 0' ) ;
138
+ assert . deepStrictEqual (
139
+ tx . outs [ 0 ] . script . toString ( 'hex' ) ,
140
+ '6a' ,
141
+ 'to_sign transaction output script must be OP_RETURN'
142
+ ) ;
143
+ } ) ;
144
+ }
145
+
146
+ utxolib . bitgo . chainCodes . forEach ( ( chain , i ) => {
147
+ run ( chain , bip322 . isTaprootChain ( chain ) , i ) ;
148
+ } ) ;
149
+ } ) ;
39
150
} ) ;
0 commit comments