Algebra Puzzle - Nested Segwit P2SH-P2WSH
To follow along this tutorial
|
Learn more:
Let’s create a simple maths puzzle with an embedded Segwit P2SH-P2WSH transaction.
Creating and Funding the P2SH-P2WSH
const bitcoin = require('bitcoinjs-lib')
const { alice } = require('./wallets.json')
const witnessStackToScriptWitness = require('./tools/witnessStackToScriptWitness')
const network = bitcoin.networks.regtest
const witnessScript = bitcoin.script.compile([
bitcoin.opcodes.OP_ADD,
bitcoin.opcodes.OP_5,
bitcoin.opcodes.OP_EQUAL])
console.log('Witness script:')
console.log(witnessScript.toString('hex'))
decodescript 935587
const p2wsh = bitcoin.payments.p2wsh({redeem: {output: witnessScript, network}, network})
const p2sh = bitcoin.payments.p2sh({redeem: p2wsh, network: network})
console.log('P2SH Address:')
console.log(p2sh.address)
sendtoaddress 2MwnRrQxKhCdr8e3vbL7ymhtzQFYPTx9xww 1
This 1 btc is the reward for whoever as the solution to the locking script.
We can note that anyone can create this script and generate the corresponding address, it will always result in the same address. |
generatetoaddress 1 bcrt1qnqud2pjfpkqrnfzxy4kp5g98r8v886wgvs9e7r
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.
Alice_1 wants to send the funds to her P2WPKH address.
const psbt = new bitcoin.Psbt({network})
.addInput({
hash: 'TX_ID',
index: TX_VOUT,
witnessUtxo: {
script: Buffer.from('a914' +
bitcoin.crypto.hash160(p2wsh.output).toString('hex') + (1)
'87', 'hex'),
value: 1e8,
},
witnessScript: Buffer.from(witnessScript, 'hex')
})
.addOutput({
address: alice[1].p2wpkh,
value: 999e5,
}) (2)
1 | Hash160 of the current transaction asm scriptSig, inside a standard P2SH template |
2 | Output, leaving 100 000 satoshis as mining fees |
Finalizing the PSBT
We can now finalize the transaction.
The scriptSig
field will receive the version byte 0 and the witness program (sha256 hash of the witness script).
The input will also contain a witness stack, composed of the solution to our math problem, and the math problem itself (witness script).
We provide 02
and 03
as an answer satisfying the witness script.
const getFinalScripts = (inputIndex, input, script) => {
// Step 1: Check to make sure the meaningful locking script matches what you expect.
const decompiled = bitcoin.script.decompile(script)
if (!decompiled || decompiled[0] !== bitcoin.opcodes.OP_ADD) {
throw new Error(`Can not finalize input #${inputIndex}`)
}
// Step 2: Create final scripts
const payment = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wsh({
redeem: {
output: script,
input: bitcoin.script.compile([bitcoin.opcodes.OP_2, bitcoin.opcodes.OP_3]), (1)
}
})
})
return {
finalScriptSig: payment.input,
finalScriptWitness:
payment.witness && payment.witness.length > 0
? witnessStackToScriptWitness(payment.witness)
: undefined
}
}
psbt.finalizeInput(0, getFinalScripts)
1 | The condition that unlocks the witness script |
We don’t need to sign this transaction since the witness script doesn’t ask for a signature.
console.log('Transaction hexadecimal:')
console.log(psbt.extractTransaction().toHex())
decoderawtransaction TX_HEX
Broadcasting the transaction
sendrawtransaction TX_HEX
getrawtransaction TX_ID true
Observations
In the vin
(input) section, we note that the scriptSig contains a 0 version byte and a witness program, which is the SHA256 32-bytes hash of the witness script.
The only stack item in scriptSig <0 <32-byte-hash>>
(Serialized version byte + witness program) is hashed with HASH160,
compared against the 20-byte-hash in the locking script of the P2SH UTXO we are spending.
bitcoin.crypto.hash160(p2wsh.output).toString('hex')
// '31c74d4132ecfdb577695cd23be18346f048cb24'
The scriptSig is then sent to the execution stack again as two separate elements. Since the version byte is 0 and the witness program is 32 bytes it is interpreted as a P2WSH program. This triggers the execution of the witness stack.