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?

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.

| improve this answer | |
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.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.