diff --git a/core/src/actor.rs b/core/src/actor.rs index 4c01f9f9..a804cd7d 100644 --- a/core/src/actor.rs +++ b/core/src/actor.rs @@ -122,6 +122,27 @@ impl Actor { Ok(self.sign(sig_hash)) } + pub fn sign_taproot_pubkey_spend( + &self, + tx_handler: &mut TxHandler, + input_index: usize, + sighash_type: Option, + ) -> Result { + let mut sighash_cache = SighashCache::new(&mut tx_handler.tx); + let sig_hash = sighash_cache.taproot_key_spend_signature_hash( + input_index, + &match sighash_type { + Some(TapSighashType::SinglePlusAnyoneCanPay) => bitcoin::sighash::Prevouts::One( + input_index, + tx_handler.prevouts[input_index].clone(), + ), + _ => bitcoin::sighash::Prevouts::All(&tx_handler.prevouts), + }, + sighash_type.unwrap_or(TapSighashType::Default), + )?; + self.sign_with_tweak(sig_hash, None) + } + pub fn sign_taproot_pubkey_spend_tx( &self, tx: &mut bitcoin::Transaction, diff --git a/core/src/database/common.rs b/core/src/database/common.rs index e8032e67..80542a76 100644 --- a/core/src/database/common.rs +++ b/core/src/database/common.rs @@ -9,10 +9,11 @@ use crate::{config::BridgeConfig, errors::BridgeError}; use crate::{EVMAddress, UTXO}; use bitcoin::address::NetworkUnchecked; use bitcoin::{Address, OutPoint, Txid}; +use secp256k1::schnorr; use sqlx::{Pool, Postgres}; use std::fs; -use super::wrapper::{AddressDB, EVMAddressDB, OutPointDB, TxOutDB, TxidDB, UTXODB}; +use super::wrapper::{AddressDB, EVMAddressDB, OutPointDB, SignatureDB, TxOutDB, TxidDB, UTXODB}; #[derive(Clone, Debug)] pub struct Database { @@ -401,6 +402,100 @@ impl Database { Ok(()) } + pub async fn save_slash_or_take_sig( + &self, + deposit_outpoint: OutPoint, + kickoff_utxo: UTXO, + slash_or_take_sig: schnorr::Signature, + ) -> Result<(), BridgeError> { + sqlx::query( + "UPDATE deposit_kickoff_utxos + SET slash_or_take_sig = $3 + WHERE deposit_outpoint = $1 AND kickoff_utxo = $2;", + ) + .bind(OutPointDB(deposit_outpoint)) + .bind(sqlx::types::Json(UTXODB { + outpoint_db: OutPointDB(kickoff_utxo.outpoint), + txout_db: TxOutDB(kickoff_utxo.txout), + })) + .bind(SignatureDB(slash_or_take_sig)) + .execute(&self.connection) + .await?; + + Ok(()) + } + + pub async fn get_slash_or_take_sig( + &self, + deposit_outpoint: OutPoint, + kickoff_utxo: UTXO, + ) -> Result, BridgeError> { + let qr: Option<(SignatureDB,)> = sqlx::query_as( + "SELECT slash_or_take_sig + FROM deposit_kickoff_utxos + WHERE deposit_outpoint = $1 AND kickoff_utxo = $2;", + ) + .bind(OutPointDB(deposit_outpoint)) + .bind(sqlx::types::Json(UTXODB { + outpoint_db: OutPointDB(kickoff_utxo.outpoint), + txout_db: TxOutDB(kickoff_utxo.txout), + })) + .fetch_optional(&self.connection) + .await?; + + match qr { + Some(sig) => Ok(Some(sig.0 .0)), + None => Ok(None), + } + } + + pub async fn save_operator_take_sig( + &self, + deposit_outpoint: OutPoint, + kickoff_utxo: UTXO, + operator_take_sig: schnorr::Signature, + ) -> Result<(), BridgeError> { + sqlx::query( + "UPDATE deposit_kickoff_utxos + SET operator_take_sig = $3 + WHERE deposit_outpoint = $1 AND kickoff_utxo = $2;", + ) + .bind(OutPointDB(deposit_outpoint)) + .bind(sqlx::types::Json(UTXODB { + outpoint_db: OutPointDB(kickoff_utxo.outpoint), + txout_db: TxOutDB(kickoff_utxo.txout), + })) + .bind(SignatureDB(operator_take_sig)) + .execute(&self.connection) + .await?; + + Ok(()) + } + + pub async fn get_operator_take_sig( + &self, + deposit_outpoint: OutPoint, + kickoff_utxo: UTXO, + ) -> Result, BridgeError> { + let qr: Option<(SignatureDB,)> = sqlx::query_as( + "SELECT operator_take_sig + FROM deposit_kickoff_utxos + WHERE deposit_outpoint = $1 AND kickoff_utxo = $2;", + ) + .bind(OutPointDB(deposit_outpoint)) + .bind(sqlx::types::Json(UTXODB { + outpoint_db: OutPointDB(kickoff_utxo.outpoint), + txout_db: TxOutDB(kickoff_utxo.txout), + })) + .fetch_optional(&self.connection) + .await?; + + match qr { + Some(sig) => Ok(Some(sig.0 .0)), + None => Ok(None), + } + } + pub async fn add_deposit_kickoff_generator_tx( &self, txid: Txid, diff --git a/core/src/database/wrapper.rs b/core/src/database/wrapper.rs index 52977c7f..326514df 100644 --- a/core/src/database/wrapper.rs +++ b/core/src/database/wrapper.rs @@ -24,7 +24,7 @@ pub struct EVMAddressDB(pub EVMAddress); #[derive(Serialize, Deserialize)] pub struct TxidDB(pub Txid); -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, sqlx::FromRow)] pub struct SignatureDB(pub secp256k1::schnorr::Signature); #[derive(Serialize, Deserialize, sqlx::FromRow)] diff --git a/core/src/errors.rs b/core/src/errors.rs index 3eea15b1..34666c21 100644 --- a/core/src/errors.rs +++ b/core/src/errors.rs @@ -3,6 +3,7 @@ //! This module defines errors, returned by the library. use bitcoin::{ + consensus::encode::FromHexError, merkle_tree::MerkleBlockError, taproot::{TaprootBuilder, TaprootBuilderError}, }; @@ -157,7 +158,7 @@ pub enum BridgeError { DepositInfoNotFound, #[error("FromHexError: {0}")] - FromHexError(#[from] hex::FromHexError), + FromHexError(#[from] FromHexError), #[error("FromSliceError: {0}")] FromSliceError(#[from] bitcoin::hashes::FromSliceError), diff --git a/core/src/extended_rpc.rs b/core/src/extended_rpc.rs index 8137d5c8..04a167dd 100644 --- a/core/src/extended_rpc.rs +++ b/core/src/extended_rpc.rs @@ -260,9 +260,9 @@ where self.client.get_transaction(txid, include_watchonly) } - pub fn send_raw_transaction( + pub fn send_raw_transaction( &self, - tx: &Transaction, + tx: T, ) -> Result { self.client.send_raw_transaction(tx) } diff --git a/core/src/musig2.rs b/core/src/musig2.rs index 2d5cb8bc..c0db4f09 100644 --- a/core/src/musig2.rs +++ b/core/src/musig2.rs @@ -1,8 +1,9 @@ +use crate::{errors::BridgeError, ByteArray66}; +use bitcoin::hashes::Hash; +use bitcoin::TapNodeHash; use musig2::{sign_partial, AggNonce, KeyAggContext, SecNonce, SecNonceSpices}; use secp256k1::{rand::Rng, PublicKey}; -use crate::{errors::BridgeError, ByteArray66}; - // We can directly use the musig2 crate for this // No need for extra types etc. // MuSigPubNonce consists of two curve points, so it's 66 bytes (compressed). @@ -17,22 +18,67 @@ pub type MuSigPartialSignature = [u8; 32]; pub type MuSigFinalSignature = [u8; 64]; pub type MuSigNoncePair = (MuSigSecNonce, MuSigPubNonce); +pub trait AggregateFromPublicKeys { + fn from_musig2_pks( + pks: Vec, + tweak: Option, + tweak_flag: bool, + ) -> secp256k1::XOnlyPublicKey; +} + +impl AggregateFromPublicKeys for secp256k1::XOnlyPublicKey { + fn from_musig2_pks( + pks: Vec, + tweak: Option, + tweak_flag: bool, + ) -> secp256k1::XOnlyPublicKey { + let key_agg_ctx = create_key_agg_ctx(pks, tweak, tweak_flag).unwrap(); + let musig_agg_pubkey: musig2::secp256k1::PublicKey = if tweak_flag { + key_agg_ctx.aggregated_pubkey() + } else { + key_agg_ctx.aggregated_pubkey_untweaked() + }; + // tracing::debug!("UNTWEAKED AGGREGATED PUBKEY: {:?}", musig_agg_pubkey); + let musig_agg_xonly_pubkey = musig_agg_pubkey.x_only_public_key().0; + let musig_agg_xonly_pubkey_wrapped = + secp256k1::XOnlyPublicKey::from_slice(&musig_agg_xonly_pubkey.serialize()).unwrap(); + + musig_agg_xonly_pubkey_wrapped + } +} + // Creates the key aggregation context, with the public keys and the tweak (if any). // There are two functions to retrieve the aggregated public key, one with the tweak and one without. pub fn create_key_agg_ctx( pks: Vec, - tweak: Option<[u8; 32]>, + tweak: Option, + tweak_flag: bool, ) -> Result { let musig_pks: Vec = pks .iter() .map(|pk| musig2::secp256k1::PublicKey::from_slice(&pk.serialize()).unwrap()) .collect::>(); let key_agg_ctx_raw = KeyAggContext::new(musig_pks)?; - let key_agg_ctx = match tweak { - Some(scalar) => key_agg_ctx_raw.with_taproot_tweak(&scalar)?, - None => key_agg_ctx_raw, - }; - Ok(key_agg_ctx) + tracing::debug!( + "UNTWEAKED AGGREGATED PUBKEY: {:?}", + key_agg_ctx_raw.aggregated_pubkey::() + ); + if tweak_flag { + let key_agg_ctx = match tweak { + Some(scalar) => key_agg_ctx_raw.with_taproot_tweak(&scalar.to_byte_array())?, + None => key_agg_ctx_raw.with_unspendable_taproot_tweak()?, + }; + tracing::debug!( + "TWEAKED AGGREGATED PUBKEY: {:?}", + key_agg_ctx.aggregated_pubkey::() + ); + Ok(key_agg_ctx) + } else { + if let Some(_) = tweak { + return Err(BridgeError::VecConversionError); // TODO: Change error handling. + } + Ok(key_agg_ctx_raw) + } } // Aggregates the public nonces into a single aggregated nonce. Wrapper for the musig2::AggNonce::sum function. @@ -48,12 +94,13 @@ pub fn aggregate_nonces(pub_nonces: Vec) -> MuSigAggNonce { // Aggregates the partial signatures into a single final signature. Wrapper for the musig2::aggregate_partial_signatures function. pub fn aggregate_partial_signatures( pks: Vec, - tweak: Option<[u8; 32]>, + tweak: Option, + tweak_flag: bool, agg_nonce: &MuSigAggNonce, partial_sigs: Vec, message: [u8; 32], ) -> Result<[u8; 64], BridgeError> { - let key_agg_ctx = create_key_agg_ctx(pks, tweak).unwrap(); + let key_agg_ctx = create_key_agg_ctx(pks, tweak, tweak_flag).unwrap(); let musig_partial_sigs: Vec = partial_sigs .iter() .map(|x| musig2::PartialSignature::from_slice(x).unwrap()) @@ -92,13 +139,14 @@ pub fn partial_sign( pks: Vec, // Aggregated tweak, if there is any. This is useful for // Taproot key-spends, since we might have script-spend conditions. - tweak: Option<[u8; 32]>, + tweak: Option, + tweak_flag: bool, sec_nonce: MuSigSecNonce, agg_nonce: MuSigAggNonce, keypair: &secp256k1::Keypair, sighash: [u8; 32], ) -> MuSigPartialSignature { - let key_agg_ctx = create_key_agg_ctx(pks, tweak).unwrap(); + let key_agg_ctx = create_key_agg_ctx(pks, tweak, tweak_flag).unwrap(); let musig_sec_nonce = SecNonce::from_bytes(&sec_nonce).unwrap(); let musig_agg_nonce = AggNonce::from_bytes(&agg_nonce.0).unwrap(); let partial_signature: [u8; 32] = sign_partial( @@ -120,6 +168,7 @@ mod tests { use crate::{ actor::Actor, errors::BridgeError, + musig2::AggregateFromPublicKeys, transaction_builder::{TransactionBuilder, TxHandler}, utils, }; @@ -127,32 +176,10 @@ mod tests { hashes::Hash, opcodes::all::OP_CHECKSIG, script, Amount, OutPoint, ScriptBuf, TapNodeHash, TxOut, Txid, }; - use secp256k1::{rand::Rng, Keypair, Message, PublicKey, XOnlyPublicKey}; + use secp256k1::{rand::Rng, Keypair, Message, XOnlyPublicKey}; use super::{nonce_pair, MuSigNoncePair}; - trait FromPublicKeys { - fn from_musig2_pks( - pks: Vec, - tweak: Option<[u8; 32]>, - ) -> (musig2::secp256k1::PublicKey, XOnlyPublicKey); - } - - impl FromPublicKeys for XOnlyPublicKey { - fn from_musig2_pks( - pks: Vec, - tweak: Option<[u8; 32]>, - ) -> (musig2::secp256k1::PublicKey, XOnlyPublicKey) { - let key_agg_ctx = super::create_key_agg_ctx(pks, tweak).unwrap(); - let musig_agg_pubkey: musig2::secp256k1::PublicKey = key_agg_ctx.aggregated_pubkey(); - let musig_agg_xonly_pubkey = musig_agg_pubkey.x_only_public_key().0; - let musig_agg_xonly_pubkey_wrapped = - XOnlyPublicKey::from_slice(&musig_agg_xonly_pubkey.serialize()).unwrap(); - - (musig_agg_pubkey, musig_agg_xonly_pubkey_wrapped) - } - } - // Generates a test setup with a given number of signers. Returns a vector of keypairs and a vector of nonce pairs. fn generate_test_setup(num_signers: usize) -> (Vec, Vec) { let mut keypair_vec: Vec = Vec::new(); @@ -182,7 +209,7 @@ mod tests { .map(|kp| kp.public_key()) .collect::>(); // Create the key aggregation context - let key_agg_ctx = super::create_key_agg_ctx(pks.clone(), None).unwrap(); + let key_agg_ctx = super::create_key_agg_ctx(pks.clone(), None, false).unwrap(); // Aggregate the public nonces into the aggregated nonce let agg_nonce = super::aggregate_nonces(nonce_pair_vec.iter().map(|x| x.1.clone()).collect()); @@ -196,6 +223,7 @@ mod tests { super::partial_sign( pks.clone(), None, + false, nonce_pair.0, agg_nonce.clone(), kp, @@ -204,9 +232,15 @@ mod tests { }) .collect(); // Aggregate the partial signatures into a final signature - let final_signature: [u8; 64] = - super::aggregate_partial_signatures(pks, None, &agg_nonce, partial_sigs, message) - .unwrap(); + let final_signature: [u8; 64] = super::aggregate_partial_signatures( + pks, + None, + false, + &agg_nonce, + partial_sigs, + message, + ) + .unwrap(); musig2::verify_single(musig_agg_pubkey, &final_signature, message) .expect("Verification failed!"); println!("MuSig2 signature verified successfully!"); @@ -230,6 +264,7 @@ mod tests { let partial_sig_0 = super::partial_sign( pks.clone(), None, + false, sec_nonce_0, agg_nonce.clone(), &kp_0, @@ -238,6 +273,7 @@ mod tests { let partial_sig_1 = super::partial_sign( pks.clone(), None, + false, sec_nonce_1, agg_nonce.clone(), &kp_1, @@ -246,15 +282,22 @@ mod tests { // Oops, a verifier accidentally added some tweak! let partial_sig_2 = super::partial_sign( pks.clone(), - Some([1u8; 32]), + Some(TapNodeHash::from_slice(&[1u8; 32]).unwrap()), + true, sec_nonce_2, agg_nonce.clone(), &kp_2, message, ); let partial_sigs = vec![partial_sig_0, partial_sig_1, partial_sig_2]; - let final_signature: Result<[u8; 64], BridgeError> = - super::aggregate_partial_signatures(pks, None, &agg_nonce, partial_sigs, message); + let final_signature: Result<[u8; 64], BridgeError> = super::aggregate_partial_signatures( + pks, + None, + false, + &agg_nonce, + partial_sigs, + message, + ); assert!(final_signature.is_err()); } @@ -268,7 +311,12 @@ mod tests { .iter() .map(|kp| kp.public_key()) .collect::>(); - let key_agg_ctx = super::create_key_agg_ctx(pks.clone(), Some(tweak)).unwrap(); + let key_agg_ctx = super::create_key_agg_ctx( + pks.clone(), + Some(TapNodeHash::from_slice(&tweak).unwrap()), + true, + ) + .unwrap(); let agg_nonce = super::aggregate_nonces(nonce_pair_vec.iter().map(|x| x.1.clone()).collect()); let musig_agg_pubkey: musig2::secp256k1::PublicKey = key_agg_ctx.aggregated_pubkey(); @@ -278,7 +326,8 @@ mod tests { .map(|(kp, nonce_pair)| { super::partial_sign( pks.clone(), - Some(tweak), + Some(TapNodeHash::from_slice(&tweak).unwrap()), + true, nonce_pair.0, agg_nonce.clone(), kp, @@ -288,7 +337,8 @@ mod tests { .collect(); let final_signature: [u8; 64] = super::aggregate_partial_signatures( pks, - Some(tweak), + Some(TapNodeHash::from_slice(&tweak).unwrap()), + true, &agg_nonce, partial_sigs, message, @@ -316,7 +366,8 @@ mod tests { let agg_nonce = super::aggregate_nonces(vec![pub_nonce_0, pub_nonce_1, pub_nonce_2]); let partial_sig_0 = super::partial_sign( pks.clone(), - Some(tweak), + Some(TapNodeHash::from_slice(&tweak).unwrap()), + true, sec_nonce_0, agg_nonce.clone(), &kp_0, @@ -324,7 +375,8 @@ mod tests { ); let partial_sig_1 = super::partial_sign( pks.clone(), - Some(tweak), + Some(TapNodeHash::from_slice(&tweak).unwrap()), + true, sec_nonce_1, agg_nonce.clone(), &kp_1, @@ -334,6 +386,7 @@ mod tests { let partial_sig_2 = super::partial_sign( pks.clone(), None, + false, sec_nonce_2, agg_nonce.clone(), &kp_2, @@ -342,7 +395,8 @@ mod tests { let partial_sigs = vec![partial_sig_0, partial_sig_1, partial_sig_2]; let final_signature = super::aggregate_partial_signatures( pks, - Some(tweak), + Some(TapNodeHash::from_slice(&tweak).unwrap()), + true, &agg_nonce, partial_sigs, message, @@ -358,7 +412,7 @@ mod tests { .iter() .map(|kp| kp.public_key()) .collect::>(); - let key_agg_ctx = super::create_key_agg_ctx(pks.clone(), None).unwrap(); + let key_agg_ctx = super::create_key_agg_ctx(pks.clone(), None, true).unwrap(); let untweaked_pubkey = key_agg_ctx.aggregated_pubkey_untweaked::(); @@ -407,18 +461,14 @@ mod tests { .unwrap() .to_byte_array(); let merkle_root = sending_address_spend_info.merkle_root(); - let tweak: [u8; 32] = match merkle_root { - Some(root) => root.to_byte_array(), - None => TapNodeHash::all_zeros().to_byte_array(), - }; - let partial_sigs: Vec<[u8; 32]> = kp_vec .iter() .zip(nonce_pair_vec.iter()) .map(|(kp, nonce_pair)| { super::partial_sign( pks.clone(), - Some(tweak), + merkle_root, + true, nonce_pair.0, agg_nonce.clone(), kp, @@ -428,16 +478,17 @@ mod tests { .collect(); let final_signature: [u8; 64] = super::aggregate_partial_signatures( pks.clone(), - Some(tweak), + merkle_root, + true, &agg_nonce, partial_sigs, message, ) .unwrap(); - let (musig_agg_pubkey, musig_agg_xonly_pubkey_wrapped) = - XOnlyPublicKey::from_musig2_pks(pks, Some(tweak)); - musig2::verify_single(musig_agg_pubkey, &final_signature, message) - .expect("Verification failed!"); + let musig_agg_xonly_pubkey_wrapped = + XOnlyPublicKey::from_musig2_pks(pks, merkle_root, true); + // musig2::verify_single(musig_agg_pubkey, &final_signature, message) + // .expect("Verification failed!"); let res = utils::SECP .verify_schnorr( &secp256k1::schnorr::Signature::from_slice(&final_signature).unwrap(), @@ -459,8 +510,8 @@ mod tests { .collect::>(); let agg_nonce = super::aggregate_nonces(nonce_pair_vec.iter().map(|x| x.1.clone()).collect()); - let (musig_agg_pubkey, musig_agg_xonly_pubkey_wrapped) = - XOnlyPublicKey::from_musig2_pks(pks.clone(), None); + let musig_agg_xonly_pubkey_wrapped = + XOnlyPublicKey::from_musig2_pks(pks.clone(), None, false); let musig2_script = bitcoin::script::Builder::new() .push_x_only_key(&musig_agg_xonly_pubkey_wrapped) .push_opcode(OP_CHECKSIG) @@ -509,6 +560,7 @@ mod tests { super::partial_sign( pks.clone(), None, + false, nonce_pair.0, agg_nonce.clone(), kp, @@ -516,11 +568,17 @@ mod tests { ) }) .collect(); - let final_signature: [u8; 64] = - super::aggregate_partial_signatures(pks, None, &agg_nonce, partial_sigs, message) - .unwrap(); - musig2::verify_single(musig_agg_pubkey, &final_signature, message) - .expect("Verification failed!"); + let final_signature: [u8; 64] = super::aggregate_partial_signatures( + pks, + None, + false, + &agg_nonce, + partial_sigs, + message, + ) + .unwrap(); + // musig2::verify_single(musig_agg_pubkey, &final_signature, message) + // .expect("Verification failed!"); let res = utils::SECP .verify_schnorr( &secp256k1::schnorr::Signature::from_slice(&final_signature).unwrap(), diff --git a/core/src/operator.rs b/core/src/operator.rs index f58b432e..819f2227 100644 --- a/core/src/operator.rs +++ b/core/src/operator.rs @@ -3,13 +3,15 @@ use crate::config::BridgeConfig; use crate::database::operator::OperatorDB; use crate::errors::BridgeError; use crate::extended_rpc::ExtendedRpc; -use crate::musig2; +use crate::musig2::{self, AggregateFromPublicKeys}; use crate::traits::rpc::OperatorRpcServer; use crate::transaction_builder::TransactionBuilder; +use crate::utils::handle_taproot_witness_new; use crate::{script_builder, utils, EVMAddress, UTXO}; use ::musig2::secp::Point; use bitcoin::address::NetworkUnchecked; use bitcoin::consensus::deserialize; +use bitcoin::consensus::encode::deserialize_hex; use bitcoin::hashes::Hash; use bitcoin::sighash::SighashCache; use bitcoin::{Address, OutPoint, TapSighash, Transaction, TxOut, Txid}; @@ -45,10 +47,11 @@ where let db = OperatorDB::new(config.clone()).await; - let key_agg_context = - musig2::create_key_agg_ctx(config.verifiers_public_keys.clone(), None)?; - let agg_point: Point = key_agg_context.aggregated_pubkey_untweaked(); - let nofn_xonly_pk = secp256k1::XOnlyPublicKey::from_slice(&agg_point.serialize_xonly())?; + let nofn_xonly_pk = secp256k1::XOnlyPublicKey::from_musig2_pks( + config.verifiers_public_keys.clone(), + None, + false, + ); let idx = config .operators_xonly_pks .iter() @@ -144,18 +147,22 @@ where )); } - let kickoff_tx_handler = TransactionBuilder::create_kickoff_utxo_tx( + let mut kickoff_tx_handler = TransactionBuilder::create_kickoff_utxo_tx( &funding_utxo, &self.nofn_xonly_pk, &self.signer.xonly_public_key, self.config.network, ); + let sig = self + .signer + .sign_taproot_pubkey_spend(&mut kickoff_tx_handler, 0, None)?; + handle_taproot_witness_new(&mut kickoff_tx_handler, &[sig.as_ref()], 0, None)?; - tracing::debug!( - "For operator index: {:?} Kickoff tx handler: {:#?}", - self.idx, - kickoff_tx_handler - ); + // tracing::debug!( + // "For operator index: {:?} Kickoff tx handler: {:#?}", + // self.idx, + // kickoff_tx_handler + // ); let change_utxo = UTXO { outpoint: OutPoint { @@ -296,9 +303,177 @@ where async fn withdrawal_proved_on_citrea( &self, _withdrawal_idx: usize, - _kickoff_merkle_root: [u8; 32], - ) -> Result<(), BridgeError> { - Ok(()) + deposit_outpoint: OutPoint, + ) -> Result, BridgeError> { + let kickoff_utxo = self + .db + .get_kickoff_utxo(deposit_outpoint) + .await? + .ok_or(BridgeError::KickoffOutpointsNotFound)?; + tracing::debug!("Kickoff UTXO FOUND: {:?}", kickoff_utxo); + let mut txs_to_be_sent = vec![]; + let mut current_searching_txid = kickoff_utxo.outpoint.txid; + let mut found_txid = false; + + for _ in 0..25 { + // Check if the current txid is onchain or in mempool + if self + .rpc + .get_raw_transaction(¤t_searching_txid, None) + .is_ok() + { + found_txid = true; + break; + } + + // Fetch the transaction and continue the loop + let (raw_signed_tx, _, _, funding_txid) = self + .db + .get_deposit_kickoff_generator_tx(current_searching_txid) + .await? + .ok_or(BridgeError::KickoffOutpointsNotFound)?; // TODO: Fix this error + + txs_to_be_sent.push(raw_signed_tx); + current_searching_txid = funding_txid; + } + + // Handle the case where no transaction was found in 25 iterations + if !found_txid { + return Err(BridgeError::KickoffOutpointsNotFound); // TODO: Fix this error + } + // tracing::debug!("Found txs to be sent: {:?}", txs_to_be_sent); + + let mut slash_or_take_tx_handler = TransactionBuilder::create_slash_or_take_tx( + deposit_outpoint, + kickoff_utxo.clone(), + &self.signer.xonly_public_key, + self.idx, + &self.nofn_xonly_pk, + self.config.network, + ); + + let slash_or_take_utxo = UTXO { + outpoint: OutPoint { + txid: slash_or_take_tx_handler.tx.compute_txid(), + vout: 0, + }, + txout: slash_or_take_tx_handler.tx.output[0].clone(), + }; + + // tracing::debug!( + // "Created slash or take tx handler: {:#?}", + // slash_or_take_tx_handler + // ); + let nofn_sig = self + .db + .get_slash_or_take_sig(deposit_outpoint, kickoff_utxo.clone()) + .await? + .ok_or(BridgeError::KickoffOutpointsNotFound)?; // TODO: Fix this error + + // tracing::debug!("Found nofn sig: {:?}", nofn_sig); + + let our_sig = + self.signer + .sign_taproot_script_spend_tx_new(&mut slash_or_take_tx_handler, 0, 0)?; + // tracing::debug!("slash_or_take_tx_handler: {:#?}", slash_or_take_tx_handler); + handle_taproot_witness_new( + &mut slash_or_take_tx_handler, + &[our_sig.as_ref(), nofn_sig.as_ref()], + 0, + Some(0), + )?; + + txs_to_be_sent.push(slash_or_take_tx_handler.tx.raw_hex()); + + tracing::debug!( + "Found txs to be sent with slash_or_take_tx: {:?}", + txs_to_be_sent + ); + + let move_tx_handler = TransactionBuilder::create_move_tx( + deposit_outpoint, + &EVMAddress([0u8; 20]), + &Address::p2tr( + &utils::SECP, + *utils::UNSPENDABLE_XONLY_PUBKEY, + None, + self.config.network, + ) + .as_unchecked(), + &self.nofn_xonly_pk, + self.config.network, + ); + let bridge_fund_outpoint = OutPoint { + txid: move_tx_handler.tx.compute_txid(), + vout: 0, + }; + + let mut operator_takes_tx = TransactionBuilder::create_operator_takes_tx( + bridge_fund_outpoint, + slash_or_take_utxo, + &self.signer.xonly_public_key, + &self.nofn_xonly_pk, + self.config.network, + ); + + let operator_takes_nofn_sig = self + .db + .get_operator_take_sig(deposit_outpoint, kickoff_utxo) + .await? + .ok_or(BridgeError::KickoffOutpointsNotFound)?; // TODO: Fix this error + tracing::debug!("Operator Found nofn sig: {:?}", operator_takes_nofn_sig); + + let our_sig = self + .signer + .sign_taproot_script_spend_tx_new(&mut operator_takes_tx, 1, 0)?; + + handle_taproot_witness_new( + &mut operator_takes_tx, + &[operator_takes_nofn_sig.as_ref()], + 0, + None, + )?; + handle_taproot_witness_new(&mut operator_takes_tx, &[our_sig.as_ref()], 1, Some(0))?; + + txs_to_be_sent.push(operator_takes_tx.tx.raw_hex()); + + // let input_0_sighash = Actor::convert_tx_to_sighash_pubkey_spend(&mut operator_takes_tx, 0)?; + // let input_0_message = Message::from_digest_slice(input_0_sighash.as_byte_array())?; + // tracing::debug!("Trying to verify signatures for operator_takes_tx"); + // let res_0 = utils::SECP.verify_schnorr( + // &operator_takes_nofn_sig, + // &input_0_message, + // &self.nofn_xonly_pk, + // )?; + // tracing::debug!("Signature verified successfully for input 0!"); + + // let input_1_sighash = + // Actor::convert_tx_to_sighash_script_spend(&mut operator_takes_tx, 1, 0)?; + // let input_1_message = Message::from_digest_slice(input_1_sighash.as_byte_array())?; + // let res_1 = utils::SECP.verify_schnorr( + // &our_sig, + // &input_1_message, + // &self.signer.xonly_public_key, + // )?; + // tracing::debug!("Signature verified successfully for input 1!"); + + // tracing::debug!( + // "Found txs to be sent with operator_takes_tx: {:?}", + // txs_to_be_sent + // ); + // let kickoff_txid = self + // .rpc + // .send_raw_transaction(&deserialize_hex(&txs_to_be_sent[0])?)?; + // tracing::debug!("Kickoff txid: {:?}", kickoff_txid); + // let slash_or_take_txid = self + // .rpc + // .send_raw_transaction(&deserialize_hex(&txs_to_be_sent[1])?)?; + // tracing::debug!("Slash or take txid: {:?}", slash_or_take_txid); + // let operator_takes_tx: Transaction = deserialize_hex(&txs_to_be_sent[2])?; + // tracing::debug!("Operator takes tx: {:#?}", operator_takes_tx); + // let operator_takes_txid = self.rpc.send_raw_transaction(&operator_takes_tx)?; + // tracing::debug!("Operator takes txid: {:?}", operator_takes_txid); + Ok(txs_to_be_sent) } } @@ -335,9 +510,9 @@ where async fn withdrawal_proved_on_citrea_rpc( &self, withdrawal_idx: usize, - kickoff_merkle_root: [u8; 32], - ) -> Result<(), BridgeError> { - self.withdrawal_proved_on_citrea(withdrawal_idx, kickoff_merkle_root) + deposit_outpoint: OutPoint, + ) -> Result, BridgeError> { + self.withdrawal_proved_on_citrea(withdrawal_idx, deposit_outpoint) .await } } diff --git a/core/src/servers.rs b/core/src/servers.rs index 8c5c658f..8499fa7a 100644 --- a/core/src/servers.rs +++ b/core/src/servers.rs @@ -31,13 +31,6 @@ pub async fn create_verifier_server( where R: RpcApiWrapper, { - let _ = Database::create_database(config.clone(), &config.db_name).await?; - let database = Database::new(config.clone()).await.unwrap(); - database - .run_sql_file("../scripts/schema.sql") - .await - .unwrap(); - database.close().await; let server = match Server::builder() .build(format!("{}:{}", config.host, config.port)) .await @@ -69,14 +62,6 @@ pub async fn create_operator_server( where R: RpcApiWrapper, { - let _ = Database::create_database(config.clone(), &config.db_name).await?; - let database = Database::new(config.clone()).await.unwrap(); - database - .run_sql_file("../scripts/schema.sql") - .await - .unwrap(); - database.close().await; - let operator = Operator::new(config.clone(), rpc).await?; let server = match Server::builder() @@ -133,21 +118,37 @@ pub async fn create_verifiers_and_operators( async move { let config_with_new_db = create_test_config_with_thread_name!(config_name, Some(&i.to_string())); - create_verifier_server( + let verifier = create_verifier_server( BridgeConfig { secret_key: *sk, - port: 0, // Use the index to calculate the port - ..config_with_new_db + port: 0, + ..config_with_new_db.clone() }, rpc, ) .await + .unwrap(); + Ok::< + ( + (HttpClient, ServerHandle, std::net::SocketAddr), + BridgeConfig, + ), + BridgeError, + >((verifier, config_with_new_db)) } }) .collect::>(); - let verifier_endpoints = futures::future::try_join_all(verifier_futures) + let verifier_results = futures::future::try_join_all(verifier_futures) .await .unwrap(); + let verifier_endpoints = verifier_results + .iter() + .map(|(v, _)| v.clone()) + .collect::>(); + let verifier_configs = verifier_results + .iter() + .map(|(_, c)| c.clone()) + .collect::>(); let all_operators_secret_keys = config.all_operators_secret_keys.clone().unwrap_or_else(|| { panic!("All secret keys of the operators are required for testing"); @@ -157,16 +158,15 @@ pub async fn create_verifiers_and_operators( .iter() .enumerate() .map(|(i, sk)| { - let i = (i + 1000).to_string(); + // let i_str = (i + 1000).to_string(); let rpc = rpc.clone(); + let verifier_config = verifier_configs[i].clone(); async move { - let config_with_new_db = - create_test_config_with_thread_name!(config_name, Some(&i.to_string())); create_operator_server( BridgeConfig { secret_key: *sk, - port: 0, // Use the index to calculate the port - ..config_with_new_db + port: 0, + ..verifier_config }, rpc, ) diff --git a/core/src/traits/rpc.rs b/core/src/traits/rpc.rs index 29744cb4..c3f37e21 100644 --- a/core/src/traits/rpc.rs +++ b/core/src/traits/rpc.rs @@ -81,12 +81,17 @@ pub trait OperatorRpc { ) -> Result, BridgeError>; #[method(name = "withdrawal_proved_on_citrea")] + /// 1- Calculate move_txid, check if the withdrawal idx matches the move_txid + /// 2- Check if it is really proved on citrea + /// 3- If it is, send operator_take_txs async fn withdrawal_proved_on_citrea_rpc( &self, withdrawal_idx: usize, - kickoff_merkle_root: [u8; 32], - ) -> Result<(), BridgeError>; + deposit_outpoint: OutPoint, + ) -> Result, BridgeError>; // #[method(name = "operator_take_sendable")] // async fn operator_take_sendable_rpc(&self, withdrawal_idx: usize) -> Result<(), BridgeError>; } + +// #[rpc(client, server, namespace = "aggregator")] diff --git a/core/src/transaction_builder.rs b/core/src/transaction_builder.rs index 63986994..305d3466 100644 --- a/core/src/transaction_builder.rs +++ b/core/src/transaction_builder.rs @@ -36,6 +36,7 @@ pub struct TransactionBuilder { pub const MOVE_TX_MIN_RELAY_FEE: u64 = 305; pub const SLASH_OR_TAKE_TX_MIN_RELAY_FEE: u64 = 305; pub const WITHDRAWAL_TX_MIN_RELAY_FEE: u64 = 305; +pub const OPERATOR_TAKES_TX_MIN_RELAY_FEE: u64 = 305; impl TransactionBuilder { /// Creates a new `TransactionBuilder`. @@ -191,8 +192,8 @@ impl TransactionBuilder { scripts: vec![deposit_script], taproot_spend_infos: vec![deposit_taproot_spend_info], }; - println!("MOVE_TX: {:?}", move_tx_handler); - println!("MOVE_TXID: {:?}", move_tx_handler.tx.compute_txid()); + // println!("MOVE_TX: {:?}", move_tx_handler); + // println!("MOVE_TXID: {:?}", move_tx_handler.tx.compute_txid()); move_tx_handler } @@ -203,6 +204,7 @@ impl TransactionBuilder { operator_xonly_pk: &XOnlyPublicKey, network: bitcoin::Network, ) -> TxHandler { + let kickoff_tx_min_relay_fee = 197; // TODO: Change this with variable kickoff utxos per txs let tx_ins = TransactionBuilder::create_tx_ins(vec![funding_utxo.outpoint]); let musig2_and_operator_script = script_builder::create_musig2_and_operator_multisig_script( nofn_xonly_pk, @@ -216,7 +218,8 @@ impl TransactionBuilder { let operator_address = Address::p2tr(&utils::SECP, *operator_xonly_pk, None, network); let change_amount = funding_utxo.txout.value - Amount::from_sat(100_000) - - script_builder::anyone_can_spend_txout().value; + - script_builder::anyone_can_spend_txout().value + - Amount::from_sat(kickoff_tx_min_relay_fee); let tx_outs = TransactionBuilder::create_tx_outs(vec![ ( @@ -267,7 +270,16 @@ impl TransactionBuilder { let (kickoff_utxo_address, kickoff_utxo_spend_info) = Self::create_kickoff_address(nofn_xonly_pk, operator_xonly_pk, network); - + tracing::debug!( + "kickoff_utxo_script_pubkey: {:?}", + kickoff_utxo_address.script_pubkey() + ); + tracing::debug!("kickoff_utxo_spend_info: {:?}", kickoff_utxo_spend_info); + tracing::debug!("kickoff_utxooo: {:?}", kickoff_utxo); + let musig2_and_operator_script = script_builder::create_musig2_and_operator_multisig_script( + nofn_xonly_pk, + operator_xonly_pk, + ); // Sanity check assert!(kickoff_utxo_address.script_pubkey() == kickoff_utxo.txout.script_pubkey); let ins = Self::create_tx_ins(vec![kickoff_utxo.outpoint]); @@ -298,12 +310,12 @@ impl TransactionBuilder { ), script_pubkey: slash_or_take_address.script_pubkey(), }, - op_return_txout, script_builder::anyone_can_spend_txout(), + op_return_txout, ]; let tx = TransactionBuilder::create_btc_tx(ins, outs); let prevouts = vec![kickoff_utxo.txout.clone()]; - let scripts = vec![vec![relative_timelock_script.clone()]]; + let scripts = vec![vec![musig2_and_operator_script]]; TxHandler { tx, prevouts, @@ -320,10 +332,11 @@ impl TransactionBuilder { network: bitcoin::Network, ) -> TxHandler { let operator_address = Address::p2tr(&utils::SECP, *operator_xonly_pk, None, network); - let ins = TransactionBuilder::create_tx_ins(vec![ - bridge_fund_outpoint, - slash_or_take_utxo.outpoint, - ]); + let mut ins = TransactionBuilder::create_tx_ins(vec![bridge_fund_outpoint]); + ins.extend(TransactionBuilder::create_tx_ins_with_sequence( + vec![slash_or_take_utxo.outpoint], + OPERATOR_TAKES_AFTER as u16, + )); let (musig2_address, musig2_spend_info) = TransactionBuilder::create_musig2_address(*nofn_xonly_pk, network); @@ -331,7 +344,7 @@ impl TransactionBuilder { let relative_timelock_script = script_builder::generate_relative_timelock_script( operator_xonly_pk, OPERATOR_TAKES_AFTER, - ); // TODO: Change this 200 to a config constant + ); let (slash_or_take_address, slash_or_take_spend_info) = TransactionBuilder::create_taproot_address( &[relative_timelock_script.clone()], @@ -347,6 +360,7 @@ impl TransactionBuilder { value: Amount::from_sat(slash_or_take_utxo.txout.value.to_sat()) + Amount::from_sat(BRIDGE_AMOUNT_SATS) - Amount::from_sat(MOVE_TX_MIN_RELAY_FEE) + - Amount::from_sat(OPERATOR_TAKES_TX_MIN_RELAY_FEE) - script_builder::anyone_can_spend_txout().value - script_builder::anyone_can_spend_txout().value, script_pubkey: operator_address.script_pubkey(), diff --git a/core/src/user.rs b/core/src/user.rs index e61c6ba1..63357299 100644 --- a/core/src/user.rs +++ b/core/src/user.rs @@ -2,7 +2,7 @@ use crate::actor::Actor; use crate::config::BridgeConfig; use crate::errors::BridgeError; use crate::extended_rpc::ExtendedRpc; -use crate::musig2::{self}; +use crate::musig2::{self, AggregateFromPublicKeys}; use crate::transaction_builder::TransactionBuilder; use crate::{EVMAddress, UTXO}; use ::musig2::secp::Point; @@ -30,11 +30,11 @@ where pub fn new(rpc: ExtendedRpc, sk: SecretKey, config: BridgeConfig) -> Self { let signer = Actor::new(sk, config.network); - let key_agg_context = - musig2::create_key_agg_ctx(config.verifiers_public_keys.clone(), None).unwrap(); - let agg_point: Point = key_agg_context.aggregated_pubkey_untweaked(); - let nofn_xonly_pk = - secp256k1::XOnlyPublicKey::from_slice(&agg_point.serialize_xonly()).unwrap(); + let nofn_xonly_pk = secp256k1::XOnlyPublicKey::from_musig2_pks( + config.verifiers_public_keys.clone(), + None, + false, + ); User { rpc, diff --git a/core/src/utils.rs b/core/src/utils.rs index 8f31019e..a78a4e26 100644 --- a/core/src/utils.rs +++ b/core/src/utils.rs @@ -1,7 +1,7 @@ use crate::actor::Actor; use crate::errors::BridgeError; use crate::musig2::aggregate_partial_signatures; -use crate::musig2::create_key_agg_ctx; +use crate::musig2::AggregateFromPublicKeys; use crate::musig2::MuSigAggNonce; use crate::transaction_builder::TransactionBuilder; use crate::transaction_builder::TxHandler; @@ -21,6 +21,7 @@ use bitcoin::Amount; use bitcoin::OutPoint; use bitcoin::ScriptBuf; use bitcoin::XOnlyPublicKey; +use bitcoincore_rpc::RawTx; use clementine_circuits::sha256_hash; use clementine_circuits::HashType; use hex; @@ -69,7 +70,7 @@ pub fn handle_taproot_witness_new>( tx: &mut TxHandler, witness_elements: &[T], txin_index: usize, - script_index: usize, + script_index: Option, ) -> Result<(), BridgeError> { let mut sighash_cache = SighashCache::new(tx.tx.borrow_mut()); @@ -80,17 +81,15 @@ pub fn handle_taproot_witness_new>( witness_elements .iter() .for_each(|element| witness.push(element)); + if let Some(index) = script_index { + let script = &tx.scripts[txin_index][index]; + let spend_control_block = tx.taproot_spend_infos[txin_index] + .control_block(&(script.clone(), LeafVersion::TapScript)) + .ok_or(BridgeError::ControlBlockError)?; - let spend_control_block = tx.taproot_spend_infos[txin_index] - .control_block(&( - tx.scripts[txin_index][script_index].clone(), - LeafVersion::TapScript, - )) - .ok_or(BridgeError::ControlBlockError)?; - - witness.push(tx.scripts[txin_index][script_index].clone()); - witness.push(&spend_control_block.serialize()); - + witness.push(script.clone()); + witness.push(spend_control_block.serialize()); + } Ok(()) } @@ -104,11 +103,8 @@ pub fn aggregate_slash_or_take_partial_sigs( partial_sigs: Vec<[u8; 32]>, network: bitcoin::Network, ) -> Result<[u8; 64], BridgeError> { - let key_agg_ctx = create_key_agg_ctx(verifiers_pks.clone(), None)?; - let musig_agg_pubkey: musig2::secp256k1::PublicKey = key_agg_ctx.aggregated_pubkey(); - let (musig_agg_xonly_pubkey, _) = musig_agg_pubkey.x_only_public_key(); let musig_agg_xonly_pubkey_wrapped = - bitcoin::XOnlyPublicKey::from_slice(&musig_agg_xonly_pubkey.serialize()).unwrap(); + secp256k1::XOnlyPublicKey::from_musig2_pks(verifiers_pks.clone(), None, false); let mut tx = TransactionBuilder::create_slash_or_take_tx( deposit_outpoint, kickoff_utxo, @@ -122,14 +118,24 @@ pub fn aggregate_slash_or_take_partial_sigs( let message: [u8; 32] = Actor::convert_tx_to_sighash_script_spend(&mut tx, 0, 0) .unwrap() .to_byte_array(); + tracing::debug!("aggregate SLASH_OR_TAKE_TX message: {:?}", message); let final_sig: [u8; 64] = aggregate_partial_signatures( verifiers_pks.clone(), None, + false, agg_nonce, partial_sigs, message, )?; - + tracing::debug!("aggregate SLASH_OR_TAKE_TX final_sig: {:?}", final_sig); + tracing::debug!( + "aggregate SLASH_OR_TAKE_TX for verifiers: {:?}", + verifiers_pks + ); + tracing::debug!( + "aggregate SLASH_OR_TAKE_TX for operator: {:?}", + operator_xonly_pk + ); Ok(final_sig) } @@ -143,11 +149,8 @@ pub fn aggregate_operator_takes_partial_sigs( partial_sigs: Vec<[u8; 32]>, network: bitcoin::Network, ) -> Result<[u8; 64], BridgeError> { - let key_agg_ctx = create_key_agg_ctx(verifiers_pks.clone(), None)?; - let musig_agg_pubkey: musig2::secp256k1::PublicKey = key_agg_ctx.aggregated_pubkey(); - let (musig_agg_xonly_pubkey, _) = musig_agg_pubkey.x_only_public_key(); let nofn_xonly_pk = - bitcoin::XOnlyPublicKey::from_slice(&musig_agg_xonly_pubkey.serialize()).unwrap(); + secp256k1::XOnlyPublicKey::from_musig2_pks(verifiers_pks.clone(), None, false); let move_tx_handler = TransactionBuilder::create_move_tx( deposit_outpoint, @@ -175,29 +178,32 @@ pub fn aggregate_operator_takes_partial_sigs( }, txout: slash_or_take_tx_handler.tx.output[0].clone(), }; - let mut tx = TransactionBuilder::create_operator_takes_tx( + let mut tx_handler = TransactionBuilder::create_operator_takes_tx( bridge_fund_outpoint, slash_or_take_utxo, operator_xonly_pk, &nofn_xonly_pk, network, ); - tracing::debug!("OPERATOR_TAKES_TX: {:?}", tx); - tracing::debug!("OPERATOR_TAKES_TX weight: {:?}", tx.tx.weight()); - let message: [u8; 32] = Actor::convert_tx_to_sighash_pubkey_spend(&mut tx, 0) + tracing::debug!( + "OPERATOR_TAKES_TX with operator_idx:{:?} {:?}", + operator_idx, + tx_handler.tx + ); + tracing::debug!("OPERATOR_TAKES_TX_HEX: {:?}", tx_handler.tx.raw_hex()); + tracing::debug!("OPERATOR_TAKES_TX weight: {:?}", tx_handler.tx.weight()); + let message: [u8; 32] = Actor::convert_tx_to_sighash_pubkey_spend(&mut tx_handler, 0) .unwrap() .to_byte_array(); - // println!("Message: {:?}", message); - // println!("Partial sigs: {:?}", partial_sigs); - // println!("Agg nonce: {:?}", agg_nonce); let final_sig: [u8; 64] = aggregate_partial_signatures( verifiers_pks.clone(), None, + true, agg_nonce, partial_sigs, message, )?; - + tracing::debug!("OPERATOR_TAKES_TX final_sig: {:?}", final_sig); Ok(final_sig) } @@ -210,11 +216,8 @@ pub fn aggregate_move_partial_sigs( partial_sigs: Vec<[u8; 32]>, network: bitcoin::Network, ) -> Result<[u8; 64], BridgeError> { - let key_agg_ctx = create_key_agg_ctx(verifiers_pks.clone(), None)?; - let musig_agg_pubkey: musig2::secp256k1::PublicKey = key_agg_ctx.aggregated_pubkey(); - let (musig_agg_xonly_pubkey, _) = musig_agg_pubkey.x_only_public_key(); let musig_agg_xonly_pubkey_wrapped = - bitcoin::XOnlyPublicKey::from_slice(&musig_agg_xonly_pubkey.serialize()).unwrap(); + secp256k1::XOnlyPublicKey::from_musig2_pks(verifiers_pks.clone(), None, false); let mut tx = TransactionBuilder::create_move_tx( deposit_outpoint, evm_address, @@ -222,14 +225,15 @@ pub fn aggregate_move_partial_sigs( &musig_agg_xonly_pubkey_wrapped, network, ); - println!("MOVE_TX: {:?}", tx); - println!("MOVE_TXID: {:?}", tx.tx.compute_txid()); + // println!("MOVE_TX: {:?}", tx); + // println!("MOVE_TXID: {:?}", tx.tx.compute_txid()); let message: [u8; 32] = Actor::convert_tx_to_sighash_script_spend(&mut tx, 0, 0) .unwrap() .to_byte_array(); let final_sig: [u8; 64] = aggregate_partial_signatures( verifiers_pks.clone(), None, + false, agg_nonce, partial_sigs, message, diff --git a/core/src/verifier.rs b/core/src/verifier.rs index a65ad255..e72eb86e 100644 --- a/core/src/verifier.rs +++ b/core/src/verifier.rs @@ -3,7 +3,9 @@ use crate::config::BridgeConfig; use crate::database::verifier::VerifierDB; use crate::errors::BridgeError; use crate::extended_rpc::ExtendedRpc; -use crate::musig2::{self, MuSigAggNonce, MuSigPartialSignature, MuSigPubNonce}; +use crate::musig2::{ + self, AggregateFromPublicKeys, MuSigAggNonce, MuSigPartialSignature, MuSigPubNonce, +}; use crate::traits::rpc::VerifierRpcServer; use crate::transaction_builder::{TransactionBuilder, TxHandler}; use crate::{utils, EVMAddress, UTXO}; @@ -13,6 +15,7 @@ use bitcoin::hashes::Hash; use bitcoin::Address; use bitcoin::{secp256k1, OutPoint}; use bitcoin_mock_rpc::RpcApiWrapper; +use bitcoincore_rpc::RawTx; use clementine_circuits::constants::BRIDGE_AMOUNT_SATS; use clementine_circuits::sha256_hash; use jsonrpsee::core::async_trait; @@ -47,10 +50,12 @@ where let db = VerifierDB::new(config.clone()).await; - let key_agg_context = - musig2::create_key_agg_ctx(config.verifiers_public_keys.clone(), None)?; - let agg_point: Point = key_agg_context.aggregated_pubkey_untweaked(); - let nofn_xonly_pk = secp256k1::XOnlyPublicKey::from_slice(&agg_point.serialize_xonly())?; + let nofn_xonly_pk = secp256k1::XOnlyPublicKey::from_musig2_pks( + config.verifiers_public_keys.clone(), + None, + false, + ); + let operator_xonly_pks = config.operators_xonly_pks.clone(); Ok(Verifier { @@ -211,6 +216,7 @@ where musig2::partial_sign( self.config.verifiers_public_keys.clone(), None, + false, *sec_nonce, agg_nonce.clone(), &self.signer.keypair, @@ -320,7 +326,15 @@ where let slash_or_take_sighash = Actor::convert_tx_to_sighash_script_spend(&mut slash_or_take_tx_handler, 0, 0) .unwrap(); - + tracing::debug!( + "Verify SLASH_OR_TAKE_TX message: {:?}", + slash_or_take_sighash + ); + tracing::debug!("Verify SLASH_OR_TAKE_SIG: {:?}", slash_or_take_sigs[index]); + tracing::debug!( + "Verify SLASH_OR_TAKE_TX operator xonly_pk: {:?}", + self.operator_xonly_pks[index] + ); utils::SECP .verify_schnorr( &slash_or_take_sigs[index], @@ -349,13 +363,25 @@ where .to_byte_array() }) .collect::>(); - println!("Operator takes sighashes: {:?}", operator_takes_sighashes); + + for (index, kickoff_utxo) in kickoff_utxos.iter().enumerate() { + self.db + .save_slash_or_take_sig( + deposit_outpoint, + kickoff_utxo.clone(), + slash_or_take_sigs[index], + ) + .await + .unwrap(); + } + + // println!("Operator takes sighashes: {:?}", operator_takes_sighashes); let nonces = self .db .save_sighashes_and_get_nonces(deposit_outpoint, 1, &operator_takes_sighashes) .await? .ok_or(BridgeError::NoncesNotFound)?; - println!("Nonces: {:?}", nonces); + // println!("Nonces: {:?}", nonces); // now iterate over nonces and sighashes and sign the operator_takes_txs let operator_takes_partial_sigs = operator_takes_sighashes .iter() @@ -364,6 +390,7 @@ where musig2::partial_sign( self.config.verifiers_public_keys.clone(), None, + true, *sec_nonce, agg_nonce.clone(), &self.signer.keypair, @@ -371,21 +398,21 @@ where ) }) .collect::>(); - println!( - "Operator takes partial sigs: {:?}", - operator_takes_partial_sigs - ); + // println!( + // "Operator takes partial sigs: {:?}", + // operator_takes_partial_sigs + // ); Ok(operator_takes_partial_sigs) } /// verify the operator_take_sigs - /// sign move_commit_tx and move_reveal_tx + /// sign move_tx async fn operator_take_txs_signed( &self, deposit_outpoint: OutPoint, operator_take_sigs: Vec, ) -> Result { - println!("Operator take signed: {:?}", operator_take_sigs); + // println!("Operator take signed: {:?}", operator_take_sigs); let (kickoff_utxos, mut move_tx_handler, bridge_fund_outpoint) = self.create_deposit_details(deposit_outpoint).await?; @@ -415,6 +442,11 @@ where &self.nofn_xonly_pk, self.config.network, ); + tracing::debug!( + "INDEXXX: {:?} Operator takes tx hex: {:?}", + index, + operator_takes_tx.tx.raw_hex() + ); let sig_hash = Actor::convert_tx_to_sighash_pubkey_spend(&mut operator_takes_tx, 0).unwrap(); @@ -428,8 +460,19 @@ where ) .unwrap(); }); - println!("MOVE_TX: {:?}", move_tx_handler); - println!("MOVE_TXID: {:?}", move_tx_handler.tx.compute_txid()); + + for (index, kickoff_utxo) in kickoff_utxos.iter().enumerate() { + self.db + .save_operator_take_sig( + deposit_outpoint, + kickoff_utxo.clone(), + operator_take_sigs[index], + ) + .await + .unwrap(); + } + // println!("MOVE_TX: {:?}", move_tx_handler); + // println!("MOVE_TXID: {:?}", move_tx_handler.tx.compute_txid()); let move_tx_sighash = Actor::convert_tx_to_sighash_script_spend(&mut move_tx_handler, 0, 0)?; // TODO: This should be musig @@ -445,6 +488,7 @@ where let move_tx_sig = musig2::partial_sign( self.config.verifiers_public_keys.clone(), None, + false, nonces[0].0, nonces[0].1.clone(), &self.signer.keypair, diff --git a/core/tests/common.rs b/core/tests/common.rs new file mode 100644 index 00000000..27651c91 --- /dev/null +++ b/core/tests/common.rs @@ -0,0 +1,260 @@ +// //! # Common utilities for tests + +use bitcoin::Address; +use bitcoin::OutPoint; +use bitcoin::Transaction; +use clementine_circuits::constants::BRIDGE_AMOUNT_SATS; +use clementine_circuits::constants::OPERATOR_TAKES_AFTER; +use clementine_core::actor::Actor; +use clementine_core::config::BridgeConfig; +use clementine_core::database::common::Database; +use clementine_core::errors::BridgeError; +use clementine_core::extended_rpc::ExtendedRpc; +use clementine_core::mock::common; +use clementine_core::musig2::aggregate_nonces; +use clementine_core::musig2::AggregateFromPublicKeys; +use clementine_core::servers::*; +use clementine_core::traits::rpc::OperatorRpcClient; +use clementine_core::traits::rpc::VerifierRpcClient; +use clementine_core::transaction_builder::TransactionBuilder; +use clementine_core::user::User; +use clementine_core::utils::aggregate_move_partial_sigs; +use clementine_core::utils::aggregate_operator_takes_partial_sigs; +use clementine_core::utils::aggregate_slash_or_take_partial_sigs; +use clementine_core::utils::handle_taproot_witness_new; +use clementine_core::EVMAddress; +use clementine_core::UTXO; +use clementine_core::{ + create_extended_rpc, create_test_config, create_test_config_with_thread_name, +}; +use crypto_bigint::rand_core::OsRng; +use jsonrpsee::http_client::HttpClient; +use jsonrpsee::server::ServerHandle; +use secp256k1::rand::Rng; +use secp256k1::SecretKey; +use std::net::SocketAddr; +use std::thread; + +pub async fn run_single_deposit( + test_config_name: &str, +) -> Result< + ( + Vec<(HttpClient, ServerHandle, SocketAddr)>, + Vec<(HttpClient, ServerHandle, SocketAddr)>, + BridgeConfig, + OutPoint, + ), + BridgeError, +> { + let mut config = create_test_config_with_thread_name!(test_config_name); + let rpc = create_extended_rpc!(config); + + let (verifiers, operators) = create_verifiers_and_operators("test_config_flow.toml").await; + + // println!("Operators: {:#?}", operators); + // println!("Verifiers: {:#?}", verifiers); + + let secret_key = secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng()); + + let signer_address = Actor::new(secret_key, config.network.clone()) + .address + .as_unchecked() + .clone(); + let user = User::new(rpc.clone(), secret_key, config.clone()); + + let evm_address = EVMAddress([1u8; 20]); + let deposit_address = user.get_deposit_address(evm_address).unwrap(); + let deposit_outpoint = rpc + .send_to_address(&deposit_address, BRIDGE_AMOUNT_SATS) + .unwrap(); + + rpc.mine_blocks(18).unwrap(); + + // for every verifier, we call new_deposit + // aggregate nonces + let mut pub_nonces = Vec::new(); + + for (client, _, _) in verifiers.iter() { + let musig_pub_nonces = client + .verifier_new_deposit_rpc(deposit_outpoint, signer_address.clone(), evm_address) + .await + .unwrap(); + + // tracing::info!("Musig Pub Nonces: {:?}", musig_pub_nonces); + + pub_nonces.push(musig_pub_nonces); + } + + let mut agg_nonces = Vec::new(); + for i in 0..pub_nonces[0].len() { + let agg_nonce = aggregate_nonces( + pub_nonces + .iter() + .map(|v| v.get(i).cloned().unwrap()) + .collect::>(), + ); + + agg_nonces.push(agg_nonce); + } + + // call operators' new_deposit + let mut kickoff_utxos = Vec::new(); + let mut signatures = Vec::new(); + + for (i, (client, _, _)) in operators.iter().enumerate() { + // Send operators some bitcoin so that they can afford the kickoff tx + let secp = bitcoin::secp256k1::Secp256k1::new(); + let operator_internal_xonly_pk = config.operators_xonly_pks.get(i).unwrap(); + let operator_address = Address::p2tr( + &secp, + *operator_internal_xonly_pk, + None, + config.network.clone(), + ); + let operator_funding_outpoint = rpc + .send_to_address(&operator_address, 2 * BRIDGE_AMOUNT_SATS) + .unwrap(); + let operator_funding_txout = rpc + .get_txout_from_outpoint(&operator_funding_outpoint) + .unwrap(); + let operator_funding_utxo = UTXO { + outpoint: operator_funding_outpoint, + txout: operator_funding_txout, + }; + + tracing::debug!("Operator {:?} funding utxo: {:?}", i, operator_funding_utxo); + client + .set_funding_utxo_rpc(operator_funding_utxo) + .await + .unwrap(); + + // Create deposit kickoff transaction + let (kickoff_utxo, signature) = client + .new_deposit_rpc(deposit_outpoint, signer_address.clone(), evm_address) + .await + .unwrap(); + + kickoff_utxos.push(kickoff_utxo); + signatures.push(signature); + } + + tracing::debug!("Now the verifiers sequence starts"); + let mut slash_or_take_partial_sigs = Vec::new(); + + for (_i, (client, ..)) in verifiers.iter().enumerate() { + let (partial_sigs, _) = client + .operator_kickoffs_generated_rpc( + deposit_outpoint, + kickoff_utxos.clone(), + signatures.clone(), + agg_nonces.clone(), + ) + .await + .unwrap(); + + slash_or_take_partial_sigs.push(partial_sigs); + } + // tracing::debug!( + // "Slash or take partial sigs: {:#?}", + // slash_or_take_partial_sigs + // ); + let mut slash_or_take_sigs = Vec::new(); + for i in 0..slash_or_take_partial_sigs[0].len() { + let agg_sig = aggregate_slash_or_take_partial_sigs( + deposit_outpoint, + kickoff_utxos[i].clone(), + config.verifiers_public_keys.clone(), + config.operators_xonly_pks[i].clone(), + i, + &agg_nonces[i + 1 + config.operators_xonly_pks.len()].clone(), + slash_or_take_partial_sigs + .iter() + .map(|v| v.get(i).cloned().unwrap()) + .collect::>(), + config.network.clone(), + )?; + + slash_or_take_sigs.push(secp256k1::schnorr::Signature::from_slice(&agg_sig)?); + } + + // tracing::debug!("Slash or take sigs: {:#?}", slash_or_take_sigs); + // call burn_txs_signed_rpc + let mut operator_take_partial_sigs: Vec> = Vec::new(); + for (_i, (client, _, _)) in verifiers.iter().enumerate() { + let partial_sigs = client + .burn_txs_signed_rpc(deposit_outpoint, vec![], slash_or_take_sigs.clone()) + .await + .unwrap(); + operator_take_partial_sigs.push(partial_sigs); + } + // tracing::debug!( + // "Operator take partial sigs: {:#?}", + // operator_take_partial_sigs + // ); + let mut operator_take_sigs = Vec::new(); + for i in 0..operator_take_partial_sigs.len() { + let agg_sig = aggregate_operator_takes_partial_sigs( + deposit_outpoint, + kickoff_utxos[i].clone(), + &config.operators_xonly_pks[i].clone(), + i, + config.verifiers_public_keys.clone(), + &agg_nonces[i + 1].clone(), + operator_take_partial_sigs + .iter() + .map(|v| v[i].clone()) + .collect(), + config.network.clone(), + )?; + + operator_take_sigs.push(secp256k1::schnorr::Signature::from_slice(&agg_sig)?); + } + // tracing::debug!("Operator take sigs: {:#?}", operator_take_sigs); + // call operator_take_txs_signed_rpc + let mut move_tx_partial_sigs = Vec::new(); + for (client, _, _) in verifiers.iter() { + let move_tx_partial_sig = client + .operator_take_txs_signed_rpc(deposit_outpoint, operator_take_sigs.clone()) + .await + .unwrap(); + move_tx_partial_sigs.push(move_tx_partial_sig); + } + + // tracing::debug!("Move tx partial sigs: {:#?}", move_tx_partial_sigs); + + // aggreagte move_tx_partial_sigs + let agg_move_tx_final_sig = aggregate_move_partial_sigs( + deposit_outpoint, + &evm_address, + &signer_address, + config.verifiers_public_keys.clone(), + &agg_nonces[0].clone(), + move_tx_partial_sigs, + config.network.clone(), + )?; + + let move_tx_sig = secp256k1::schnorr::Signature::from_slice(&agg_move_tx_final_sig)?; + + let nofn_xonly_pk = secp256k1::XOnlyPublicKey::from_musig2_pks( + config.verifiers_public_keys.clone(), + None, + false, + ); + + let mut move_tx_handler = TransactionBuilder::create_move_tx( + deposit_outpoint, + &evm_address, + &signer_address, + &nofn_xonly_pk, + config.network, + ); + let mut move_tx_witness_elements = Vec::new(); + move_tx_witness_elements.push(move_tx_sig.serialize().to_vec()); + handle_taproot_witness_new(&mut move_tx_handler, &move_tx_witness_elements, 0, Some(0))?; + tracing::debug!("Move tx: {:#?}", move_tx_handler.tx); + // tracing::debug!("Move tx_hex: {:?}", move_tx_handler.tx.raw_hex()); + tracing::debug!("Move tx weight: {:?}", move_tx_handler.tx.weight()); + let move_txid = rpc.send_raw_transaction(&move_tx_handler.tx).unwrap(); + tracing::debug!("Move txid: {:?}", move_txid); + Ok((verifiers, operators, config, deposit_outpoint)) +} diff --git a/core/tests/flow.rs b/core/tests/flow.rs index 32aeeb0d..f30d020b 100644 --- a/core/tests/flow.rs +++ b/core/tests/flow.rs @@ -3,241 +3,67 @@ // //! This testss checks if basic deposit and withdraw operations are OK or not. use bitcoin::Address; -use clementine_circuits::constants::BRIDGE_AMOUNT_SATS; -use clementine_core::actor::Actor; -use clementine_core::database::common::Database; -use clementine_core::errors::BridgeError; +use clementine_circuits::constants::OPERATOR_TAKES_AFTER; use clementine_core::extended_rpc::ExtendedRpc; -use clementine_core::mock::common; -use clementine_core::musig2::aggregate_nonces; -use clementine_core::musig2::create_key_agg_ctx; -use clementine_core::servers::*; -use clementine_core::traits::rpc::OperatorRpcClient; -use clementine_core::traits::rpc::VerifierRpcClient; -use clementine_core::transaction_builder::TransactionBuilder; -use clementine_core::user::User; -use clementine_core::utils::aggregate_move_partial_sigs; -use clementine_core::utils::aggregate_operator_takes_partial_sigs; -use clementine_core::utils::aggregate_slash_or_take_partial_sigs; -use clementine_core::utils::handle_taproot_witness_new; -use clementine_core::EVMAddress; -use clementine_core::UTXO; use clementine_core::{ - create_extended_rpc, create_test_config, create_test_config_with_thread_name, + create_extended_rpc, errors::BridgeError, traits::rpc::OperatorRpcClient, user::User, }; -use std::thread; +use common::run_single_deposit; +use secp256k1::SecretKey; + +mod common; #[tokio::test] async fn test_deposit() -> Result<(), BridgeError> { - let mut config = create_test_config_with_thread_name!("test_config_flow.toml"); - let rpc = create_extended_rpc!(config); - - let (verifiers, operators) = create_verifiers_and_operators("test_config_flow.toml").await; - - // println!("Operators: {:#?}", operators); - // println!("Verifiers: {:#?}", verifiers); - - let secret_key = secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng()); - - let signer_address = Actor::new(secret_key, config.network.clone()) - .address - .as_unchecked() - .clone(); - let user = User::new(rpc.clone(), secret_key, config.clone()); - - let evm_address = EVMAddress([1u8; 20]); - let deposit_address = user.get_deposit_address(evm_address).unwrap(); - let deposit_outpoint = rpc - .send_to_address(&deposit_address, BRIDGE_AMOUNT_SATS) - .unwrap(); - - rpc.mine_blocks(18).unwrap(); - - // for every verifier, we call new_deposit - // aggregate nonces - let mut pub_nonces = Vec::new(); - - for (client, _, _) in verifiers.iter() { - let musig_pub_nonces = client - .verifier_new_deposit_rpc(deposit_outpoint, signer_address.clone(), evm_address) - .await - .unwrap(); - - // tracing::info!("Musig Pub Nonces: {:?}", musig_pub_nonces); - - pub_nonces.push(musig_pub_nonces); - } - - let mut agg_nonces = Vec::new(); - for i in 0..pub_nonces[0].len() { - let agg_nonce = aggregate_nonces( - pub_nonces - .iter() - .map(|v| v.get(i).cloned().unwrap()) - .collect::>(), - ); - - agg_nonces.push(agg_nonce); - } - - // call operators' new_deposit - let mut kickoff_utxos = Vec::new(); - let mut signatures = Vec::new(); - - for (i, (client, _, _)) in operators.iter().enumerate() { - // Send operators some bitcoin so that they can afford the kickoff tx - let secp = bitcoin::secp256k1::Secp256k1::new(); - let operator_internal_xonly_pk = config.operators_xonly_pks.get(i).unwrap(); - let operator_address = Address::p2tr( - &secp, - *operator_internal_xonly_pk, - None, - config.network.clone(), - ); - let operator_funding_outpoint = rpc - .send_to_address(&operator_address, 2 * BRIDGE_AMOUNT_SATS) - .unwrap(); - let operator_funding_txout = rpc - .get_txout_from_outpoint(&operator_funding_outpoint) - .unwrap(); - let operator_funding_utxo = UTXO { - outpoint: operator_funding_outpoint, - txout: operator_funding_txout, - }; - - tracing::debug!("Operator {:?} funding utxo: {:?}", i, operator_funding_utxo); - client - .set_funding_utxo_rpc(operator_funding_utxo) - .await - .unwrap(); - - // Create deposit kickoff transaction - let (kickoff_utxo, signature) = client - .new_deposit_rpc(deposit_outpoint, signer_address.clone(), evm_address) - .await - .unwrap(); - - kickoff_utxos.push(kickoff_utxo); - signatures.push(signature); + match run_single_deposit("test_config_flow.toml").await { + Ok((_, _, _, deposit_outpoint)) => { + // tracing::debug!("Verifiers: {:#?}", verifiers); + // tracing::debug!("Operators: {:#?}", operators); + tracing::debug!("Deposit outpoint: {:#?}", deposit_outpoint); + Ok(()) + } + Err(e) => { + tracing::error!("Error: {:?}", e); + Err(e) + } } +} - tracing::debug!("Now the verifiers sequence starts"); - let mut slash_or_take_partial_sigs = Vec::new(); - - for (_i, (client, ..)) in verifiers.iter().enumerate() { - let (partial_sigs, _) = client - .operator_kickoffs_generated_rpc( - deposit_outpoint, - kickoff_utxos.clone(), - signatures.clone(), - agg_nonces.clone(), - ) - .await - .unwrap(); +#[tokio::test] +async fn test_honest_operator_takes_refund() { + // let mut config = create_test_config_with_thread_name!("test_config_flow.toml"); + let (verifiers, operators, mut config, deposit_outpoint) = + run_single_deposit("test_config_flow.toml").await.unwrap(); + let rpc = create_extended_rpc!(config); - slash_or_take_partial_sigs.push(partial_sigs); - } - tracing::debug!( - "Slash or take partial sigs: {:#?}", - slash_or_take_partial_sigs + let secp = bitcoin::secp256k1::Secp256k1::new(); + let user_sk = SecretKey::from_slice(&[12u8; 32]).unwrap(); + let user = User::new(rpc.clone(), user_sk, config.clone()); + let withdrawal_address = Address::p2tr( + &secp, + user_sk.x_only_public_key(&secp).0, + None, + config.network, ); - let mut slash_or_take_sigs = Vec::new(); - for i in 0..slash_or_take_partial_sigs[0].len() { - let agg_sig = aggregate_slash_or_take_partial_sigs( - deposit_outpoint, - kickoff_utxos[i].clone(), - config.verifiers_public_keys.clone(), - config.operators_xonly_pks[i].clone(), - i, - &agg_nonces[i + 1 + config.operators_xonly_pks.len()].clone(), - slash_or_take_partial_sigs - .iter() - .map(|v| v.get(i).cloned().unwrap()) - .collect::>(), - config.network.clone(), - )?; - - slash_or_take_sigs.push(secp256k1::schnorr::Signature::from_slice(&agg_sig)?); - } - - // tracing::debug!("Slash or take sigs: {:#?}", slash_or_take_sigs); - // call burn_txs_signed_rpc - let mut operator_take_partial_sigs: Vec> = Vec::new(); - for (_i, (client, _, _)) in verifiers.iter().enumerate() { - let partial_sigs = client - .burn_txs_signed_rpc(deposit_outpoint, vec![], slash_or_take_sigs.clone()) - .await - .unwrap(); - operator_take_partial_sigs.push(partial_sigs); - } - // tracing::debug!( - // "Operator take partial sigs: {:#?}", - // operator_take_partial_sigs - // ); - let mut operator_take_sigs = Vec::new(); - for i in 0..operator_take_partial_sigs.len() { - let agg_sig = aggregate_operator_takes_partial_sigs( - deposit_outpoint, - kickoff_utxos[i].clone(), - &config.operators_xonly_pks[i].clone(), - i, - config.verifiers_public_keys.clone(), - &agg_nonces[i + 1].clone(), - operator_take_partial_sigs - .iter() - .map(|v| v[i].clone()) - .collect(), - config.network.clone(), - )?; + let (empty_utxo, withdrawal_tx_out, user_sig) = + user.generate_withdrawal_sig(withdrawal_address).unwrap(); + let withdrawal_provide_txid = operators[0] + .0 + .new_withdrawal_sig_rpc(0, user_sig, empty_utxo, withdrawal_tx_out) + .await + .unwrap(); + println!("{:?}", withdrawal_provide_txid); + let txs_to_be_sent = operators[0] + .0 + .withdrawal_proved_on_citrea_rpc(0, deposit_outpoint) + .await + .unwrap(); - operator_take_sigs.push(secp256k1::schnorr::Signature::from_slice(&agg_sig)?); - } - // tracing::debug!("Operator take sigs: {:#?}", operator_take_sigs); - // call operator_take_txs_signed_rpc - let mut move_tx_partial_sigs = Vec::new(); - for (client, _, _) in verifiers.iter() { - let move_tx_partial_sig = client - .operator_take_txs_signed_rpc(deposit_outpoint, operator_take_sigs.clone()) - .await - .unwrap(); - move_tx_partial_sigs.push(move_tx_partial_sig); + for tx in txs_to_be_sent.iter().take(txs_to_be_sent.len() - 1) { + rpc.send_raw_transaction(tx.clone()).unwrap(); } - - // tracing::debug!("Move tx partial sigs: {:#?}", move_tx_partial_sigs); - - // aggreagte move_tx_partial_sigs - let agg_move_tx_final_sig = aggregate_move_partial_sigs( - deposit_outpoint, - &evm_address, - &signer_address, - config.verifiers_public_keys.clone(), - &agg_nonces[0].clone(), - move_tx_partial_sigs, - config.network.clone(), - )?; - - let move_tx_sig = secp256k1::schnorr::Signature::from_slice(&agg_move_tx_final_sig)?; - - let key_agg_ctx = create_key_agg_ctx(config.verifiers_public_keys.clone(), None)?; - let musig_agg_pubkey: musig2::secp256k1::PublicKey = key_agg_ctx.aggregated_pubkey(); - let (musig_agg_xonly_pubkey, _) = musig_agg_pubkey.x_only_public_key(); - let nofn_xonly_pk = - bitcoin::XOnlyPublicKey::from_slice(&musig_agg_xonly_pubkey.serialize()).unwrap(); - - let mut move_tx_handler = TransactionBuilder::create_move_tx( - deposit_outpoint, - &evm_address, - &signer_address, - &nofn_xonly_pk, - config.network, - ); - let mut move_tx_witness_elements = Vec::new(); - move_tx_witness_elements.push(move_tx_sig.serialize().to_vec()); - handle_taproot_witness_new(&mut move_tx_handler, &move_tx_witness_elements, 0, 0)?; - tracing::debug!("Move tx: {:#?}", move_tx_handler.tx); - // tracing::debug!("Move tx_hex: {:?}", move_tx_handler.tx.raw_hex()); - tracing::debug!("Move tx weight: {:?}", move_tx_handler.tx.weight()); - let move_txid = rpc.send_raw_transaction(&move_tx_handler.tx).unwrap(); - tracing::debug!("Move txid: {:?}", move_txid); - Ok(()) + rpc.mine_blocks(OPERATOR_TAKES_AFTER as u64).unwrap(); + // send the last tx + rpc.send_raw_transaction(txs_to_be_sent.last().unwrap().clone()) + .unwrap(); } diff --git a/core/tests/musig2.rs b/core/tests/musig2.rs index df523d04..ee998f60 100644 --- a/core/tests/musig2.rs +++ b/core/tests/musig2.rs @@ -1,5 +1,5 @@ use bitcoin::opcodes::all::OP_CHECKSIG; -use bitcoin::{hashes::Hash, script, Amount, ScriptBuf, TapNodeHash}; +use bitcoin::{hashes::Hash, script, Amount, ScriptBuf}; use bitcoincore_rpc::{Client, RawTx}; use clementine_core::database::common::Database; use clementine_core::mock::common; @@ -44,7 +44,111 @@ async fn test_musig2_key_spend() { .map(|x| ByteArray66(x.1 .0)) .collect::>(), ); - let key_agg_ctx = create_key_agg_ctx(pks.clone(), None).unwrap(); + let key_agg_ctx = create_key_agg_ctx(pks.clone(), None, true).unwrap(); + let untweaked_pubkey = + key_agg_ctx.aggregated_pubkey_untweaked::(); + let untweaked_xonly_pubkey: secp256k1::XOnlyPublicKey = + secp256k1::XOnlyPublicKey::from_slice(&untweaked_pubkey.x_only_public_key().0.serialize()) + .unwrap(); + let (to_address, _) = TransactionBuilder::create_taproot_address(&vec![], None, config.network); + let (from_address, from_address_spend_info) = TransactionBuilder::create_taproot_address( + &vec![], + Some(untweaked_xonly_pubkey), + config.network, + ); + let utxo = rpc.send_to_address(&from_address, 100_000_000).unwrap(); + let prevout = rpc.get_txout_from_outpoint(&utxo).unwrap(); + let tx_outs = TransactionBuilder::create_tx_outs(vec![( + Amount::from_sat(99_000_000), + to_address.script_pubkey(), + )]); + let tx_ins = TransactionBuilder::create_tx_ins(vec![utxo]); + let dummy_tx = TransactionBuilder::create_btc_tx(tx_ins, tx_outs); + let mut tx_details = TxHandler { + tx: dummy_tx, + prevouts: vec![prevout], + scripts: vec![vec![]], + taproot_spend_infos: vec![from_address_spend_info.clone()], + }; + let message = Actor::convert_tx_to_sighash_pubkey_spend(&mut tx_details, 0) + .unwrap() + .to_byte_array(); + let merkle_root = from_address_spend_info.merkle_root(); + tracing::debug!("Merkle Root: {:?}", merkle_root); + let key_agg_ctx = create_key_agg_ctx(pks.clone(), merkle_root, true).unwrap(); + + let partial_sigs: Vec<[u8; 32]> = kp_vec + .iter() + .zip(nonce_pair_vec.iter()) + .map(|(kp, nonce_pair)| { + partial_sign( + pks.clone(), + merkle_root, + true, + nonce_pair.0, + agg_nonce.clone(), + kp, + message, + ) + }) + .collect(); + let final_signature: [u8; 64] = aggregate_partial_signatures( + pks.clone(), + merkle_root, + true, + &agg_nonce, + partial_sigs, + message, + ) + .unwrap(); + let musig_agg_pubkey: musig2::secp256k1::PublicKey = key_agg_ctx.aggregated_pubkey(); + let musig_agg_xonly_pubkey = musig_agg_pubkey.x_only_public_key().0; + let musig_agg_xonly_pubkey_wrapped = + bitcoin::XOnlyPublicKey::from_slice(&musig_agg_xonly_pubkey.serialize()).unwrap(); + + musig2::verify_single(musig_agg_pubkey, &final_signature, message) + .expect("Verification failed!"); + let schnorr_sig = secp256k1::schnorr::Signature::from_slice(&final_signature).unwrap(); + let res = secp + .verify_schnorr( + &schnorr_sig, + &Message::from_digest(message), + &musig_agg_xonly_pubkey_wrapped, + ) + .unwrap(); + println!("MuSig2 signature verified successfully!"); + println!("SECP Verification: {:?}", res); + tx_details.tx.input[0].witness.push(&final_signature); + let txid = rpc.send_raw_transaction(&tx_details.tx).unwrap(); + println!("Transaction sent successfully! Txid: {}", txid); +} + +#[tokio::test] +async fn test_musig2_key_spend_with_script() { + let secp = bitcoin::secp256k1::Secp256k1::new(); + + let mut config: BridgeConfig = create_test_config_with_thread_name!("test_config_musig2.toml"); + let rpc: ExtendedRpc = create_extended_rpc!(config); + let sks = config.all_verifiers_secret_keys.unwrap(); + let kp_vec: Vec = sks + .iter() + .map(|sk| Keypair::from_secret_key(&secp, &sk)) + .collect(); + let nonce_pair_vec: Vec = kp_vec + .iter() + .map(|kp| nonce_pair(&kp, &mut secp256k1::rand::thread_rng())) + .collect(); + let pks = kp_vec + .iter() + .map(|kp| kp.public_key()) + .collect::>(); + let agg_nonce = aggregate_nonces( + nonce_pair_vec + .iter() + .map(|x| ByteArray66(x.1 .0)) + .collect::>(), + ); + let key_agg_ctx = create_key_agg_ctx(pks.clone(), None, false).unwrap(); let untweaked_pubkey = key_agg_ctx.aggregated_pubkey_untweaked::(); let untweaked_xonly_pubkey: secp256k1::XOnlyPublicKey = @@ -52,12 +156,7 @@ async fn test_musig2_key_spend() { .unwrap(); let dummy_script = script::Builder::new().push_int(1).into_script(); let scripts: Vec = vec![dummy_script]; - let to_address = bitcoin::Address::p2tr( - &secp, - *utils::UNSPENDABLE_XONLY_PUBKEY, - None, - bitcoin::Network::Regtest, - ); + let (to_address, _) = TransactionBuilder::create_taproot_address(&vec![], None, config.network); let (from_address, from_address_spend_info) = TransactionBuilder::create_taproot_address( &scripts, Some(untweaked_xonly_pubkey), @@ -81,11 +180,7 @@ async fn test_musig2_key_spend() { .unwrap() .to_byte_array(); let merkle_root = from_address_spend_info.merkle_root(); - let tweak: [u8; 32] = match merkle_root { - Some(root) => root.to_byte_array(), - None => TapNodeHash::all_zeros().to_byte_array(), - }; - let key_agg_ctx = create_key_agg_ctx(pks.clone(), Some(tweak)).unwrap(); + let key_agg_ctx = create_key_agg_ctx(pks.clone(), merkle_root, true).unwrap(); let partial_sigs: Vec<[u8; 32]> = kp_vec .iter() @@ -93,7 +188,8 @@ async fn test_musig2_key_spend() { .map(|(kp, nonce_pair)| { partial_sign( pks.clone(), - Some(tweak), + merkle_root, + true, nonce_pair.0, agg_nonce.clone(), kp, @@ -101,9 +197,15 @@ async fn test_musig2_key_spend() { ) }) .collect(); - let final_signature: [u8; 64] = - aggregate_partial_signatures(pks.clone(), Some(tweak), &agg_nonce, partial_sigs, message) - .unwrap(); + let final_signature: [u8; 64] = aggregate_partial_signatures( + pks.clone(), + merkle_root, + true, + &agg_nonce, + partial_sigs, + message, + ) + .unwrap(); let musig_agg_pubkey: musig2::secp256k1::PublicKey = key_agg_ctx.aggregated_pubkey(); let musig_agg_xonly_pubkey = musig_agg_pubkey.x_only_public_key().0; let musig_agg_xonly_pubkey_wrapped = @@ -119,11 +221,11 @@ async fn test_musig2_key_spend() { &musig_agg_xonly_pubkey_wrapped, ) .unwrap(); - println!("MuSig2 signature verified successfully!"); - println!("SECP Verification: {:?}", res); + // println!("MuSig2 signature verified successfully!"); + // println!("SECP Verification: {:?}", res); tx_details.tx.input[0].witness.push(&final_signature); let txid = rpc.send_raw_transaction(&tx_details.tx).unwrap(); - println!("Transaction sent successfully! Txid: {}", txid); + // println!("Transaction sent successfully! Txid: {}", txid); } #[tokio::test] @@ -151,7 +253,7 @@ async fn test_musig2_script_spend() { .map(|x| ByteArray66(x.1 .0)) .collect::>(), ); - let key_agg_ctx = create_key_agg_ctx(pks.clone(), None).unwrap(); + let key_agg_ctx = create_key_agg_ctx(pks.clone(), None, false).unwrap(); let musig_agg_pubkey: musig2::secp256k1::PublicKey = key_agg_ctx.aggregated_pubkey(); let (musig_agg_xonly_pubkey, _) = musig_agg_pubkey.x_only_public_key(); let musig_agg_xonly_pubkey_wrapped = @@ -196,6 +298,7 @@ async fn test_musig2_script_spend() { partial_sign( pks.clone(), None, + false, nonce_pair.0, agg_nonce.clone(), kp, @@ -204,7 +307,8 @@ async fn test_musig2_script_spend() { }) .collect(); let final_signature: [u8; 64] = - aggregate_partial_signatures(pks.clone(), None, &agg_nonce, partial_sigs, message).unwrap(); + aggregate_partial_signatures(pks.clone(), None, false, &agg_nonce, partial_sigs, message) + .unwrap(); musig2::verify_single(musig_agg_pubkey, &final_signature, message) .expect("Verification failed!"); let res = utils::SECP @@ -218,7 +322,7 @@ async fn test_musig2_script_spend() { println!("SECP Verification: {:?}", res); let schnorr_sig = secp256k1::schnorr::Signature::from_slice(&final_signature).unwrap(); let witness_elements = vec![schnorr_sig.as_ref()]; - handle_taproot_witness_new(&mut tx_details, &witness_elements, 0, 0).unwrap(); + handle_taproot_witness_new(&mut tx_details, &witness_elements, 0, Some(0)).unwrap(); println!("HEX: {:?}", tx_details.tx.raw_hex()); let txid = rpc.send_raw_transaction(&tx_details.tx).unwrap(); println!("Transaction sent successfully! Txid: {}", txid); diff --git a/core/tests/taproot.rs b/core/tests/taproot.rs index c054a3b1..0dc9d819 100644 --- a/core/tests/taproot.rs +++ b/core/tests/taproot.rs @@ -86,7 +86,7 @@ async fn run() { .sign_taproot_script_spend_tx_new_tweaked(&mut tx_details, 0, 0) .unwrap(); - handle_taproot_witness_new(&mut tx_details, &vec![sig.as_ref()], 0, 0).unwrap(); + handle_taproot_witness_new(&mut tx_details, &vec![sig.as_ref()], 0, Some(0)).unwrap(); let result = rpc.send_raw_transaction(&tx_details.tx).unwrap(); // let mut sighash_cache = SighashCache::new(tx.clone()); diff --git a/core/tests/withdrawal.rs b/core/tests/withdrawal.rs deleted file mode 100644 index 70808104..00000000 --- a/core/tests/withdrawal.rs +++ /dev/null @@ -1,71 +0,0 @@ -//! # Withdrawal Flow Test -//! -//! This test checks if basic withdrawal operations are OK or not. - -use bitcoin::Address; -use clementine_core::database::common::Database; -use clementine_core::extended_rpc::ExtendedRpc; -use clementine_core::mock::common; -use clementine_core::servers::*; -use clementine_core::traits::rpc::OperatorRpcClient; -use clementine_core::user::User; -use clementine_core::{ - create_extended_rpc, create_test_config, create_test_config_with_thread_name, -}; -use crypto_bigint::rand_core::OsRng; -use secp256k1::rand::Rng; -use secp256k1::SecretKey; -use std::thread; - -#[tokio::test] -async fn test_withdrawal_request() { - let mut config = create_test_config_with_thread_name!("test_config_flow.toml"); - let rpc = create_extended_rpc!(config); - - let (operator_client, _operator_handler, _operator_addr) = - create_operator_server(config.clone(), rpc.clone()) - .await - .unwrap(); - let secp = bitcoin::secp256k1::Secp256k1::new(); - let user_sk = SecretKey::from_slice(&OsRng.gen::<[u8; 32]>()).unwrap(); - let user = User::new(rpc, user_sk, config.clone()); - let withdrawal_address = Address::p2tr( - &secp, - user_sk.x_only_public_key(&secp).0, - None, - config.network, - ); - let (empty_utxo, withdrawal_tx_out, user_sig) = - user.generate_withdrawal_sig(withdrawal_address).unwrap(); - let withdrawal_provide_txid = operator_client - .new_withdrawal_sig_rpc(0, user_sig, empty_utxo, withdrawal_tx_out) - .await - .unwrap(); - println!("{:?}", withdrawal_provide_txid); -} - -#[tokio::test] -async fn test_honest_operator_takes_refund() { - let mut config = create_test_config_with_thread_name!("test_config_flow.toml"); - let rpc = create_extended_rpc!(config); - - let secp = bitcoin::secp256k1::Secp256k1::new(); - let user_sk = SecretKey::from_slice(&OsRng.gen::<[u8; 32]>()).unwrap(); - let user = User::new(rpc, user_sk, config.clone()); - let withdrawal_address = Address::p2tr( - &secp, - user_sk.x_only_public_key(&secp).0, - None, - config.network, - ); - let (_empty_utxo, _withdrawal_tx_out, _user_sig) = - user.generate_withdrawal_sig(withdrawal_address).unwrap(); - // let withdrawal_provide_txid = operator_client - // .new_withdrawal_sig_rpc(0, user_sig, empty_utxo, withdrawal_tx_out) - // .await - // .unwrap(); - // println!("{:?}", withdrawal_provide_txid); -} - -#[tokio::test] -async fn test_malicious_operator_gets_slashed() {} diff --git a/scripts/schema.sql b/scripts/schema.sql index d3c84177..01bee841 100644 --- a/scripts/schema.sql +++ b/scripts/schema.sql @@ -59,6 +59,8 @@ create table deposit_kickoff_utxos ( id serial primary key, deposit_outpoint text not null check (deposit_outpoint ~ '^[a-fA-F0-9]{64}:(0|[1-9][0-9]{0,9})$'), kickoff_utxo jsonb not null, + slash_or_take_sig text, + operator_take_sig text, created_at timestamp not null default now(), unique (deposit_outpoint, kickoff_utxo) );