Algebra Puzzle - Legacy P2SH

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

Let’s create a simple math puzzle with a legacy P2SH transaction.

Creating and Funding the P2SH

Import libraries, test wallets and set the network
const bitcoin = require('bitcoinjs-lib')
const { alice } = require('./wallets.json')
const network = bitcoin.networks.regtest
Create the script and generate its address.
const redeemScript = bitcoin.script.compile([
  bitcoin.opcodes.OP_ADD,
  bitcoin.opcodes.OP_5,
  bitcoin.opcodes.OP_EQUAL])

console.log('Redeem script:')
console.log(redeemScript.toString('hex'))
You can decode the script in Bitcoin Core CLI.
decodescript 935587

The p2sh method will generate an object that contains the P2SH address.

const p2sh = bitcoin.payments.p2sh({redeem: {output: redeemScript, network}, network})
console.log('P2SH address:')
console.log(p2sh.address)
Let’s fund this address with 1 BTC. This is the reward for whoever as the solution to the locking script.
sendtoaddress 2N7WfHK1ftrTdhWej8rnFNR7guhvhfGWwFR 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 outpoint (txid / vout).
gettransaction TX_ID
Find the output index (or vout) under details  vout.

Preparing the spending transaction

Let’s now 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, TX_OUT and TX_HEX.
const psbt = new bitcoin.Psbt({network})
  .addInput({
    hash: 'TX_ID',
    index: TX_VOUT,
    nonWitnessUtxo: Buffer.from('TX_HEX', 'hex'),
    redeemScript: Buffer.from(redeemScript, 'hex')
  }) (1)
  .addOutput({
    address: alice[1].p2wpkh,
    value: 999e5,
  }) (2)
1 Input referencing the outpoint of our P2SH funding transaction, the previous tx, and the redeem script
2 Output, leaving 100 000 satoshis as mining fees

Creating the unlocking script

Now we can finalize the transaction with the unlocking script, providing a solution to the math problem.

We provide 02 and 03 as an answer satisfying the redeem 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.p2sh({
    redeem: {
      output: script,
      input: bitcoin.script.compile([bitcoin.opcodes.OP_2, bitcoin.opcodes.OP_3]), (1)
    },
  })

  return {
    finalScriptSig: payment.input
  }
}

psbt.finalizeInput(0, getFinalScripts)
1 The condition that unlocks the redeem script

We don’t need to sign this transaction since the redeem 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

We can decrypt the unlocking script in Bitcoin Core CLI with decodescript. You will notice that it is the concatenation of the corresponding hex value of the specified opcodes, OP_2, OP_3 and the redeem script OP_ADD OP_5 OP_EQUAL.

decodescript 5253935587
Be aware that the hex script is serialized and includes a <03> before the redeem script. In order to decode the script properly we need to remove this pushbyte.