2018-07-23 09:45:01 +02:00
const { describe , it , before } = require ( 'mocha' )
2018-06-25 08:24:37 +02:00
const assert = require ( 'assert' )
const bitcoin = require ( '../../' )
const regtestUtils = require ( './_regtest' )
const regtest = regtestUtils . network
const bip68 = require ( 'bip68' )
2017-12-01 03:13:55 +01:00
2018-06-25 08:24:37 +02:00
const alice = bitcoin . ECPair . fromWIF ( 'cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe' , regtest )
const bob = bitcoin . ECPair . fromWIF ( 'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x' , regtest )
2018-08-22 05:23:56 +02:00
const charles = bitcoin . ECPair . fromWIF ( 'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMSb4Ubnf' , regtest )
const dave = bitcoin . ECPair . fromWIF ( 'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMwS4pqnx' , regtest )
2017-12-01 03:13:55 +01:00
2019-04-09 08:09:50 +02:00
describe ( 'bitcoinjs-lib (transactions w/ CSV)' , ( ) => {
2018-05-30 02:23:55 +02:00
// force update MTP
2019-04-09 08:09:50 +02:00
before ( async ( ) => {
2019-04-07 15:27:16 +02:00
await regtestUtils . mine ( 11 )
2018-05-30 02:23:55 +02:00
} )
2018-06-25 08:37:45 +02:00
const hashType = bitcoin . Transaction . SIGHASH _ALL
2017-12-01 03:13:55 +01:00
2019-05-21 09:27:42 +02:00
// IF MTP (from when confirmed) > seconds, _alice can redeem
2019-05-21 09:26:00 +02:00
function csvCheckSigOutput ( _alice , _bob , sequence ) {
2018-09-03 09:54:56 +02:00
return bitcoin . script . fromASM ( `
OP _IF
$ { bitcoin . script . number . encode ( sequence ) . toString ( 'hex' ) }
OP _CHECKSEQUENCEVERIFY
OP _DROP
OP _ELSE
2019-05-21 09:26:00 +02:00
$ { _bob . publicKey . toString ( 'hex' ) }
2018-09-03 09:54:56 +02:00
OP _CHECKSIGVERIFY
OP _ENDIF
2019-05-21 09:26:00 +02:00
$ { _alice . publicKey . toString ( 'hex' ) }
2018-09-03 09:54:56 +02:00
OP _CHECKSIG
` .trim().replace(/ \s +/g, ' '))
2017-12-01 03:13:55 +01:00
}
2019-05-21 09:27:42 +02:00
// 2 of 3 multisig of _bob, _charles, _dave,
// but after sequence1 time, _alice can allow the multisig to become 1 of 3.
// but after sequence2 time, _alice can sign for the output all by themself.
2018-09-03 08:53:09 +02:00
// Ref: https://github.com/bitcoinbook/bitcoinbook/blob/f8b883dcd4e3d1b9adf40fed59b7e898fbd9241f/ch07.asciidoc#complex-script-example
// Note: bitcoinjs-lib will not offer specific support for problems with
// advanced script usages such as below. Use at your own risk.
2019-05-21 09:26:00 +02:00
function complexCsvOutput ( _alice , _bob , _charles , _dave , sequence1 , sequence2 ) {
2018-09-03 09:54:56 +02:00
return bitcoin . script . fromASM ( `
OP _IF
OP _IF
OP _2
OP _ELSE
$ { bitcoin . script . number . encode ( sequence1 ) . toString ( 'hex' ) }
OP _CHECKSEQUENCEVERIFY
OP _DROP
2019-05-21 09:26:00 +02:00
$ { _alice . publicKey . toString ( 'hex' ) }
2018-09-03 09:54:56 +02:00
OP _CHECKSIGVERIFY
OP _1
OP _ENDIF
2019-05-21 09:26:00 +02:00
$ { _bob . publicKey . toString ( 'hex' ) }
$ { _charles . publicKey . toString ( 'hex' ) }
$ { _dave . publicKey . toString ( 'hex' ) }
2018-09-03 09:54:56 +02:00
OP _3
OP _CHECKMULTISIG
OP _ELSE
$ { bitcoin . script . number . encode ( sequence2 ) . toString ( 'hex' ) }
OP _CHECKSEQUENCEVERIFY
OP _DROP
2019-05-21 09:26:00 +02:00
$ { _alice . publicKey . toString ( 'hex' ) }
2018-09-03 09:54:56 +02:00
OP _CHECKSIG
OP _ENDIF
` .trim().replace(/ \s +/g, ' '))
2018-08-22 05:23:56 +02:00
}
2017-12-01 03:13:55 +01:00
// expiry will pass, {Alice's signature} OP_TRUE
2019-05-21 09:11:45 +02:00
it ( 'can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future) (simple CHECKSEQUENCEVERIFY)' , async ( ) => {
2019-04-07 15:27:16 +02:00
// 5 blocks from now
const sequence = bip68 . encode ( { blocks : 5 } )
const p2sh = bitcoin . payments . p2sh ( {
redeem : {
output : csvCheckSigOutput ( alice , bob , sequence )
} ,
network : regtest
} )
// fund the P2SH(CSV) address
const unspent = await regtestUtils . faucet ( p2sh . address , 1e5 )
const txb = new bitcoin . TransactionBuilder ( regtest )
txb . addInput ( unspent . txId , unspent . vout , sequence )
txb . addOutput ( regtestUtils . RANDOM _ADDRESS , 7e4 )
// {Alice's signature} OP_TRUE
const tx = txb . buildIncomplete ( )
const signatureHash = tx . hashForSignature ( 0 , p2sh . redeem . output , hashType )
const redeemScriptSig = bitcoin . payments . p2sh ( {
network : regtest ,
redeem : {
network : regtest ,
output : p2sh . redeem . output ,
input : bitcoin . script . compile ( [
bitcoin . script . signature . encode ( alice . sign ( signatureHash ) , hashType ) ,
bitcoin . opcodes . OP _TRUE
] )
}
} ) . input
tx . setInputScript ( 0 , redeemScriptSig )
// TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently
// ...
// into the future!
await regtestUtils . mine ( 10 )
await regtestUtils . broadcast ( tx . toHex ( ) )
await regtestUtils . verify ( {
txId : tx . getId ( ) ,
address : regtestUtils . RANDOM _ADDRESS ,
vout : 0 ,
value : 7e4
2017-12-01 03:13:55 +01:00
} )
} )
// expiry in the future, {Alice's signature} OP_TRUE
2019-05-21 09:11:45 +02:00
it ( 'can create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry (simple CHECKSEQUENCEVERIFY)' , async ( ) => {
2017-12-01 03:13:55 +01:00
// two hours after confirmation
2018-06-25 08:37:45 +02:00
const sequence = bip68 . encode ( { seconds : 7168 } )
2018-07-03 14:54:24 +02:00
const p2sh = bitcoin . payments . p2sh ( {
network : regtest ,
redeem : {
output : csvCheckSigOutput ( alice , bob , sequence )
}
} )
2017-12-01 03:13:55 +01:00
// fund the P2SH(CSV) address
2019-04-07 15:27:16 +02:00
const unspent = await regtestUtils . faucet ( p2sh . address , 2e4 )
2017-12-01 03:13:55 +01:00
2019-04-07 15:27:16 +02:00
const txb = new bitcoin . TransactionBuilder ( regtest )
txb . addInput ( unspent . txId , unspent . vout , sequence )
txb . addOutput ( regtestUtils . RANDOM _ADDRESS , 1e4 )
2017-12-01 03:13:55 +01:00
2019-04-07 15:27:16 +02:00
// {Alice's signature} OP_TRUE
const tx = txb . buildIncomplete ( )
const signatureHash = tx . hashForSignature ( 0 , p2sh . redeem . output , hashType )
const redeemScriptSig = bitcoin . payments . p2sh ( {
network : regtest ,
redeem : {
2018-07-03 14:54:24 +02:00
network : regtest ,
2019-04-07 15:27:16 +02:00
output : p2sh . redeem . output ,
input : bitcoin . script . compile ( [
bitcoin . script . signature . encode ( alice . sign ( signatureHash ) , hashType ) ,
bitcoin . script . signature . encode ( bob . sign ( signatureHash ) , hashType ) ,
bitcoin . opcodes . OP _TRUE
] )
}
} ) . input
tx . setInputScript ( 0 , redeemScriptSig )
await regtestUtils . broadcast ( tx . toHex ( ) ) . catch ( err => {
2019-04-09 08:09:50 +02:00
assert . throws ( ( ) => {
2019-04-07 15:27:16 +02:00
if ( err ) throw err
} , /Error: non-BIP68-final \(code 64\)/ )
2017-12-01 03:13:55 +01:00
} )
} )
2018-08-22 05:23:56 +02:00
// Check first combination of complex CSV, 2 of 3
2019-05-21 09:11:45 +02:00
it ( 'can create (and broadcast via 3PBP) a Transaction where Bob and Charles can send (complex CHECKSEQUENCEVERIFY)' , async ( ) => {
const height = await regtestUtils . height ( )
// 2 blocks from now
const sequence1 = bip68 . encode ( { blocks : 2 } )
// 5 blocks from now
const sequence2 = bip68 . encode ( { blocks : 5 } )
const p2sh = bitcoin . payments . p2sh ( {
redeem : {
output : complexCsvOutput ( alice , bob , charles , dave , sequence1 , sequence2 )
} ,
network : regtest
} )
// fund the P2SH(CCSV) address
const unspent = await regtestUtils . faucet ( p2sh . address , 1e5 )
const txb = new bitcoin . TransactionBuilder ( regtest )
txb . addInput ( unspent . txId , unspent . vout )
txb . addOutput ( regtestUtils . RANDOM _ADDRESS , 7e4 )
// OP_0 {Bob sig} {Charles sig} OP_TRUE OP_TRUE
const tx = txb . buildIncomplete ( )
const signatureHash = tx . hashForSignature ( 0 , p2sh . redeem . output , hashType )
const redeemScriptSig = bitcoin . payments . p2sh ( {
network : regtest ,
redeem : {
network : regtest ,
output : p2sh . redeem . output ,
input : bitcoin . script . compile ( [
bitcoin . opcodes . OP _0 ,
bitcoin . script . signature . encode ( bob . sign ( signatureHash ) , hashType ) ,
bitcoin . script . signature . encode ( charles . sign ( signatureHash ) , hashType ) ,
bitcoin . opcodes . OP _TRUE ,
bitcoin . opcodes . OP _TRUE
] )
}
} ) . input
tx . setInputScript ( 0 , redeemScriptSig )
await regtestUtils . broadcast ( tx . toHex ( ) )
await regtestUtils . verify ( {
txId : tx . getId ( ) ,
address : regtestUtils . RANDOM _ADDRESS ,
vout : 0 ,
value : 7e4
2018-08-22 05:23:56 +02:00
} )
} )
2018-09-03 08:53:09 +02:00
// Check first combination of complex CSV, mediator + 1 of 3 after 2 blocks
2019-05-21 09:11:45 +02:00
it ( 'can create (and broadcast via 3PBP) a Transaction where Alice (mediator) and Bob can send after 2 blocks (complex CHECKSEQUENCEVERIFY)' , async ( ) => {
const height = await regtestUtils . height ( )
// 2 blocks from now
const sequence1 = bip68 . encode ( { blocks : 2 } )
// 5 blocks from now
const sequence2 = bip68 . encode ( { blocks : 5 } )
const p2sh = bitcoin . payments . p2sh ( {
redeem : {
output : complexCsvOutput ( alice , bob , charles , dave , sequence1 , sequence2 )
} ,
network : regtest
} )
// fund the P2SH(CCSV) address
const unspent = await regtestUtils . faucet ( p2sh . address , 1e5 )
const txb = new bitcoin . TransactionBuilder ( regtest )
txb . addInput ( unspent . txId , unspent . vout , sequence1 ) // Set sequence1 for input
txb . addOutput ( regtestUtils . RANDOM _ADDRESS , 7e4 )
// OP_0 {Bob sig} {Alice mediator sig} OP_FALSE OP_TRUE
const tx = txb . buildIncomplete ( )
const signatureHash = tx . hashForSignature ( 0 , p2sh . redeem . output , hashType )
const redeemScriptSig = bitcoin . payments . p2sh ( {
network : regtest ,
redeem : {
network : regtest ,
output : p2sh . redeem . output ,
input : bitcoin . script . compile ( [
bitcoin . opcodes . OP _0 ,
bitcoin . script . signature . encode ( bob . sign ( signatureHash ) , hashType ) ,
bitcoin . script . signature . encode ( alice . sign ( signatureHash ) , hashType ) ,
bitcoin . opcodes . OP _0 ,
bitcoin . opcodes . OP _TRUE
] )
}
} ) . input
tx . setInputScript ( 0 , redeemScriptSig )
// Wait 2 blocks
await regtestUtils . mine ( 2 )
await regtestUtils . broadcast ( tx . toHex ( ) )
await regtestUtils . verify ( {
txId : tx . getId ( ) ,
address : regtestUtils . RANDOM _ADDRESS ,
vout : 0 ,
value : 7e4
2018-08-22 05:23:56 +02:00
} )
} )
2018-09-03 08:53:09 +02:00
// Check first combination of complex CSV, mediator after 5 blocks
2019-05-21 09:11:45 +02:00
it ( 'can create (and broadcast via 3PBP) a Transaction where Alice (mediator) can send after 5 blocks (complex CHECKSEQUENCEVERIFY)' , async ( ) => {
const height = await regtestUtils . height ( )
// 2 blocks from now
const sequence1 = bip68 . encode ( { blocks : 2 } )
// 5 blocks from now
const sequence2 = bip68 . encode ( { blocks : 5 } )
const p2sh = bitcoin . payments . p2sh ( {
redeem : {
output : complexCsvOutput ( alice , bob , charles , dave , sequence1 , sequence2 )
} ,
network : regtest
} )
// fund the P2SH(CCSV) address
const unspent = await regtestUtils . faucet ( p2sh . address , 1e5 )
const txb = new bitcoin . TransactionBuilder ( regtest )
txb . addInput ( unspent . txId , unspent . vout , sequence2 ) // Set sequence2 for input
txb . addOutput ( regtestUtils . RANDOM _ADDRESS , 7e4 )
// {Alice mediator sig} OP_FALSE
const tx = txb . buildIncomplete ( )
const signatureHash = tx . hashForSignature ( 0 , p2sh . redeem . output , hashType )
const redeemScriptSig = bitcoin . payments . p2sh ( {
network : regtest ,
redeem : {
network : regtest ,
output : p2sh . redeem . output ,
input : bitcoin . script . compile ( [
bitcoin . script . signature . encode ( alice . sign ( signatureHash ) , hashType ) ,
bitcoin . opcodes . OP _0
] )
}
} ) . input
tx . setInputScript ( 0 , redeemScriptSig )
// Wait 5 blocks
await regtestUtils . mine ( 5 )
await regtestUtils . broadcast ( tx . toHex ( ) )
await regtestUtils . verify ( {
txId : tx . getId ( ) ,
address : regtestUtils . RANDOM _ADDRESS ,
vout : 0 ,
value : 7e4
2018-08-22 05:23:56 +02:00
} )
} )
2017-12-01 03:13:55 +01:00
} )