2019-09-07 06:42:03 +02:00
import * as assert from 'assert' ;
import { before , describe , it } from 'mocha' ;
import * as bitcoin from '../..' ;
import { regtestUtils } from './_regtest' ;
const regtest = regtestUtils . network ;
const bip68 = require ( 'bip68' ) ;
const alice = bitcoin . ECPair . fromWIF (
'cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe' ,
regtest ,
) ;
const bob = bitcoin . ECPair . fromWIF (
'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x' ,
regtest ,
) ;
const charles = bitcoin . ECPair . fromWIF (
'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMSb4Ubnf' ,
regtest ,
) ;
const dave = bitcoin . ECPair . fromWIF (
'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMwS4pqnx' ,
regtest ,
) ;
console . warn = ( ) = > { } ; // Silence the Deprecation Warning
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-09-07 06:42:03 +02:00
await regtestUtils . mine ( 11 ) ;
} ) ;
2018-05-30 02:23:55 +02:00
2019-09-07 06:42:03 +02:00
const hashType = bitcoin . Transaction . SIGHASH_ALL ;
2017-12-01 03:13:55 +01:00
2019-09-07 06:42:03 +02:00
type keyPair = { publicKey : Buffer } ;
2019-05-21 09:27:42 +02:00
// IF MTP (from when confirmed) > seconds, _alice can redeem
2019-09-07 06:42:03 +02:00
function csvCheckSigOutput ( _alice : keyPair , _bob : keyPair , sequence : number ) {
return bitcoin . script . fromASM (
`
2018-09-03 09:54:56 +02:00
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
2019-09-07 06:42:03 +02:00
`
. 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-09-07 06:42:03 +02:00
function complexCsvOutput (
_alice : keyPair ,
_bob : keyPair ,
_charles : keyPair ,
_dave : keyPair ,
sequence1 : number ,
sequence2 : number ,
) {
return bitcoin . script . fromASM (
`
2018-09-03 09:54:56 +02:00
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
2019-09-07 06:42:03 +02:00
`
. 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
2019-09-07 06:42:03 +02:00
const sequence = bip68 . encode ( { blocks : 5 } ) ;
2019-04-07 15:27:16 +02:00
const p2sh = bitcoin . payments . p2sh ( {
redeem : {
2019-09-07 06:42:03 +02:00
output : csvCheckSigOutput ( alice , bob , sequence ) ,
2019-04-07 15:27:16 +02:00
} ,
2019-09-07 06:42:03 +02:00
network : regtest ,
} ) ;
2019-04-07 15:27:16 +02:00
// fund the P2SH(CSV) address
2019-09-07 06:42:03 +02:00
const unspent = await regtestUtils . faucet ( p2sh . address ! , 1 e5 ) ;
2019-04-07 15:27:16 +02:00
2019-09-07 06:42:03 +02:00
const txb = new bitcoin . TransactionBuilder ( regtest ) ;
txb . addInput ( unspent . txId , unspent . vout , sequence ) ;
txb . addOutput ( regtestUtils . RANDOM_ADDRESS , 7 e4 ) ;
2019-04-07 15:27:16 +02:00
// {Alice's signature} OP_TRUE
2019-09-07 06:42:03 +02:00
const tx = txb . buildIncomplete ( ) ;
const signatureHash = tx . hashForSignature (
0 ,
p2sh . redeem ! . output ! ,
hashType ,
) ;
2019-04-07 15:27:16 +02:00
const redeemScriptSig = bitcoin . payments . p2sh ( {
network : regtest ,
redeem : {
network : regtest ,
2019-09-07 06:42:03 +02:00
output : p2sh.redeem ! . output ,
2019-04-07 15:27:16 +02:00
input : bitcoin.script.compile ( [
bitcoin . script . signature . encode ( alice . sign ( signatureHash ) , hashType ) ,
2019-09-07 06:42:03 +02:00
bitcoin . opcodes . OP_TRUE ,
] ) ,
} ,
} ) . input ;
tx . setInputScript ( 0 , redeemScriptSig ! ) ;
2019-04-07 15:27:16 +02:00
// TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently
// ...
// into the future!
2019-09-07 06:42:03 +02:00
await regtestUtils . mine ( 10 ) ;
2019-04-07 15:27:16 +02:00
2019-09-07 06:42:03 +02:00
await regtestUtils . broadcast ( tx . toHex ( ) ) ;
2019-04-07 15:27:16 +02:00
await regtestUtils . verify ( {
txId : tx.getId ( ) ,
address : regtestUtils.RANDOM_ADDRESS ,
vout : 0 ,
2019-09-07 06:42:03 +02:00
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
2019-09-07 06:42:03 +02:00
const sequence = bip68 . encode ( { seconds : 7168 } ) ;
2018-07-03 14:54:24 +02:00
const p2sh = bitcoin . payments . p2sh ( {
network : regtest ,
redeem : {
2019-09-07 06:42:03 +02:00
output : csvCheckSigOutput ( alice , bob , sequence ) ,
} ,
} ) ;
2017-12-01 03:13:55 +01:00
// fund the P2SH(CSV) address
2019-09-07 06:42:03 +02:00
const unspent = await regtestUtils . faucet ( p2sh . address ! , 2 e4 ) ;
2017-12-01 03:13:55 +01:00
2019-09-07 06:42:03 +02:00
const txb = new bitcoin . TransactionBuilder ( regtest ) ;
txb . addInput ( unspent . txId , unspent . vout , sequence ) ;
txb . addOutput ( regtestUtils . RANDOM_ADDRESS , 1 e4 ) ;
2017-12-01 03:13:55 +01:00
2019-04-07 15:27:16 +02:00
// {Alice's signature} OP_TRUE
2019-09-07 06:42:03 +02:00
const tx = txb . buildIncomplete ( ) ;
const signatureHash = tx . hashForSignature (
0 ,
p2sh . redeem ! . output ! ,
hashType ,
) ;
2019-04-07 15:27:16 +02:00
const redeemScriptSig = bitcoin . payments . p2sh ( {
network : regtest ,
redeem : {
2018-07-03 14:54:24 +02:00
network : regtest ,
2019-09-07 06:42:03 +02:00
output : p2sh.redeem ! . output ,
2019-04-07 15:27:16 +02:00
input : bitcoin.script.compile ( [
bitcoin . script . signature . encode ( alice . sign ( signatureHash ) , hashType ) ,
bitcoin . script . signature . encode ( bob . sign ( signatureHash ) , hashType ) ,
2019-09-07 06:42:03 +02:00
bitcoin . opcodes . OP_TRUE ,
] ) ,
} ,
} ) . input ;
tx . setInputScript ( 0 , redeemScriptSig ! ) ;
2019-04-07 15:27:16 +02:00
await regtestUtils . broadcast ( tx . toHex ( ) ) . catch ( err = > {
2019-04-09 08:09:50 +02:00
assert . throws ( ( ) = > {
2019-09-07 06:42:03 +02:00
if ( err ) throw err ;
} , /Error: non-BIP68-final \(code 64\)/ ) ;
} ) ;
} ) ;
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 ( ) = > {
// 2 blocks from now
2019-09-07 06:42:03 +02:00
const sequence1 = bip68 . encode ( { blocks : 2 } ) ;
2019-05-21 09:11:45 +02:00
// 5 blocks from now
2019-09-07 06:42:03 +02:00
const sequence2 = bip68 . encode ( { blocks : 5 } ) ;
2019-05-21 09:11:45 +02:00
const p2sh = bitcoin . payments . p2sh ( {
redeem : {
2019-09-07 06:42:03 +02:00
output : complexCsvOutput (
alice ,
bob ,
charles ,
dave ,
sequence1 ,
sequence2 ,
) ,
2019-05-21 09:11:45 +02:00
} ,
2019-09-07 06:42:03 +02:00
network : regtest ,
} ) ;
2019-05-21 09:11:45 +02:00
// fund the P2SH(CCSV) address
2019-09-07 06:42:03 +02:00
const unspent = await regtestUtils . faucet ( p2sh . address ! , 1 e5 ) ;
2019-05-21 09:11:45 +02:00
2019-09-07 06:42:03 +02:00
const txb = new bitcoin . TransactionBuilder ( regtest ) ;
txb . addInput ( unspent . txId , unspent . vout ) ;
txb . addOutput ( regtestUtils . RANDOM_ADDRESS , 7 e4 ) ;
2019-05-21 09:11:45 +02:00
// OP_0 {Bob sig} {Charles sig} OP_TRUE OP_TRUE
2019-09-07 06:42:03 +02:00
const tx = txb . buildIncomplete ( ) ;
const signatureHash = tx . hashForSignature (
0 ,
p2sh . redeem ! . output ! ,
hashType ,
) ;
2019-05-21 09:11:45 +02:00
const redeemScriptSig = bitcoin . payments . p2sh ( {
network : regtest ,
redeem : {
network : regtest ,
2019-09-07 06:42:03 +02:00
output : p2sh.redeem ! . output ,
2019-05-21 09:11:45 +02:00
input : bitcoin.script.compile ( [
bitcoin . opcodes . OP_0 ,
bitcoin . script . signature . encode ( bob . sign ( signatureHash ) , hashType ) ,
2019-09-07 06:42:03 +02:00
bitcoin . script . signature . encode (
charles . sign ( signatureHash ) ,
hashType ,
) ,
bitcoin . opcodes . OP_TRUE ,
2019-05-21 09:11:45 +02:00
bitcoin . opcodes . OP_TRUE ,
2019-09-07 06:42:03 +02:00
] ) ,
} ,
} ) . input ;
tx . setInputScript ( 0 , redeemScriptSig ! ) ;
2019-05-21 09:11:45 +02:00
2019-09-07 06:42:03 +02:00
await regtestUtils . broadcast ( tx . toHex ( ) ) ;
2019-05-21 09:11:45 +02:00
await regtestUtils . verify ( {
txId : tx.getId ( ) ,
address : regtestUtils.RANDOM_ADDRESS ,
vout : 0 ,
2019-09-07 06:42:03 +02:00
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 ( ) = > {
// 2 blocks from now
2019-09-07 06:42:03 +02:00
const sequence1 = bip68 . encode ( { blocks : 2 } ) ;
2019-05-21 09:11:45 +02:00
// 5 blocks from now
2019-09-07 06:42:03 +02:00
const sequence2 = bip68 . encode ( { blocks : 5 } ) ;
2019-05-21 09:11:45 +02:00
const p2sh = bitcoin . payments . p2sh ( {
redeem : {
2019-09-07 06:42:03 +02:00
output : complexCsvOutput (
alice ,
bob ,
charles ,
dave ,
sequence1 ,
sequence2 ,
) ,
2019-05-21 09:11:45 +02:00
} ,
2019-09-07 06:42:03 +02:00
network : regtest ,
} ) ;
2019-05-21 09:11:45 +02:00
// fund the P2SH(CCSV) address
2019-09-07 06:42:03 +02:00
const unspent = await regtestUtils . faucet ( p2sh . address ! , 1 e5 ) ;
2019-05-21 09:11:45 +02:00
2019-09-07 06:42:03 +02:00
const txb = new bitcoin . TransactionBuilder ( regtest ) ;
txb . addInput ( unspent . txId , unspent . vout , sequence1 ) ; // Set sequence1 for input
txb . addOutput ( regtestUtils . RANDOM_ADDRESS , 7 e4 ) ;
2019-05-21 09:11:45 +02:00
// OP_0 {Bob sig} {Alice mediator sig} OP_FALSE OP_TRUE
2019-09-07 06:42:03 +02:00
const tx = txb . buildIncomplete ( ) ;
const signatureHash = tx . hashForSignature (
0 ,
p2sh . redeem ! . output ! ,
hashType ,
) ;
2019-05-21 09:11:45 +02:00
const redeemScriptSig = bitcoin . payments . p2sh ( {
network : regtest ,
redeem : {
network : regtest ,
2019-09-07 06:42:03 +02:00
output : p2sh.redeem ! . output ,
2019-05-21 09:11:45 +02:00
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 ,
2019-09-07 06:42:03 +02:00
bitcoin . opcodes . OP_TRUE ,
] ) ,
} ,
} ) . input ;
tx . setInputScript ( 0 , redeemScriptSig ! ) ;
2019-05-21 09:11:45 +02:00
// Wait 2 blocks
2019-09-07 06:42:03 +02:00
await regtestUtils . mine ( 2 ) ;
2019-05-21 09:11:45 +02:00
2019-09-07 06:42:03 +02:00
await regtestUtils . broadcast ( tx . toHex ( ) ) ;
2019-05-21 09:11:45 +02:00
await regtestUtils . verify ( {
txId : tx.getId ( ) ,
address : regtestUtils.RANDOM_ADDRESS ,
vout : 0 ,
2019-09-07 06:42:03 +02:00
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 ( ) = > {
// 2 blocks from now
2019-09-07 06:42:03 +02:00
const sequence1 = bip68 . encode ( { blocks : 2 } ) ;
2019-05-21 09:11:45 +02:00
// 5 blocks from now
2019-09-07 06:42:03 +02:00
const sequence2 = bip68 . encode ( { blocks : 5 } ) ;
2019-05-21 09:11:45 +02:00
const p2sh = bitcoin . payments . p2sh ( {
redeem : {
2019-09-07 06:42:03 +02:00
output : complexCsvOutput (
alice ,
bob ,
charles ,
dave ,
sequence1 ,
sequence2 ,
) ,
2019-05-21 09:11:45 +02:00
} ,
2019-09-07 06:42:03 +02:00
network : regtest ,
} ) ;
2019-05-21 09:11:45 +02:00
// fund the P2SH(CCSV) address
2019-09-07 06:42:03 +02:00
const unspent = await regtestUtils . faucet ( p2sh . address ! , 1 e5 ) ;
2019-05-21 09:11:45 +02:00
2019-09-07 06:42:03 +02:00
const txb = new bitcoin . TransactionBuilder ( regtest ) ;
txb . addInput ( unspent . txId , unspent . vout , sequence2 ) ; // Set sequence2 for input
txb . addOutput ( regtestUtils . RANDOM_ADDRESS , 7 e4 ) ;
2019-05-21 09:11:45 +02:00
// {Alice mediator sig} OP_FALSE
2019-09-07 06:42:03 +02:00
const tx = txb . buildIncomplete ( ) ;
const signatureHash = tx . hashForSignature (
0 ,
p2sh . redeem ! . output ! ,
hashType ,
) ;
2019-05-21 09:11:45 +02:00
const redeemScriptSig = bitcoin . payments . p2sh ( {
network : regtest ,
redeem : {
network : regtest ,
2019-09-07 06:42:03 +02:00
output : p2sh.redeem ! . output ,
2019-05-21 09:11:45 +02:00
input : bitcoin.script.compile ( [
bitcoin . script . signature . encode ( alice . sign ( signatureHash ) , hashType ) ,
2019-09-07 06:42:03 +02:00
bitcoin . opcodes . OP_0 ,
] ) ,
} ,
} ) . input ;
tx . setInputScript ( 0 , redeemScriptSig ! ) ;
2019-05-21 09:11:45 +02:00
// Wait 5 blocks
2019-09-07 06:42:03 +02:00
await regtestUtils . mine ( 5 ) ;
2019-05-21 09:11:45 +02:00
2019-09-07 06:42:03 +02:00
await regtestUtils . broadcast ( tx . toHex ( ) ) ;
2019-05-21 09:11:45 +02:00
await regtestUtils . verify ( {
txId : tx.getId ( ) ,
address : regtestUtils.RANDOM_ADDRESS ,
vout : 0 ,
2019-09-07 06:42:03 +02:00
value : 7e4 ,
} ) ;
} ) ;
} ) ;