Algebra Puzzle - Native Segwit P2WSH

To follow along this tutorial

  • Clone the Github repository

  • cd code

  • npm install or yarn install

  • Execute the transaction code by typing node tx_filename.js

  • Alternatively you can enter the commands step-by-step by cd into ./code then type node in a terminal to open the Node.js REPL

  • Open the Bitcoin Core GUI console or use bitcoin-cli for the Bitcoin Core commands

  • Use bx aka Libbitcoin-explorer as a handy complement

To learn more about P2WSH:

Let’s create a simple math puzzle with a native Segwit P2WSH transaction.

Creating and Funding the P2WSH

Import libraries, test wallets and set the network
const bitcoin = require('bitcoinjs-lib')
const { alice } = require('./wallets.json')
const witnessStackToScriptWitness = require('./tools/witnessStackToScriptWitness')
const network = bitcoin.networks.regtest
Create the witness script and generate its address.
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.
We can decode the witness script in Bitcoin Core CLI.
decodescript 935587
Now we can generate the address of our simple smart contract.
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.
Send 1 BTC to this P2WSH address, which is the reward for whoever provides a solution to the locking script.
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.
Get the output index so that we have the full outpoint (txid and output index).
gettransaction TX_ID
Find the output index (or vout) under details  vout.

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.

Create the PSBT by filling TX_ID and TX_OUT.
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.

Finalize the PSBT.
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.

Extract the transaction and get the raw hex serialization.
console.log('Transaction hexadecimal:')
console.log(psbt.extractTransaction().toHex())
Inspect the raw transaction with Bitcoin Core CLI, check that everything is correct.
decoderawtransaction TX_HEX

Broadcasting the transaction

It’s time to broadcast the transaction via Bitcoin Core CLI.
sendrawtransaction TX_HEX
Inspect the transaction.
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.