bitcoinjs/bitcoinjs-lib

Help with spending a custom script with Pay to Witness Script Hash

dcale opened this issue · 3 comments

I have a working p2sh example and now I'm trying to replicate it with p2wsh. This is the working p2sh example (yes I'm sharing my test private keys):

import ECPairFactory from "ecpair";
import * as ecc from "tiny-secp256k1";
import * as bitcoin from "bitcoinjs-lib";
import { testnet } from "bitcoinjs-lib/src/networks";
import https from "https";

const NETWORK = testnet;
const ECPair = ECPairFactory(ecc);
bitcoin.initEccLib(ecc);

const keyPair = ECPair.fromWIF(
  "cW8RD4U8ovYDd5W6WxGEGsT7SxZfECBYZACziZzTDJEFd1YeXUkA",
  NETWORK
);

const pubkeyHex = keyPair.publicKey.toString("hex");

function createCustomWalletAddress(
  gateKeeperPublicKeyHex,
  targetRecipient
): string {
  const payloadScriptAsm = `${targetRecipient} OP_DROP ${gateKeeperPublicKeyHex} OP_CHECKSIG`;
  const payloadScript = bitcoin.script.fromASM(payloadScriptAsm);

  const { output, address, witness } = bitcoin.payments.p2sh({
    redeem: { output: payloadScript, network: NETWORK },
    network: NETWORK,
  });
  console.log("output", output);
  console.log("address", address);
  console.log("witness", witness);

  const tx = new bitcoin.Transaction();
  tx.version = 2;
  tx.addInput(
    Buffer.from(
      "73f407e46e019c7e8fc43fb77e809bb0e5d55a75849fd8986a4b785eb24017fc",
      "hex"
    ).reverse(),
    0
  );
  tx.addOutput(output!, 1500);

  const signatureHash = tx.hashForSignature(
    0,
    payloadScript,
    bitcoin.Transaction.SIGHASH_ALL
  );

  const redeemScriptSig = bitcoin.payments.p2sh({
    redeem: {
      input: bitcoin.script.compile([
        bitcoin.script.signature.encode(
          keyPair.sign(signatureHash),
          bitcoin.Transaction.SIGHASH_ALL
        ),
      ]),
      output: payloadScript,
    },
  }).input;
  console.log("redeemScriptSig", redeemScriptSig);
  tx.setInputScript(0, redeemScriptSig!);

  console.log("tx.ins", tx.ins);
  console.log("tx.outs", tx.outs);

  console.log("tx", tx.toHex());

  blockBookAPIGet(`/api/v2/sendtx/${tx.toHex()}`).then(console.log);

  return address!;
}

console.log(createCustomWalletAddress(pubkeyHex, "0011223344"));

Now I am able to successfully create a segwit address like so:

  const payloadScriptAsm = `${targetRecipient} OP_DROP ${gateKeeperPublicKeyHex} OP_CHECKSIG`;
  const payloadScript = bitcoin.script.fromASM(payloadScriptAsm);
  const txHash =  "0d68ef18fef2deacbd9efec230820b0a4bbd06f36c64fa740aef3a0614e3e60f"
  const inputNr = 1
  const { output, address, redeem } = bitcoin.payments.p2wsh({
    redeem: { output: payloadScript, network: NETWORK },
    network: NETWORK,
  });

But that's as far as I get, the bitcoin.Psbt does not seem to allow me pass custom script arguments (nor generate the signature I'm looking for). I'm looking for something like in my p2sh example were I could do:

  const redeemScriptSig = bitcoin.payments.p2sh({
    redeem: {
      input: bitcoin.script.compile([
        bitcoin.script.signature.encode(
          keyPair.sign(signatureHash),
          bitcoin.Transaction.SIGHASH_ALL
        ),
      ]),
      output: payloadScript,
    },
  }).input;
  console.log("redeemScriptSig", redeemScriptSig);

If you want to modify your existing code, replace:

  1. hashForSignature with hashForWitnessV0 (and fill in the necessary extra data for the args (notably the input amount))
  2. payments.p2sh with payments.p2wsh
  3. }).input; with }).witness;
  4. tx.setInputScript with tx.setWitness

And you should be good to go.

If you want to use Psbt, you need to write a finalizer.

If you're using TypeScript your IDE should help you with the arguments and return values required for the finalizer function that you need to pass in when finalizing.

(hashForWitnessV0 is correct for p2wsh, not hashForWitnessV1)

<3 wow thank you, it worked! Should've asked earlier :)