Script with CHECKSEQUENCEVERIFY - Legacy P2SH
To follow along this tutorial
|
Let’s create a legacy P2SH transaction with a script that contains the OP_CHECKSEQUENCEVERIFY
relative timelock opcode.
The script is almost the same as Script with CHECKLOCKTIMEVERIFY - Legacy P2SH
but with a relative timelock of 5 blocks.
Learn more about OP_CHECKSEQUENCEVERIFY:
Either alice_1 can spend the P2SH UTXO but only when 5 blocks have been mined after the funding transaction is first confirmed, or bob_1 and alice_1 can redeem the funds at any time.
function csvCheckSigOutput(aQ, bQ, timelock) {
return bitcoin.script.compile([
bitcoin.opcodes.OP_IF,
bitcoin.script.number.encode(timelock),
bitcoin.opcodes.OP_CHECKSEQUENCEVERIFY,
bitcoin.opcodes.OP_DROP,
bitcoin.opcodes.OP_ELSE,
bQ.publicKey,
bitcoin.opcodes.OP_CHECKSIGVERIFY,
bitcoin.opcodes.OP_ENDIF,
aQ.publicKey,
bitcoin.opcodes.OP_CHECKSIG,
])
}
Creating and Funding the P2SH
const bitcoin = require('bitcoinjs-lib')
const { alice, bob } = require('./wallets.json')
const network = bitcoin.networks.regtest
const hashType = bitcoin.Transaction.SIGHASH_ALL
const bip68 = require('bip68')
const keyPairAlice1 = bitcoin.ECPair.fromWIF(alice[1].wif, network)
const keyPairBob1 = bitcoin.ECPair.fromWIF(bob[1].wif, network)
In both scenarios alice_1 P2WPKH address will get back the funds.
const p2wpkhAlice1 = bitcoin.payments.p2wpkh({pubkey: keyPairAlice1.publicKey, network})
console.log('P2WPKH address')
console.log(p2wpkhAlice1.address)
const timelock = bip68.encode({blocks: 5}) (1)
1 | We encode the sequence value according to BIP68 specification. |
const redeemScript = csvCheckSigOutput(keyPairAlice1, keyPairBob1, timelock)
console.log('Redeem script:')
console.log(redeemScript.toString('hex'))
const p2sh = bitcoin.payments.p2sh({redeem: {output: redeemScript, network}, network})
console.log('P2SH address:')
console.log(p2sh.address)
sendtoaddress 2Mw8mn5xQWk8Pz2KNXLnjSvS6TemKVELLyy 1
Note that our redeem script doesn’t contain any variable data so the P2WSH will always be the same. |
gettransaction TX_ID
Find the output index (or vout) under | .
Preparing the spending transaction
Now let’s prepare the spending transaction by setting input and output, as well as the nSequence value for the first scenario.
const txb = new bitcoin.TransactionBuilder(network)
// txb.addInput(prevTx, vout, sequence, prevTxScript)
txb.addInput('TX_ID', TX_VOUT, timelock) (1)
1 | Only in case we want to run the first scenario we set the sequence field as the timelock value. |
txb.addOutput(p2wpkhAlice1.address, 999e5)
const tx = txb.buildIncomplete()
Creating the unlocking script
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
There are two ways the redeem the funds, alice_1 after the timelock expiry or alice_1 and bob_1 at any time. We control which branch of the script we want to run by ending our unlocking script with a boolean value.
const inputScriptFirstBranch = bitcoin.payments.p2sh({
redeem: {
input: bitcoin.script.compile([
bitcoin.script.signature.encode(keyPairAlice1.sign(signatureHash), hashType),
bitcoin.opcodes.OP_TRUE,
]),
output: redeemScript
},
}).input
const inputScriptSecondBranch = bitcoin.payments.p2sh({
redeem: {
input: bitcoin.script.compile([
bitcoin.script.signature.encode(keyPairAlice1.sign(signatureHash), hashType),
bitcoin.script.signature.encode(keyPairBob1.sign(signatureHash), hashType),
bitcoin.opcodes.OP_FALSE
]),
output: redeemScript
}
}).input
tx.setInputScript(0, inputScriptFirstBranch)
//tx.setInputScript(0, inputScriptSecondBranch)
No build
step here as we have already called buildIncomplete
.
console.log('Transaction hexadecimal:')
console.log(tx.toHex())
decoderawtransaction TX_HEX
Broadcasting the transaction
If we run the first scenario we need 5 blocks to be mined so that the timelock will expire.
generatetoaddress 5 bcrt1qnqud2pjfpkqrnfzxy4kp5g98r8v886wgvs9e7r
sendrawtransaction TX_HEX
getrawtransaction TX_ID true
Observations
On the first scenario, we note that the input sequence field is 5 and that our scriptSig contains:
-
Alice_1 signature
-
1, which is equivalent to OP_TRUE
-
the redeem script, that we can decode with
decodescript
On the second scenario, we note that our scriptSig contains:
-
Alice_1 signature
-
Bob_1 signature
-
0, which is equivalent to OP_FALSE
-
the redeem script, that we can decode with
decodescript