bitcoinjs / bitcoinjs-lib

A javascript Bitcoin library for node.js and browsers.
MIT License
5.62k stars 2.09k forks source link

Recreate a partially sign multisig transaction using Psbt #1653

Closed shubendrak closed 3 years ago

shubendrak commented 3 years ago

I have received a partially sign transaction in JSON format where some of inputs are signed by the sender. Below is the JSON.

const psaTx = {
  ins: [
    {
      sequence: 4294967295,
      script:
        "483045022100cbec6b67c22ab7337d64a544f9d018f377a0a1b37a9f2d1cbd303a8e10e20ca80220330694a940482fb4ac8cff0b1b3138ea969753c23a5cd7fe4d46eed0db7bcada41410488bfbde6ae1469bcadeb371ce1d1122e331239a189f7e83f8b2a2ca7b4828fa1ea959530c9f403b3a7780f25ee3baa9b8c6b9582486aee726600509208d6f5c1",
      outpoint: {
        hash:
          "ef6c65e188ac83ca6ccf48ab2f1115e679478e79596ea41964811353411b9d85",
        index: 0,
      },
    },
    {
      sequence: 4294967295,
      script: "",
      outpoint: {
        hash:
          "b1f59c85c571c04831fb2cdab8612c976946f67eb165cd4621b312b3087716b2",
        index: 0,
      },
    },
  ],
  locktime: 0,
  version: 1,
  outs: [
    {
      script:
        "006a0f416c6c65676f72792f416c6c5061794c758400019f18…4786f436f6d6d6974697369676e61747572651a000d5fffff",
      value: 0,
    },
    {
      script: "76a9142a67fe8a4a0ed7b02e9cb856faedfad6185ab02e88ac",
      value: 50000,
    },
    {
      script: "76a9148ae10ad3e7b8d629cd985181c7af0a00704b34ec88ac",
      value: 98996000,
    },
    {
      script: "76a91492dfea13a4576a072067342eb8898059d4a5b1b388ac",
      value: 1000000,
    },
  ],
};

I want to recreate this partially sign transaction using Psbt, so that i can sign the 2nd input. Here is what i have tried.

const psbt = new Psbt({
  network: network.BITCOIN_SV_REGTEST,
  forkCoin: "bch",
});
psbt.setVersion(1);
psaTx.ins.forEach((input) => {
  psbt.addInput({
    hash: Buffer.from(input.outpoint.hash, "hex"),
    index: input.outpoint.index,
    sequence: input.sequence,
    sighashType: Transaction.SIGHASH_BITCOINCASHBIP143,
    redeemScript: Buffer.from(input.script, "hex"),   /* Unsure how to use the script i received */
  });
});
psaTx.outs.forEach((output) => {
  psbt.addOutput({
    script: Buffer.from(output.script),
    value: output.value,
  });
});
psbt.validateSignaturesOfAllInputs();
psbt.finalizeAllInputs();

It fails with the error No signatures to validate I believe i am doing something wrong while adding input. Any thoughts on how to successfully recreate a transaction and sign the 2nd input would be of great help.

junderw commented 3 years ago

For the inputs you need to parse out the info and place it in a partialSig.

Since SV doesn't have P2WSH or P2SH anymore, redeemScript and witnessScript are useless.

You can also use witnessUtxo instead of nonWitnessUtxo, which is less info needed... but that depends on whether your fork supports witnessUtxo for the BCH forkID...

But if you use nonWitnessUtxo you need the full parent transaction for each input.

Also, if the hash is the txid you search on a block explorer then just pass it as a hex string... if it is the little endian representation of the hash (the order in the raw transaction) then you should convert to Buffer. PSBT will read hex string as "block explorer type txid" and read Buffer as "raw transaction hash" (which the byte order is flipped)

const p2pkh = bitcoinjs.payments.p2pkh({
  input: Buffer.from(input.script, 'hex')),
  network: network.BITCOIN_SV_REGTEST,
});
psbt.addInput({
  hash: input.outpoint.hash,
  index: input.outpoint.index,
  sequence: input.sequence,
  sighashType: Transaction.SIGHASH_BITCOINCASHBIP143,
  partialSig: [
    {
      pubkey: p2pkh.pubkey,
      signature: p2pkh.signature,
    }
  ],
  nonWitnessUtxo: Buffer.from(THE_FULL_RAW_TX_OF_input_outpoint_hash, 'hex'),
});
shubendrak commented 3 years ago

Thank you for your reply. I updated my script based on your input. Note: The signature of partially signed Tx is SIGHASH_ALL.

psaTx.ins.forEach((input) => {
  if (input.script) {
    const p2pkh = payments.p2pkh({
      input: Buffer.from(input.script, "hex"),
      network: network.BITCOIN_SV_REGTEST,
    });
    psbt.addInput({
      hash: input.outpoint.hash,
      index: input.outpoint.index,
      sequence: input.sequence,
      sighashType: Transaction.SIGHASH_BITCOINCASHBIP143, /* Unsure, if this is correct */
      partialSig: [
        {
          pubkey: p2pkh.pubkey,
          signature: p2pkh.signature,
        },
      ],
      nonWitnessUtxo: Buffer.from(
        "01000000036c90fe8bd53c8ddee71e793ebb79754e972eb38390de6546bc1552a878b8179b000000006a473044022072c997af39d5d1797bc38a807491e2b6a7bbd1ff0c0756c825003f63ca46f16b022008b034071884fbf699a807bb33b2090d5d0676ecff4f80ae55f9471abaa2ff0641210328f6f52289e063519887a993cc61ca49c81155938a82215894e7d9f2971455a1ffffffff02fb882e467e09fde6bcc588590bdd4ef4081fca861010d36fd6a1090e2e5c5c000000006b483045022100c5bf0c068678358f9146ab46e4b56808ba5ab0138fdf4bc3a317009035c8fa97022007cca04f8f72711587674c055744d1cb154f86b4af1737985482ef7dffc0f0aa41210328f6f52289e063519887a993cc61ca49c81155938a82215894e7d9f2971455a1ffffffff0bfa82f4297e481f53de14861ea06fecb69159e1097ce1f1af9592ea74b76708000000006a47304402203f1f6e1d5e4ff82f148b52af98a60c1b5931eeedf65ff134121e6fc35fdbae82022033b74ee4568d700300182494e7bc1ff480794feb1cd7b26d863bfd428fb3287841210328f6f52289e063519887a993cc61ca49c81155938a82215894e7d9f2971455a1ffffffff0200e40b54020000001976a91431ef5c58d2d8deaa8e1a535342bd3de37c6301e188accee7052a010000001976a914901b5cbf24dd3ea1cc5609575e81a8f516363b2f88ac00000000",
        "hex"
      ),
    });
  } else {
    psbt.addInput({
      hash: input.outpoint.hash,
      index: input.outpoint.index,
      sequence: input.sequence,
      sighashType: Transaction.SIGHASH_BITCOINCASHBIP143, /* Unsure, if this is correct */
      nonWitnessUtxo: Buffer.from(
        "0100000001dabcb52ed0b892822e4588a0127fbe258a79e94723a5613258f9d5b6cdd8b1eb010000006b48304502210086190fc528579c56d0018a0e0b42be4268c0d98cbecc8408d71724fea301d9280220770349a6d25782f334dcf52858b1652f54a95e3a26294f7ee155d419efa7395d4121022164c95be0a49eb0e3d70479f1791efbc7193a90262a0054f1bfd259b9a9d5a2ffffffff0100e1f505000000001976a914baaed1a187644557393eddd7aa48634db581b6a688ac00000000",
        "hex"
      ),
    });
  }
});

Now i am getting error Can not modify transaction, signatures exist. Could you tell me what is wrong now. Also, I am working with BitcoinSV using this fork https://github.com/junderw/bitcoinjs-lib. As i understand BitcoinSV doesn't support segwit. Please correct me if i am wrong. I believe i can not use witnessUtxo.

junderw commented 3 years ago

Sorry, I forgot. addInput and addOutput makes checks to ensure you don't invalidate signatures.

You will need to do all the addInput and addOutput first. Then loop over each input and use updateInput to add the partialSig AFTER all the inputs and outputs are added without signatures.


witnessUtxo was created in BIP174 for BTC (which has segwit) which is why it is named as such, BUT the actual meaning of the witnessUtxo and nonWitnessUtxo and why they differ is "whether the signature commits to the value of the output or not"

In BTC, only segwit commits to the input's output value. In BSV etc. they commit to the value even with non-segwit p2pkh. (iirc, you'll have to figure that out with trial and error I guess).

Looking at the code in branch cash520, it seems as though BSV and BCH etc. will work with witnessUtxo.

witnessUtxo: {
  script: p2pkh.output,
  value: VALUE_OF_OUTPUT_BEING_SPENT_IN_SATOSHIS,
}