Algebra Puzzle - Native Segwit P2WSH
To follow along this tutorial
|
To learn more about P2WSH:
Let’s create a simple math puzzle with a native Segwit P2WSH transaction.
Creating and Funding the 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'))
// '935587'
In a P2WSH context, a redeem script is called a witness script. |
decodescript 935587
const p2wsh = bitcoin.payments.p2wsh({redeem: {output: witnessScript, network}, network})
console.log('P2WSH Address:')
console.log(p2wsh.address) (1)
1 | The p2wsh method will generate an object that contains the P2WSH address. |
sendtoaddress bcrt1qpt7c23c0wep9e8up4ywn070w3tqz3828ngy34aj8slsfxrh08ddq2d2pyu 1
We can note that anyone can create this script and generate the corresponding address, it will always result in the same address. |
gettransaction TX_ID
Find the output index (or vout) under | .
The output of our funding transaction should have a locking script composed as follows: <00 version byte>
+ <32-byte hash
witness program>
.
The SHA256 of the witness script must match the 32-byte witness program.
bitcoin.crypto.sha256(Buffer.from('935587', 'hex')).toString('hex')
// '0afd85470f76425c9f81a91d37f9ee8ac0289d479a091af64787e0930eef3b5a'
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('0020' +
bitcoin.crypto.sha256(witnessScript).toString('hex'),
'hex'),
value: 1e8,
},
witnessScript: Buffer.from(witnessScript, 'hex')
}) (1)
.addOutput({
address: alice[1].p2wpkh,
value: 999e5,
}) (2)
1 | Input referencing the outpoint, locking script, and btc value of the P2WSH UTXO we are spending, and the witness script |
2 | Output, leaving 100 000 satoshis as mining fees |
Adding the witness data
Now we can finalize the transaction with the witness data, providing a solution to the math problem plus the problem itself, effectively allowing us to spend from the P2WSH.
We provide 02
and 03
as an answer, plus 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.p2wsh({
redeem: {
output: script,
input: bitcoin.script.compile([bitcoin.opcodes.OP_2, bitcoin.opcodes.OP_3]), (1)
},
})
return {
finalScriptWitness:
payment.witness && payment.witness.length > 0
? witnessStackToScriptWitness(payment.witness)
: undefined
}
}
psbt.finalizeInput(0, getFinalScripts)
1 | The condition that unlocks the witness script |
Note that we are pushing the integer values, not the corresponding opcode values. |
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 section, we note that the scriptSig
field is empty, and that our solution data and witness script are located in the witness txinwitness
field.
The SHA256 hash of the witness script, last item in txinwitness
, is compared against the 32-byte hash located in the P2WSH UTXO we are spending.
The script is then executed with the remaining data from the witness txinwitness
field.