11import assert from 'assert' ;
22
3- import { Transaction } from '@bitgo/utxo-lib' ;
3+ import * as utxolib from '@bitgo/utxo-lib' ;
44
55import * as bip322 from '../../src/bip322' ;
66
77import { BIP322_PAYMENT_P2WPKH_FIXTURE , BIP322_PRV_FIXTURE as prv } from './bip322.utils' ;
8+
89describe ( 'BIP322 toSign' , function ( ) {
910 describe ( 'buildToSignPsbt' , function ( ) {
1011 const scriptPubKey = BIP322_PAYMENT_P2WPKH_FIXTURE . output as Buffer ;
@@ -28,12 +29,122 @@ describe('BIP322 toSign', function () {
2829 } ;
2930 const result = bip322 . buildToSignPsbt ( toSpendTx , addressDetails ) ;
3031 const computedTxid = result
31- . signAllInputs ( prv , [ Transaction . SIGHASH_ALL ] )
32+ . signAllInputs ( prv , [ utxolib . Transaction . SIGHASH_ALL ] )
3233 . finalizeAllInputs ( )
3334 . extractTransaction ( )
3435 . getId ( ) ;
3536 assert . strictEqual ( computedTxid , txid , `Transaction ID for message "${ message } " does not match expected value` ) ;
3637 } ) ;
3738 } ) ;
3839 } ) ;
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+ } ) ;
39150} ) ;
0 commit comments