Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

checkScript: non-wrapped ms #114

Open
gregdhill opened this issue Oct 28, 2024 · 2 comments
Open

checkScript: non-wrapped ms #114

gregdhill opened this issue Oct 28, 2024 · 2 comments

Comments

@gregdhill
Copy link

We're hitting this error when spending from a UTXO whose input(s) come from a multisig. It looks like we can get around this by setting disableScriptCheck to true but I'm not sure why this is flagged as an error since we are not spending from a multisig. Our code is here for reference but to reproduce it should be enough to provide an input to selectUTXO with an array of possibleInputs where at least one input has a UTXO which spends from a multisig to the user's wallet.

@paulmillr
Copy link
Owner

Can't reproduce, please provide specific input that fails.

  • if we use witnessUtxo, then script of input will be user wallet script -> there is nothing to check
  • if we use nonWitnessUtxo:
    • normalizeInput decodes previous utxo as 'RawTx'
    • there is 'scriptCheck': checkScript(prevOut && prevOut.script, res.redeemScript, res.witnessScript);
      but it happens only on output (which is input for out tx) prevOut = res.nonWitnessUtxo.outputs[res.index]
  • There is additional check inside 'PSBT decoding' (https://github.com/paulmillr/scure-btc-signer/blob/1.3.2/src/psbt.ts#L302)
    but it was fixed in 1.3.2, which you use according to your package.json
    • since it was decoded from 'raw' version of tx, there is no 'script' in inputx, only 'finalScriptSig'.
    • NOTE: this fix is for different error, it would happen in your use-case if previous UTXO sends change back to multi-sig.

Here is an example of this specific case working (attempt at reproduction):

should.only('GH-114: unwrapped multisig', () => {
  const privKey1 = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
  const P1 = secp256k1.getPublicKey(privKey1, true);
  const wpkh = btc.p2wpkh(P1);
  // multisig utxo
  const compressed = hex.decode(
    '030000000000000000000000000000000000000000000000000000000000000001'
  );
  const compressed2 = hex.decode(
    '030000000000000000000000000000000000000000000000000000000000000002'
  );
  const compressed3 = hex.decode(
    '030000000000000000000000000000000000000000000000000000000000000003'
  );
  const ms = btc.p2ms(2, [compressed, compressed2, compressed3]);
  const oldBrokenTx = new btc.Transaction({
    // NOTE: here we need disableScriptCheck, because we construct raw tx
    // in mentioned use case this tx will be signed and serialized by somebody else.
    disableScriptCheck: true,
    allowLegacyWitnessUtxo: true,
  });
  oldBrokenTx.addInput({
    txid: hex.decode('c061c23190ed3370ad5206769651eaf6fac6d87d85b5db34e30a74e0c4a6da3e'),
    index: 0,
    witnessUtxo: { script: ms.script, amount: 100_000n },
  });
  oldBrokenTx.addOutputAddress(wpkh.address, 90_000n);
  // Add broken signatures
  oldBrokenTx.updateInput(
    0,
    {
      partialSig: [
        [compressed, new Uint8Array(33)],
        [compressed2, new Uint8Array(33)],
      ],
    },
    true
  );
  oldBrokenTx.finalize();
  const brokenCheck = btc.Transaction.fromRaw(hex.decode(oldBrokenTx.hex));
  // Now, lets try to spend output of that transaction
  // we spend wpkh output from this tx, but input inside this tx is broken
  const INPUT = {
    ...wpkh,
    txid: oldBrokenTx.id,
    index: 0,
    nonWitnessUtxo: oldBrokenTx.hex,
  };
  const spendTx = new btc.Transaction();
  spendTx.addInput(INPUT);
  spendTx.addOutputAddress(wpkh.address, 80_000n);
  spendTx.sign(privKey1);
  spendTx.finalize();
  deepStrictEqual(spendTx.id, '63f33dac6b7ed734c1720ed32865601d85fcbdec370df08f8562d40ec9e79c6b');
  deepStrictEqual(
    spendTx.hex,
    '02000000000101ec495ac2d31a09a8511203caec7cade7bdd2f1c9ff9acf106042e1d0e2bcfc8d0000000000ffffffff01803801000000000016001479b000887626b294a914501a4cd226b58b23598302483045022100ca2b3abb71e8b207dd9630cb9c7d5d5b2824183e9a2b79b1a4eff7cf7787f5ab0220464b4b3f688497a7fdb45d2403becd86d4a05d2c211a8a21724b98bdc7742b510121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f00000000'
  );
  const transaction = btc.selectUTXO([INPUT], [], 'default', {
    changeAddress: wpkh.address, // Refund surplus to the payment address
    feePerByte: BigInt(Math.ceil(1)), // round up to the nearest integer
    bip69: true, // Sort inputs and outputs according to BIP69
    createTx: true, // Create the transaction
    dust: BigInt(546), // TODO: update scure-btc-signer
  });
  //console.log(transaction);
  // no crash here!
});

@gregdhill
Copy link
Author

Trying to put together a local repro FYI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants
@paulmillr @gregdhill and others