P2WSH-MultiSig fails with documented OP_0 workaround

0

As per the note at Developer Guide - MultiSig and BIP147 when using OP_CHECKMULTISIG an OP_0 needs to be prefaced to the scriptSig to accommodate a bug in the original Bitcoin implementation.

If the same workaround is applied in a Witness Program the script seems to fail the SCRIPT_FLAGS_VERIFY_NULLDUMMY check. What's the correct way to add the OP_0 to a MultiSig Witness Program? Adding an OP_0 as per the sample below results in a final stack element containing 0x00 which fails the check.

auto aliceKeyData = ParseHex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866");
CKey aliceKey{};
aliceKey.Set(aliceKeyData.begin(), aliceKeyData.end(), true);
CPubKey alicePubkey = aliceKey.GetPubKey();

auto keyData = ParseHex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9");
CKey bobKey{};
bobKey.Set(keyData.begin(), keyData.end(), true);
CPubKey bobPubKey = bobKey.GetPubKey();

CScript redeemScript = CScript{} << OP_1 << ToByteVector(alicePubkey) << ToByteVector(bobPubKey) << OP_2 << OP_CHECKMULTISIG;
uint256 redeemScriptHash{};
CSHA256().Write(redeemScript.data(), redeemScript.size()).Finalize(redeemScriptHash.begin());
CScript scriptPubkey = CScript{} << OP_0 << ToByteVector(redeemScriptHash); // P2WSH

int amount = 600000000;

CScript scriptSig;
CScriptWitness scriptWitness;
CMutableTransaction tx = BuildFundingTransaction(scriptSig, scriptWitness, amount);

uint256 coinZeroSigHash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL, amount, SIGVERSION_WITNESS_V0);

CScript op0Script = CScript{} << OP_0;

std::vector<uint8_t> coinZeroBobSig{};
bobKey.Sign(coinZeroSigHash, coinZeroBobSig, 0);
coinZeroBobSig.push_back(SIGHASH_ALL);

CScriptWitness& witness = tx.vin[0].scriptWitness;
witness.stack.push_back(ToByteVector(op0Script));  // <-- Results in 0x00 stack element in Witness Program.
witness.stack.push_back(ToByteVector(coinZeroBobSig));
witness.stack.push_back(ToByteVector(redeemScript));

CDataStream txSpendingStm(SER_NETWORK, PROTOCOL_VERSION);
txSpendingStm << tx;
std::cout << "Spending Tx: " << CTransaction(tx).ToString() << std::endl;

bitcoinconsensus_error err;
auto spendCoinZeroResult = bitcoinconsensus_verify_script_with_amount(scriptPubkey.data(), scriptPubkey.size(), amount, (const unsigned char*)&txSpendingStm[0], txSpendingStm.size(), 0, bitcoinconsensus_SCRIPT_FLAGS_VERIFY_ALL, &err);
std::cout << "Spend Coin Zero result: " << spendCoinZeroResult << ", error code " << err << std::endl;

One trivial fix is to add an empty vector to the Witness Program stack rather than an OP_0, such as by using witness.stack.emplace_back() instead of witness.stack.push_back(ToByteVector(op0Script)), but is that likely to cause any other issues?

sipwiz

Posted 2018-02-26T10:45:01.783

Reputation: 685

Answers

2

The real requirement is that the extra input to OP_CHECKMULTISIG is a zero (that is: the empty byte array).

For non-SegWit inputs, the only canonical way to get such a zero on the stack is by including an OP_0 in the scriptSig. OP_0 just pushes an empty byte array onto the stack.

In SegWit inputs there is no longer a script to build that stack. Instead, you specify the stack directly. As a result, not just the recommended way, but the only way is adding an empty array to the scriptWitness.

Pieter Wuille

Posted 2018-02-26T10:45:01.783

Reputation: 54 032

1

For anyone working in the core test framework, I used this code to create a 2 of 2 multisig witness stack with a null dummy as the first element on the stack:

witness_program = CScript([OP_2, pubkey1, pubkey2, OP_2,OP_CHECKMULTISIG]

tx.wit.vtxinwit[0].scriptWitness.stack = [b'', sig1, sig2, witness_program]

I'm not a Python programmer so it took me some time to figure out that all I needed was b'' to create an empty byte array element.

Richard Myers

Posted 2018-02-26T10:45:01.783

Reputation: 23