@@ -5,17 +5,20 @@ import {
55 ElectrumNetworkProvider ,
66 Network ,
77 TransactionBuilder ,
8+ SignatureAlgorithm ,
9+ HashType ,
810} from '../../src/index.js' ;
911import {
1012 alicePriv ,
1113 alicePub ,
1214 oracle ,
1315 oraclePub ,
1416} from '../fixture/vars.js' ;
15- import { gatherUtxos , getTxOutputs } from '../test-util.js' ;
17+ import { gatherUtxos , getTxOutputs , itOrSkip } from '../test-util.js' ;
1618import { FailedRequireError } from '../../src/Errors.js' ;
1719import artifact from '../fixture/hodl_vault.artifact.js' ;
1820import { randomUtxo } from '../../src/utils.js' ;
21+ import { placeholder } from '@cashscript/utils' ;
1922
2023describe ( 'HodlVault' , ( ) => {
2124 const provider = process . env . TESTS_USE_CHIPNET
@@ -95,5 +98,110 @@ describe('HodlVault', () => {
9598 const txOutputs = getTxOutputs ( tx ) ;
9699 expect ( txOutputs ) . toEqual ( expect . arrayContaining ( [ { to, amount } ] ) ) ;
97100 } ) ;
101+
102+ it ( 'should succeed when price is high enough, ECDSA sig and datasig' , async ( ) => {
103+ // given
104+ const message = oracle . createMessage ( 100000n , 30000n ) ;
105+ const oracleSig = oracle . signMessage ( message , SignatureAlgorithm . ECDSA ) ;
106+ const to = hodlVault . address ;
107+ const amount = 10000n ;
108+ const { utxos, changeAmount } = gatherUtxos ( await hodlVault . getUtxos ( ) , { amount, fee : 2000n } ) ;
109+
110+ const signatureTemplate = new SignatureTemplate ( alicePriv , HashType . SIGHASH_ALL , SignatureAlgorithm . ECDSA ) ;
111+
112+ // when
113+ const tx = await new TransactionBuilder ( { provider } )
114+ . addInputs ( utxos , hodlVault . unlock . spend ( signatureTemplate , oracleSig , message ) )
115+ . addOutput ( { to : to , amount : amount } )
116+ . addOutput ( { to : to , amount : changeAmount } )
117+ . setLocktime ( 100_000 )
118+ . send ( ) ;
119+
120+ // then
121+ const txOutputs = getTxOutputs ( tx ) ;
122+ expect ( txOutputs ) . toEqual ( expect . arrayContaining ( [ { to, amount } ] ) ) ;
123+ } ) ;
124+
125+ itOrSkip ( ! Boolean ( process . env . TESTS_USE_CHIPNET ) , 'should succeed with precomputed ECDSA signature' , async ( ) => {
126+ // given
127+ const cleanProvider = new MockNetworkProvider ( ) ;
128+ const contract = new Contract ( artifact , [ alicePub , oraclePub , 99000n , 30000n ] , { provider : cleanProvider } ) ;
129+ cleanProvider . addUtxo ( contract . address , {
130+ satoshis : 100000n ,
131+ txid : '11' . repeat ( 32 ) ,
132+ vout : 0 ,
133+ } ) ;
134+ const message = oracle . createMessage ( 100000n , 30000n ) ;
135+ const oracleSig = oracle . signMessage ( message , SignatureAlgorithm . ECDSA ) ;
136+ const to = contract . address ;
137+ const amount = 10000n ;
138+ const { utxos, changeAmount } = gatherUtxos ( await contract . getUtxos ( ) , { amount, fee : 2000n } ) ;
139+ const signature = '3045022100aa004a425c0c911594c0333164f990c760991b7f84272f35d98c9c6617d9c53602207dfe4729224d4e61496dff11963982cf79f05d623a6e4004b5f50b7cefa7175241' ;
140+
141+ // when
142+ const tx = await new TransactionBuilder ( { provider : cleanProvider } )
143+ . addInputs ( utxos , contract . unlock . spend ( signature , oracleSig , message ) )
144+ . addOutput ( { to : to , amount : amount } )
145+ . addOutput ( { to : to , amount : changeAmount } )
146+ . setLocktime ( 100_000 )
147+ . send ( ) ;
148+
149+ // then
150+ const txOutputs = getTxOutputs ( tx ) ;
151+ expect ( txOutputs ) . toEqual ( expect . arrayContaining ( [ { to, amount } ] ) ) ;
152+ } ) ;
153+
154+ it ( 'should fail to accept wrong signature lengths' , async ( ) => {
155+ // given
156+ const message = oracle . createMessage ( 100000n , 30000n ) ;
157+ const oracleSig = oracle . signMessage ( message , SignatureAlgorithm . ECDSA ) ;
158+ const to = hodlVault . address ;
159+ const amount = 10000n ;
160+ const { utxos, changeAmount } = gatherUtxos ( await hodlVault . getUtxos ( ) , { amount, fee : 2000n } ) ;
161+
162+ // sig: unlocker should throw when given an improper length
163+ expect ( ( ) => hodlVault . unlock . spend ( placeholder ( 100 ) , oracleSig , message ) ) . toThrow ( "Found type 'bytes100' where type 'sig' was expected" ) ;
164+
165+ // sig: unlocker should not throw when given a proper length, but transaction should fail on invalid sig
166+ // Note that this fails with "FailedTransactionEvaluationError" because an invalid signature encoding is NOT a failed
167+ // require statement
168+ await expect ( new TransactionBuilder ( { provider } )
169+ . addInputs ( utxos , hodlVault . unlock . spend ( placeholder ( 71 ) , oracleSig , message ) )
170+ . addOutput ( { to : to , amount : amount } )
171+ . addOutput ( { to : to , amount : changeAmount } )
172+ . setLocktime ( 100_000 )
173+ . send ( ) ) . rejects . toThrow ( 'HodlVault.cash:27 Error in transaction at input 0 in contract HodlVault.cash at line 27' ) ;
174+
175+ // sig: unlocker should not throw when given an empty byte array, but transaction should fail on require statement
176+ // Note that this fails with "FailedRequireError" because a zero-length signature IS a failed require statement
177+ await expect ( new TransactionBuilder ( { provider } )
178+ . addInputs ( utxos , hodlVault . unlock . spend ( placeholder ( 0 ) , oracleSig , message ) )
179+ . addOutput ( { to : to , amount : amount } )
180+ . addOutput ( { to : to , amount : changeAmount } )
181+ . setLocktime ( 100_000 )
182+ . send ( ) ) . rejects . toThrow ( 'HodlVault.cash:27 Require statement failed at input 0 in contract HodlVault.cash at line 27' ) ;
183+
184+ // datasig: unlocker should throw when given an improper length
185+ const signatureTemplate = new SignatureTemplate ( alicePriv , HashType . SIGHASH_ALL , SignatureAlgorithm . ECDSA ) ;
186+ expect ( ( ) => hodlVault . unlock . spend ( signatureTemplate , placeholder ( 100 ) , message ) ) . toThrow ( "Found type 'bytes100' where type 'datasig' was expected" ) ;
187+
188+ // datasig: unlocker should not throw when given a proper length, but transaction should fail on invalid sig
189+ // TODO: This somehow fails with "FailedRequireError" instead of "FailedTransactionEvaluationError", check why
190+ await expect ( new TransactionBuilder ( { provider } )
191+ . addInputs ( utxos , hodlVault . unlock . spend ( signatureTemplate , placeholder ( 64 ) , message ) )
192+ . addOutput ( { to : to , amount : amount } )
193+ . addOutput ( { to : to , amount : changeAmount } )
194+ . setLocktime ( 100_000 )
195+ . send ( ) ) . rejects . toThrow ( 'HodlVault.cash:26 Require statement failed at input 0 in contract HodlVault.cash at line 26' ) ;
196+
197+ // datasig: unlocker should not throw when given an empty byte array, but transaction should fail on require statement
198+ // Note that this fails with "FailedRequireError" because a zero-length signature IS a failed require statement
199+ await expect ( new TransactionBuilder ( { provider } )
200+ . addInputs ( utxos , hodlVault . unlock . spend ( signatureTemplate , placeholder ( 0 ) , message ) )
201+ . addOutput ( { to : to , amount : amount } )
202+ . addOutput ( { to : to , amount : changeAmount } )
203+ . setLocktime ( 100_000 )
204+ . send ( ) ) . rejects . toThrow ( 'HodlVault.cash:26 Require statement failed at input 0 in contract HodlVault.cash at line 26' ) ;
205+ } ) ;
98206 } ) ;
99207} ) ;
0 commit comments