3

I am looking for code examples (in rust-bitcoin, but even in Python) of how to calculate sighash for various input types. Specifically, I need to calculate the sighashes for M of N multisig unlocking script inputs for these transaction types: P2MS, P2SH, P2WSH and P2TR (to determine which M of N keys correspond to the M input signatures).

For example for a P2SH case, I am looking at output 0 in this tx.

In the spending transaction, the input #3 contains a redeem 2 of 3 mutisig script with 3 compressed 33 byte PubKeys

OP_PUSHNUM_2 OP_PUSHBYTES_33 02261f84d51bb64371cb5e9eec3bbc0c0c7320eb7fa9c5076a394a48a9cd74bfd3 OP_PUSHBYTES_33 023e66621cf94ac25d8bb687abef86d01847805d6e9bff8c3999f18f478cde5ab6 OP_PUSHBYTES_33 02d844059fae247b8e1325f56519d1eb7d4b632ec77b9f71f03102167f3c7fa591 OP_PUSHNUM_3 OP_CHECKMULTISIG

and two signatures in the ScriptSig (OP_PUSHBYTES_71 and OP_PUSHBYTES_72). I concretely want to know which two of the three PubKeys above correspond to these two signatures.

For that, I need to double-loop over the two keys and the three signatures and pass these pairs along with the scriphash into verify_ecdsa(&script_hash, &signature, &pubkey) from secp256k1, and I would like to understand in detail how to compute the sighashes. I am also not clear if these multisig inputs can have any of these three sighash flags SIGHASH_ALL, SIGHASH_SINGLE and SIGHASH_NONE, or only the first one is ever used with multisig?

PS. In the Andrew Poelstra's answer, there's an example sighash computation for P2WPKH. Although I do not need the P2(W)PKH cases because I am not trying to verify the blocks, the example involves manually converting the P2WPKH output into a P2PKH output by wrapping the pkh into OP_DUP OP_HASH160 <pkh-from-P2WPKH-out> OP_EQUALVERIFY OP_CHECKSIG and passing the result into sighash.segwit_encode_signing_data_to(). It would be good to see explicit multisig examples.

4
  • 1
    I would suggest rewriting this question to emphasize that you actually want to compute a sighash, which is very different and more complicated than figuring out the CMS pubkey mapping. To answer the easy parts of your question: no, Bitcoin Core does not expose sighashes in any RPC, and its internal architecture would make this a little tricy. rust-bitcoin exposes the [docs.rs/bitcoin/latest/bitcoin/util/sighash/… type which will assist in computing sighashes, but we have not written any worked examples. Mar 6, 2023 at 14:39
  • 1
    I have opened github.com/rust-bitcoin/rust-bitcoin/issues/1698 to track the lack of documentation in rust-bitcoin. Mar 6, 2023 at 14:43
  • Andrew, I have rewritten the question. When you synthesize the P2PKH output locking script 76a914f5693fbaf062221baf891d813d5856e4f8ab54eb88ac from P2WPKH output in your answer, you drop the first 0x19 byte from github.com/bitcoin/bips/blob/master/bip-0143.mediawiki : "The item 5 : For P2WPKH witness program, the scriptCode is 0x1976a914{20-byte-pubkey-hash}88ac" Why is that 0x19 there in the BIP 143? Mar 8, 2023 at 22:48
  • 1
    The 0x19 is a length prefix. It is automatically incuded by rust-bitcoin. Mar 11, 2023 at 20:52

2 Answers 2

3

I have the following code laying around for the transaction 663becacc6368150a46725e404ccdfa34d1fffbececa784c31f0a7849b4dad08, which I picked off my mempool the other day, whose hex is:

020000000001015ce1d4ffc716022f83cc0d557e6dad0500eeff9e9623bde014bdc09c5b672d750000000000fdffffff025fb7460b000000001600142cf4c1dc0352e0658971ca62a7457a1cd8c3389c4ce3a2000000000016001433f57fe374c6ceab61c8639128c038ac2a8c8db60247304402203cb50efb5c4a9aa7fd369ab6f4b226db99f44f9c610b5b50bc42f343a6aa401302201af791542eee6c1b11705e8895cc5adc36458910dc91aadcafb76a6478a29b9f01210242e811e66fd17e9a6e4ef772766c668d6e0595ca1d7f0583148bc460b575fbfdf0df0b00

use bitcoin::consensus::deserialize;
use bitcoin::hashes::{hex, sha256d, Hash};
use bitcoin::util::sighash;
use std::env;
use std::str::FromStr;

fn main() { 
    let rawtx = env::args().skip(1).next().unwrap();

    let bytes: Vec<u8> = hex::FromHex::from_hex(&rawtx)
        .expect("hex decoding");
    let tx: bitcoin::Transaction = deserialize(&bytes)
        .expect("tx deserialization");

    let pk = bitcoin::secp256k1::PublicKey::from_str(
        "0242e811e66fd17e9a6e4ef772766c668d6e0595ca1d7f0583148bc460b575fbfd",
    ).unwrap();

    let mut sighash = sighash::SighashCache::new(&tx);
    let mut out_bytes = vec![];
    sighash.segwit_encode_signing_data_to(
        &mut out_bytes,
        0,
        &bitcoin::Script::from_str("76a914f5693fbaf062221baf891d813d5856e4f8ab54eb88ac").unwrap(),
        200000000,
        bitcoin::EcdsaSighashType::All,
    ).expect("computing sighash");


    println!("{}", hex::ToHex::to_hex(&out_bytes[..]));

    let sig = bitcoin::secp256k1::ecdsa::Signature::from_str(
        "304402203cb50efb5c4a9aa7fd369ab6f4b226db99f44f9c610b5b50bc42f343a6aa401302201af791542eee6c1b11705e8895cc5adc36458910dc91aadcafb76a6478a29b9f",
    ).unwrap();
    let secp = bitcoin::secp256k1::Secp256k1::new();

    let hash = sha256d::Hash::hash(&out_bytes);
    let msg = bitcoin::secp256k1::Message::from_slice(&hash[..]).unwrap();
    secp.verify_ecdsa(
        &msg,
        &sig,
        &pk,
    ).unwrap();
}

This works only for this specific p2wpkh transaction, but it may help you get started. The weird thing is that the script_code is created by synthethizing a p2pkh output from the p2wpkh output. For p2wsh or p2sh (as any CHECKMULTISIG) script will be, this is simpler: you can just use the redeem/witness script here.

0

I created a GitHub repo with the answer examples.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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