I’m trying to spend an Taproot address via a key spend path with musig2 lib in Rust. This address is aggregated with 2 owners. But I still got this message when I broadcast the hex transaction to https://blockstream.info/testnet/tx/push
sendrawtransaction RPC error: {"code":-26,"message":"non-mandatory-script-verify-flag (Invalid Schnorr signature)"}
The logic to create this transaction is that. First I need to create a deposit transaction that contains that aggregated address as output.
Here the code to create the aggregated address :
pub fn aggregate_pubkeys(
owner_pubkey: PublicKey,
se_pubkey: PublicKey,
) -> (PublicKey, PublicKey, Address, KeyAggContext) {
let secp = Secp256k1::new();
let mut pubkeys: Vec<PublicKey> = vec![];
pubkeys.push(owner_pubkey);
pubkeys.push(se_pubkey);
let key_agg_ctx_tw = KeyAggContext::new(pubkeys.clone())
.unwrap()
.with_unspendable_taproot_tweak()
.unwrap();
let aggregated_pubkey: PublicKey = key_agg_ctx_tw.aggregated_pubkey_untweaked();
let aggregated_pubkey_tw: PublicKey = key_agg_ctx_tw.aggregated_pubkey();
let aggregated_address = Address::p2tr(
&secp,
aggregated_pubkey.x_only_public_key().0,
None,
Network::Testnet,
);
(
aggregated_pubkey,
aggregated_pubkey_tw,
aggregated_address,
key_agg_ctx_tw,
)
}
Then I use the aggregated pub key to create the scriptpubkey for the output to the deposit transaction :
let agg_scriptpubkey = ScriptBuf::new_p2tr(&secp, agg_pubkey.x_only_public_key().0, None);
let utxo = TxOut {
value: Amount::from_sat(amount),
script_pubkey: agg_scriptpubkey,
};
aggregated_pubkey_tw and key_agg_ctx_tw are used to create the aggregated signature. I follow this document to create the signature https://docs.rs/musig2/latest/musig2/
here the code to create the spend transaction :
pub async fn create_bk_tx(
pool: &PoolWrapper,
conn: &NodeConnector,
key_agg_ctx: &KeyAggContext,
agg_pubkey: &PublicKey,
agg_pubkey_tw: &PublicKey,
agg_address: &Address,
receiver_address: &str,
txid: &str,
vout: u32,
amount: u64,
statechain_id: &str,
) -> Result<()> {
let secp = Secp256k1::new();
let seckey = pool
.get_seckey_by_id(&statechain_id)
.await
.unwrap()
.unwrap();
let seckey = SecretKey::from_str(&seckey).unwrap();
// CREATE UNSIGNED TRANSACTION
let agg_scriptpubkey = ScriptBuf::new_p2tr(&secp, agg_pubkey.x_only_public_key().0, None);
let prev_outpoint = OutPoint {
txid: txid.parse().unwrap(),
vout: vout.into(),
};
let input = TxIn {
previous_output: prev_outpoint,
script_sig: ScriptBuf::default(),
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
witness: Witness::default(),
};
let output_address = Address::from_str(receiver_address).unwrap();
let checked_output_address = output_address.require_network(Network::Testnet).unwrap();
let spend = TxOut {
value: Amount::from_sat(amount - BASE_TX_FEE),
script_pubkey: checked_output_address.script_pubkey(),
};
let mut unsigned_tx = Transaction {
version: transaction::Version::TWO, // Post BIP-68.
lock_time: absolute::LockTime::ZERO, // Ignore the locktime.
input: vec![input], // Input goes into index 0.
output: vec![spend], // Outputs, order does not matter.
};
let utxo = TxOut {
value: Amount::from_sat(amount),
script_pubkey: agg_scriptpubkey,
};
let prevouts = vec![utxo];
let prevouts = Prevouts::All(&prevouts);
let mut sighasher = SighashCache::new(&mut unsigned_tx);
let sighash_type = TapSighashType::All;
let sighash = sighasher
.taproot_key_spend_signature_hash(0, &prevouts, sighash_type)
.expect("failed to construct sighash");
let message = sighash.to_string();
let parsed_msg = message.clone();
let msg_clone = parsed_msg.clone();
let msg = parsed_msg.clone();
// MUSIG 2 TO CREATE SIGNATURE ON THE UNSIGNED TRANSACTION
let get_nonce_res = statechain::get_nonce(&conn, statechain_id, &signed_statechain_id).await?; // API to get nonce from the server
let server_pubnonce = get_nonce_res.server_nonce;
let nonce_seed = [0xACu8; 32];
let secnonce = SecNonce::build(nonce_seed).with_seckey(seckey).build();
let our_public_nonce = secnonce.public_nonce();
let public_nonces = [
our_public_nonce,
server_pubnonce.parse::<PubNonce>().unwrap(),
];
let agg_pubnonce: AggNonce = public_nonces.iter().sum();
let agg_pubnonce_str = agg_pubnonce.to_string();
let our_partial_signature: PartialSignature = musig2::sign_partial(
&key_agg_ctx,
seckey,
secnonce,
&agg_pubnonce,
message,
)
.expect("error creating partial signature");
let serialized_key_agg_ctx = key_agg_ctx
.to_bytes()
.to_hex_string(bitcoin::hex::Case::Lower);
let get_sign_res = statechain::get_partial_signature(
&conn,
&serialized_key_agg_ctx,
&statechain_id,
&signed_statechain_id,
&msg_clone,
&agg_pubnonce_str,
)
.await?; // API to request the partial signature from the server
let server_signature = get_sign_res.partial_signature;
let partial_signatures = [
our_partial_signature,
PartialSignature::from_hex(&server_signature).unwrap(),
];
let final_signature: secp256k1::schnorr::Signature = musig2::aggregate_partial_signatures(
&key_agg_ctx,
&agg_pubnonce,
partial_signatures,
msg_clone,
)
.expect("error aggregating signatures");
musig2::verify_single(*agg_pubkey_tw, final_signature, msg)
.expect("aggregated signature must be valid");
let signature = bitcoin::taproot::Signature {
sig: final_signature,
hash_ty: sighash_type,
};
let mut wit = Witness::new();
wit.push(signature.to_vec());
*sighasher.witness_mut(0).unwrap() = wit;
let tx = sighasher.into_transaction();
let tx_hex = consensus::encode::serialize_hex(&tx); // the final transaction to broadcast
Ok(())
}
The signature pass the musig2 verification but not the node verification. I think the problem can relate to the sighash message that I request to sign. Here the deposit transaction and the spend transaction generated from the code. Any thoughts or help would be immensely appreciated. Thank you in advance!
deposit tx hex :
02000000000101adbc7f34e1e97d380be4056b77843588f0bf15549ee6044eec203d52108dd0c30000000000ffffffff02ec13000000000000225120d0d489a32e40e1fa4811aae37c0f1661b27f295bf7bb9d6725a80b17347306675b07000000000000160014c9da028a0c782cbd1c20210e6a80592210fb190c0247304402201633ac94ab0c6561cd48244129a59c03d1b0acfc59cf141ee723ba1755b1376d02202ba6003fd635dca60e68f03b5a01567d71c73eeac23fa5cf0d89d2ef359cde400121030253610ad0dd0958c56cd4a2865fb3c2b333c12eaa5a7c5986c324543e186b3800000000
Spend tx hex :
020000000001010412ef1c75adf318b320982fb31824d9da843fc4ec3b80af12846ec0893b7b440000000000fdffffff016400000000000000160014c9da028a0c782cbd1c20210e6a80592210fb190c0141ca1633ea01751f2b5e66330cd54a2cae88a5e92901a2a3261e266a1392685780d48f511309b0d3d2e2828409948723180d1be3a597ca55df12aeaddbed2008280100000000