Monday, September 16, 2024
HomeBitcointaproot - Musig signature validation mismatch

taproot – Musig signature validation mismatch


I am making an attempt to know the primitives of Taproot and Musig2 so I can implement them into my utility. I am utilizing the rust-bitcoin library and have written a script to create a Taproot deal with from an aggregated Musig2 public key (i’ve funded it with sats on mutinynet), create a tx spending from it & signal it, confirm the signature, after which spend the tx. Nevertheless, I am getting the error mandatory-script-verify-flag-failed (Invalid Schnorr signature) when I attempt to spend the tx. The odd factor is that the in-script sig validation throws no points.

Right here is the related code:


use bitcoin::consensus::Encodable;

use bitcoin::hashes::Hash;
use bitcoin::key::{Keypair, UntweakedPublicKey};
use bitcoin::locktime::absolute;

use bitcoin::secp256k1::{rand, Message, Secp256k1, SecretKey, Verification};
use bitcoin::sighash::{Prevouts, SighashCache, TapSighashType};
use bitcoin::{
    transaction, Deal with, Quantity, Community, OutPoint, PublicKey, ScriptBuf, Sequence, Transaction,
    TxIn, TxOut, Txid, Witness, XOnlyPublicKey,
};
use musig2::secp::Level;
use musig2::{
    aggregate_partial_signatures, sign_partial, verify_single, AggNonce, BinaryEncoding,
    KeyAggContext, PubNonce, SecNonce,
};
use rand::thread_rng;

use std::str::FromStr;

const DUMMY_UTXO_AMOUNT: u64 = 100000;
const SPEND_AMOUNT: u64 = 90000;
const SK: &str = "e504905952697837ac0f0dc9e46deb0340ae6468b185ba79e4fa96ebda3964c2";
const SK2: &str = "6bb7bc5dd0c9db0ca8bdba4616ee92afceb432551e0f23b32ea7bcada18da696";
const SK3: &str = "57d44ecb8812680e871732233ed8e4d4e11a25f22aef6c81dbe35a424ad8db41";
const TXID: &str = "726499fb478422087e7a89a04f25de5ed98e87c9881e4a264e8f31143cdee17b";
const VOUT: u32 = 1;

fn important() {
    let keypair_array = vec![
        Keypair::from_secret_key(&Secp256k1::new(), &SecretKey::from_str(SK).unwrap()),
        Keypair::from_secret_key(&Secp256k1::new(), &SecretKey::from_str(SK2).unwrap()),
        Keypair::from_secret_key(&Secp256k1::new(), &SecretKey::from_str(SK3).unwrap()),
    ];

    let begin = std::time::Prompt::now();
    let tx = construct_refund_transaction(
        TXID,
        VOUT,
        DUMMY_UTXO_AMOUNT,
        SPEND_AMOUNT,
        "tb1p6e4q26tvyc7dmxfnk5zzhkux9d6pqpn0k5qxlw4gvc90unc0m3rq93a0tl",
        keypair_array,
    );
    let length = begin.elapsed();

    println!("Time taken to assemble refund transaction: {:?}", length);

    match tx {
        Okay(hex) => {
            post_tx(hex);
        }
        Err(e) => {
            println!("Error developing transaction: {:?}", e);
        }
    }
}

pub fn construct_refund_transaction(
    from_txid: &str,
    from_vout: u32,
    from_amount: u64,
    spend_amount: u64,
    to_address: &str,
    keypair_array: Vec,
) -> End result> {
    // Step 1: Convert public keys to factors for MuSig2
    let points_vec: Vec = keypair_array
        .iter()
        .map(|keypair| Level::from_hex(&keypair.public_key().to_string()).unwrap())
        .acquire();

    // Step 2: Create a KeyAggContext for MuSig2
    let context = KeyAggContext::new(points_vec).unwrap();

    // Step 3: Get the aggregated public key
    let agg_pubkey: [u8; 33] = context.aggregated_pubkey();
    // println!("Mixture pubkey: {:?}", hex::encode(agg_pubkey));

    // Step 4: Create a P2TR deal with from the aggregated public key
    let pubkey = PublicKey::from_slice(&agg_pubkey).unwrap();
    let xonly_pubkey = XOnlyPublicKey::from(pubkey);
    let secp = Secp256k1::new();
    // let deal with = Deal with::p2tr(&secp, xonly_pubkey, None, Community::Signet);
    // println!("P2TR deal with: {}", deal with);

    // Step 5: Get the unspent transaction output (UTXO)
    let (out_point, utxo) = unspent_transaction_output(
        &secp,
        xonly_pubkey,
        Txid::from_str(from_txid).unwrap(),
        from_vout,
        from_amount,
    );

    // Step 6: Get the receiver's deal with
    let deal with = receivers_address(to_address);

    // Step 7: Create the transaction enter
    let enter = TxIn {
        previous_output: out_point,
        script_sig: ScriptBuf::default(), // Empty for P2TR
        sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
        witness: Witness::default(), // Shall be stuffed after signing
    };

    // Step 8: Create the transaction output
    let spend = TxOut {
        worth: Quantity::from_sat(spend_amount),
        script_pubkey: deal with.script_pubkey(),
    };

    // Step 9: Assemble the unsigned transaction
    let mut unsigned_tx = Transaction {
        model: transaction::Model::TWO,
        lock_time: absolute::LockTime::ZERO,
        enter: vec![input],
        output: vec![spend],
    };
    let input_index = 0;

    // Step 10: Put together for signing
    let sighash_type = TapSighashType::Default;
    let prevouts = vec![utxo];
    let prevouts = Prevouts::All(&prevouts);

    // Step 11: Calculate the sighash
    let mut sighasher = SighashCache::new(&mut unsigned_tx);
    let sighash = sighasher
        .taproot_key_spend_signature_hash(input_index, &prevouts, sighash_type)
        .anticipate("didn't assemble sighash");

    // Step 12: Put together the message to be signed
    let msg = Message::from_digest(sighash.to_byte_array());

    // Step 13: Generate random nonces for MuSig2
    let mut rng = thread_rng();
    let n = keypair_array.len();
    let secret_nonces: Vec = (0..n).map(|_| SecNonce::random(&mut rng)).acquire();
    let public_nonces: Vec = secret_nonces.iter().map(|sn| sn.public_nonce()).acquire();

    // Step 14: Mixture nonces
    let agg_nonce = AggNonce::sum(&public_nonces);

    // Step 15: Generate partial signatures
    let mut partial_sigs = Vec::new();
    for (i, keypair) in keypair_array.iter().enumerate() {
        let seckey =
            musig2::secp::Scalar::from_slice(&keypair.secret_key().secret_bytes()).unwrap();
        let partial_sig: musig2::PartialSignature = sign_partial(
            &context,
            seckey,
            secret_nonces[i].clone(),
            &agg_nonce,
            msg.as_ref(),
        )
        .unwrap();
        partial_sigs.push(partial_sig);
        // println!("Partial signature {}: {:?}", i, partial_sig);
    }

    // Step 16: Mixture partial signatures
    let aggregated_signature: musig2::CompactSignature =
        aggregate_partial_signatures(&context, &agg_nonce, partial_sigs, msg.as_ref()).unwrap();
    // println!("Aggregated signature: {:?}", aggregated_signature);

    // Step 17: Confirm the aggregated signature
    let verification_result = verify_single(
        Level::from_slice(&agg_pubkey).unwrap(),
        aggregated_signature,
        msg.as_ref(),
    );

    match verification_result {
        Okay(()) => println!("Signature is legitimate"),
        Err(e) => println!("Signature verification failed: {:?}", e),
    }

    // Step 18: Add the signature to the transaction witness
    sighasher
        .witness_mut(input_index)
        .unwrap()
        .push(&aggregated_signature.to_bytes());

    // Step 19: Finalize the signed transaction
    let tx = sighasher.into_transaction();

    // Step 20: Serialize and print the signed transaction
    let mut serialized = Vec::new();
    tx.consensus_encode(&mut serialized).unwrap();
    let hex = serialized.as_hex();
    // println!("Transaction hex: {}", hex);

    Okay(hex.to_string())
}

// Helper operate to parse and validate the receiver's deal with
fn receivers_address(deal with: &str) -> Deal with {
    deal with
        .parse::
>() .anticipate("a sound deal with") .require_network(Community::Signet) .anticipate("legitimate deal with for signet") } // Helper operate to create an unspent transaction output (UTXO) fn unspent_transaction_output( secp: &Secp256k1, internal_key: UntweakedPublicKey, txid: Txid, vout: u32, quantity: u64, ) -> (OutPoint, TxOut) { let script_pubkey = ScriptBuf::new_p2tr(secp, internal_key, None); let out_point = OutPoint { txid, vout }; let utxo = TxOut { worth: Quantity::from_sat(quantity), script_pubkey, }; (out_point, utxo) } fn post_tx(hex: String) { let shopper = reqwest::blocking::Consumer::new(); let res = shopper.publish("https://mutinynet.com/api/tx").physique(hex).ship(); match res { Okay(res) => { if res.standing().is_success() { println!("Response: {:?}", res.textual content()); } else { let error_text = res.textual content().unwrap_or_default(); if let Some(message) = error_text.cut up("message":"").nth(1) { if let Some(error_message) = message.cut up(""").subsequent() { println!("Error: {}", error_message); } else { println!("Error: {}", error_text); } } else { println!("Error: {}", error_text); } } } Err(e) => { println!("Error sending request: {:?}", e); } } }

Am I doing something blatantly improper?

(these are dummy SKs I generated for the aim of this take a look at)

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments