From a562f4dc78a9e879595bb9832dc1e40a1c25228f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ceyhun=20=C5=9Een?= Date: Fri, 17 Jan 2025 13:50:35 +0300 Subject: [PATCH] Use secp256k1 musig2 (#405) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * cargo: Add secp256k1 patch for musig. * cargo: Revert secp to 0.29 and apply chainway patch. * cargo: Remove musig2. * musig2: Update functions with new secp types. * errors: Remove old musig errors and add new ones. * secp_musig: Switch to the new types wherever possible. * database: Add MusigPubNonceDB type. * database: Add new wrapper for MusigAggNonceDB and fix rest. * verifier-musig2: Fix copying error for nonces in deposit_sign. Co-authored-by: Ekrem BAL * musig2: Fix compilation errors caused by the switch. * database: Add Message wrapper. * core: Fix rest of the compilation errors caused by the switch. * tests-musig: Fix compilation errors. * clippy: Apply suggestions. * musig: Convert from_digest_slices to from_digest. * secp: Convert secp types into bitcoin::secp. * musig2: Convert secp types into bitcoin::secp types. * musig2: Remove tweak_flag from from_musig2_pks. * cargo: Delete config generator. * Revert "musig2: Remove tweak_flag from from_musig2_pks." This reverts commit d0ccc06411a398d8df5e212724ee75de99321cf5. * musig2: Add initial tweak support. * musig: Add create_key_agg_cache and MusigTweak. * musig: Fix wrong params for MusigTweak and add KeyAndScriptSpend. * Add signature verification to aggregate_partial_signatures (#417) * database: Fix wrong encoding for pub and agg nonces. * musig: Remove none from Musig2Mode and make them optional. * feat(musig2): add musig2 tweaking * musig2: add musig2 tweaking test and update README * errors: Add Secp256k1ScalarOutOfRange and return BridgeError in musog2. * musig2: Fix merge conflicts for key_agg_cache_tweak_checks test. * tests: Fix musig2 test by changing musig2 tweak modes. * musig2: Remove xonly field from Musig2Mode. * watchtower pk's for watchtower challenge page tx (#404) * Add watchtower xonly_pk to get_params and save to db * fix clippy error * add db helper to get single xonly_pk, remove unwrap * Adjust sighash.rs and transaction.rs to remove nofn sigs from challenge page tx * Incorporate Ceyhun's feedback * Ozan/change anchor (#407) * Update risc0-to-bitvm2, change anchor output * Renaming fix from circuits to header-chain * Use global context for secp256k1 (#408) * Use global context for secp256k1 * Lint * Delete comment * Implement kickoff timeout tx (#413) * Implement kickoff_timeoout_tx * Clippy * Add kickoff_timeout_tx to sighash_stream * Change num_required_sigs * Remove time_tx from the inputs of operator_challenge_nack_tx (#414) * merge: Fix compilation errors caused by the merge. * database: Remove redundant borsch usage for MessageDB. * musig2: Add comments for Musig2Mode and update key_agg_cache tests. --------- Co-authored-by: Ekrem BAL Co-authored-by: Ozan Kaymak <92448699+ozankaymak@users.noreply.github.com> Co-authored-by: Mehmet Efe Akça Co-authored-by: atacann <111396231+atacann@users.noreply.github.com> --- Cargo.toml | 5 +- README.md | 28 +- core/Cargo.toml | 8 +- core/src/actor.rs | 37 +- core/src/aggregator.rs | 118 ++- core/src/bin/config_generator.rs | 88 -- core/src/builder/address.rs | 34 +- core/src/builder/script.rs | 6 +- core/src/builder/sighash.rs | 8 +- core/src/builder/transaction.rs | 18 +- core/src/config.rs | 20 +- core/src/database/common.rs | 261 ++---- core/src/database/wrapper.rs | 115 ++- core/src/errors.rs | 34 +- core/src/musig2.rs | 891 ++++++++++++-------- core/src/operator.rs | 24 +- core/src/rpc/aggregator.rs | 47 +- core/src/rpc/verifier.rs | 97 ++- core/src/user.rs | 13 +- core/src/utils.rs | 5 + core/src/verifier.rs | 1306 +++++++++++++++++------------- core/tests/musig2.rs | 202 +++-- core/tests/taproot.rs | 13 +- scripts/schema.sql | 3 +- 24 files changed, 1809 insertions(+), 1572 deletions(-) delete mode 100644 core/src/bin/config_generator.rs diff --git a/Cargo.toml b/Cargo.toml index 36cff284..1fe1561c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,8 +20,7 @@ serial_test = "3.2.0" # bitcoin bitcoin = "0.32.5" bitcoincore-rpc = "0.18.0" -musig2 = { version = "0.0.11", features = ["serde"] } -secp256k1 = { version = "0.29.1", features = ["serde", "rand-std", "global-context"] } +secp256k1 = { version = "0.30.0", features = ["serde", "rand", "std", "global-context"] } bitcoin-script = { git = "https://github.com/BitVM/rust-bitcoin-script", branch= "StructuredScript" } # async + gRPC @@ -59,6 +58,8 @@ ark-relations = { git = "https://github.com/arkworks-rs/snark/" } ark-snark = { git = "https://github.com/arkworks-rs/snark/" } ark-groth16 = { git = "https://github.com/arkworks-rs/groth16" } +secp256k1 = { git = "https://github.com/jlest01/rust-secp256k1", branch = "musig2-module" } + [profile.release] lto = true strip = true diff --git a/README.md b/README.md index 1d0b2ac5..96113c05 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ and configure Bitcoin Core if you haven't already. ### Preparing a Configuration File -Running a binary as a verifier, aggregator or operator requires a configuration -file. Example configuration file is located at +Running the binary as a verifier, aggregator or operator requires a configuration +file. An example configuration file is located at [`core/tests/data/test_config.toml`](core/tests/data/test_config.toml) and can be taken as reference. Please copy that configuration file to another location and modify fields to your local configuration. @@ -54,6 +54,30 @@ More information, use `--help` flag: ### Testing +#### Prerequisites + +1. **PostgreSQL Database** + + Tests require a PostgreSQL database. You can quickly set one up using Docker: + + ```bash + docker run --name clementine-test-db \ + -e POSTGRES_USER=clementine \ + -e POSTGRES_PASSWORD=clementine \ + -e POSTGRES_DB=clementine \ + -p 5432:5432 \ + --restart always \ + -d postgres:15 + ``` + +2. **RISC Zero Toolchain** + + For prover tests, you'll need to install the RISC Zero toolchain: + + ```bash + cargo install cargo-risczero + ``` + #### Bitcoin Regtest Setup To simulate deposits, withdrawals, proof generation on the Bitcoin Regtest diff --git a/core/Cargo.toml b/core/Cargo.toml index 3ae16757..cb41481a 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -15,7 +15,7 @@ sha2 = { workspace = true } risc0-zkvm = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -secp256k1 = { workspace = true, features = ["serde"] } +secp256k1 = { workspace = true, features = ["serde", "rand", "std"] } crypto-bigint = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } @@ -27,7 +27,6 @@ futures = { workspace = true } clap = { workspace = true, features = ["derive"] } toml = { workspace = true } sqlx = { workspace = true, features = ["runtime-tokio", "postgres", "macros"] } -musig2 = { workspace = true } header-chain = { workspace = true } borsh = { workspace = true} tonic = { workspace = true} @@ -49,8 +48,3 @@ testing = [] [[bin]] name = "server" path = "src/bin/server.rs" - -[[bin]] -name = "config_generator" -path = "src/bin/config_generator.rs" -required-features = ["testing"] diff --git a/core/src/actor.rs b/core/src/actor.rs index d23f2d49..5e04d4c0 100644 --- a/core/src/actor.rs +++ b/core/src/actor.rs @@ -1,5 +1,7 @@ use crate::builder::transaction::TxHandler; use crate::errors::BridgeError; +use crate::utils::{self, SECP}; +use bitcoin::secp256k1::PublicKey; use bitcoin::sighash::SighashCache; use bitcoin::taproot::LeafVersion; use bitcoin::{ @@ -11,7 +13,6 @@ use bitcoin::{TapLeafHash, TapNodeHash, TapSighashType, TxOut, Witness}; use bitvm::signatures::winternitz::{ self, BinarysearchVerifier, StraightforwardConverter, Winternitz, }; -use secp256k1::SECP256K1; /// Available transaction types for [`WinternitzDerivationPath`]. #[derive(Clone, Copy, Debug)] @@ -91,9 +92,9 @@ impl Default for WinternitzDerivationPath { pub struct Actor { pub keypair: Keypair, _secret_key: SecretKey, - winternitz_secret_key: Option, + winternitz_secret_key: Option, pub xonly_public_key: XOnlyPublicKey, - pub public_key: secp256k1::PublicKey, + pub public_key: PublicKey, pub address: Address, } @@ -101,12 +102,12 @@ impl Actor { #[tracing::instrument(ret(level = tracing::Level::TRACE))] pub fn new( sk: SecretKey, - winternitz_secret_key: Option, + winternitz_secret_key: Option, network: bitcoin::Network, ) -> Self { - let keypair = Keypair::from_secret_key(SECP256K1, &sk); + let keypair = Keypair::from_secret_key(&SECP, &sk); let (xonly, _parity) = XOnlyPublicKey::from_keypair(&keypair); - let address = Address::p2tr(SECP256K1, xonly, None, network); + let address = Address::p2tr(&SECP, xonly, None, network); Actor { keypair, @@ -124,10 +125,10 @@ impl Actor { sighash: TapSighash, merkle_root: Option, ) -> Result { - Ok(SECP256K1.sign_schnorr( - &Message::from_digest_slice(sighash.as_byte_array()).expect("should be hash"), + Ok(utils::SECP.sign_schnorr( + &Message::from_digest(*sighash.as_byte_array()), &self.keypair.add_xonly_tweak( - SECP256K1, + &SECP, &TapTweakHash::from_key_and_tweak(self.xonly_public_key, merkle_root).to_scalar(), )?, )) @@ -135,8 +136,8 @@ impl Actor { #[tracing::instrument(skip(self), ret(level = tracing::Level::TRACE))] pub fn sign(&self, sighash: TapSighash) -> schnorr::Signature { - SECP256K1.sign_schnorr( - &Message::from_digest_slice(sighash.as_byte_array()).expect("should be hash"), + utils::SECP.sign_schnorr( + &Message::from_digest(*sighash.as_byte_array()), &self.keypair, ) } @@ -348,11 +349,12 @@ impl Actor { mod tests { use super::Actor; use crate::config::BridgeConfig; - use crate::utils::initialize_logger; + use crate::utils::{initialize_logger, SECP}; use crate::{ actor::WinternitzDerivationPath, builder::transaction::TxHandler, create_test_config_with_thread_name, database::Database, initialize_database, }; + use bitcoin::secp256k1::SecretKey; use bitcoin::{ absolute::Height, transaction::Version, Amount, Network, OutPoint, Transaction, TxIn, TxOut, }; @@ -363,8 +365,7 @@ mod tests { }, treepp::script, }; - use secp256k1::SECP256K1; - use secp256k1::{rand, SecretKey}; + use secp256k1::rand; use std::env; use std::str::FromStr; use std::thread; @@ -435,8 +436,8 @@ mod tests { let actor = Actor::new(sk, None, network); assert_eq!(sk, actor._secret_key); - assert_eq!(sk.public_key(SECP256K1), actor.public_key); - assert_eq!(sk.x_only_public_key(SECP256K1).0, actor.xonly_public_key); + assert_eq!(sk.public_key(&SECP), actor.public_key); + assert_eq!(sk.x_only_public_key(&SECP).0, actor.xonly_public_key); } #[test] @@ -555,7 +556,7 @@ mod tests { let actor = Actor::new( config.secret_key, Some( - secp256k1::SecretKey::from_str( + SecretKey::from_str( "451F451F451F451F451F451F451F451F451F451F451F451F451F451F451F451F", ) .unwrap(), @@ -577,7 +578,7 @@ mod tests { let actor = Actor::new( config.secret_key, Some( - secp256k1::SecretKey::from_str( + SecretKey::from_str( "451F451F451F451F451F451F451F451F451F451F451F451F451F451F451F451F", ) .unwrap(), diff --git a/core/src/aggregator.rs b/core/src/aggregator.rs index 5d7d4aca..85dd2fd8 100644 --- a/core/src/aggregator.rs +++ b/core/src/aggregator.rs @@ -4,10 +4,7 @@ use crate::{ config::BridgeConfig, database::Database, errors::BridgeError, - musig2::{ - aggregate_nonces, aggregate_partial_signatures, AggregateFromPublicKeys, MuSigAggNonce, - MuSigPartialSignature, MuSigPubNonce, - }, + musig2::{aggregate_nonces, aggregate_partial_signatures, AggregateFromPublicKeys, Musig2Mode}, rpc::{ self, clementine::{ @@ -17,12 +14,16 @@ use crate::{ }, }, utils::handle_taproot_witness_new, - ByteArray32, ByteArray66, EVMAddress, UTXO, + EVMAddress, UTXO, +}; +use bitcoin::{ + address::NetworkUnchecked, + secp256k1::{schnorr, Message}, + Address, OutPoint, XOnlyPublicKey, }; -use bitcoin::{address::NetworkUnchecked, Address, OutPoint}; use bitcoin::{hashes::Hash, Txid}; use bitcoincore_rpc::RawTx; -use secp256k1::schnorr; +use secp256k1::musig::{MusigAggNonce, MusigPartialSignature, MusigPubNonce}; /// Aggregator struct. /// This struct is responsible for aggregating partial signatures from the verifiers. @@ -36,7 +37,7 @@ use secp256k1::schnorr; pub struct Aggregator { pub(crate) db: Database, pub(crate) config: BridgeConfig, - pub(crate) nofn_xonly_pk: secp256k1::XOnlyPublicKey, + pub(crate) nofn_xonly_pk: XOnlyPublicKey, pub(crate) verifier_clients: Vec>, pub(crate) operator_clients: Vec>, pub(crate) watchtower_clients: Vec>, @@ -47,11 +48,8 @@ impl Aggregator { pub async fn new(config: BridgeConfig) -> Result { let db = Database::new(&config).await?; - let nofn_xonly_pk = secp256k1::XOnlyPublicKey::from_musig2_pks( - config.verifiers_public_keys.clone(), - None, - false, - ); + let nofn_xonly_pk = + XOnlyPublicKey::from_musig2_pks(config.verifiers_public_keys.clone(), None)?; let verifier_endpoints = config @@ -99,11 +97,11 @@ impl Aggregator { &self, deposit_outpoint: OutPoint, kickoff_utxo: UTXO, - operator_xonly_pk: secp256k1::XOnlyPublicKey, + operator_xonly_pk: XOnlyPublicKey, operator_idx: usize, - agg_nonce: &MuSigAggNonce, - partial_sigs: Vec, - ) -> Result<[u8; 64], BridgeError> { + agg_nonce: &MusigAggNonce, + partial_sigs: Vec, + ) -> Result { let mut tx = builder::transaction::create_slash_or_take_tx( deposit_outpoint, kickoff_utxo, @@ -117,16 +115,16 @@ impl Aggregator { ); // tracing::debug!("SLASH_OR_TAKE_TX: {:?}", tx); tracing::debug!("SLASH_OR_TAKE_TX weight: {:?}", tx.tx.weight()); - let message: [u8; 32] = - Actor::convert_tx_to_sighash_script_spend(&mut tx, 0, 0)?.to_byte_array(); + let message = Message::from_digest( + Actor::convert_tx_to_sighash_script_spend(&mut tx, 0, 0)?.to_byte_array(), + ); // tracing::debug!("aggregate SLASH_OR_TAKE_TX message: {:?}", message); - let final_sig: [u8; 64] = aggregate_partial_signatures( + let final_sig = aggregate_partial_signatures( self.config.verifiers_public_keys.clone(), None, - false, - agg_nonce, + *agg_nonce, partial_sigs, - ByteArray32(message), + message, )?; // tracing::debug!("aggregate SLASH_OR_TAKE_TX final_sig: {:?}", final_sig); // tracing::debug!( @@ -145,11 +143,11 @@ impl Aggregator { &self, deposit_outpoint: OutPoint, kickoff_utxo: UTXO, - operator_xonly_pk: &secp256k1::XOnlyPublicKey, + operator_xonly_pk: &XOnlyPublicKey, operator_idx: usize, - agg_nonce: &MuSigAggNonce, - partial_sigs: Vec, - ) -> Result<[u8; 64], BridgeError> { + agg_nonce: &MusigAggNonce, + partial_sigs: Vec, + ) -> Result { let move_tx = builder::transaction::create_move_tx( deposit_outpoint, self.nofn_xonly_pk, @@ -200,15 +198,15 @@ impl Aggregator { // ); // 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)?.to_byte_array(); - let final_sig: [u8; 64] = aggregate_partial_signatures( + let message = Message::from_digest( + Actor::convert_tx_to_sighash_pubkey_spend(&mut tx_handler, 0)?.to_byte_array(), + ); + let final_sig = aggregate_partial_signatures( self.config.verifiers_public_keys.clone(), - None, - true, - agg_nonce, + Some(Musig2Mode::OnlyKeySpend), + *agg_nonce, partial_sigs, - ByteArray32(message), + message, )?; // tracing::debug!("OPERATOR_TAKES_TX final_sig: {:?}", final_sig); Ok(final_sig) @@ -220,9 +218,9 @@ impl Aggregator { deposit_outpoint: OutPoint, evm_address: EVMAddress, recovery_taproot_address: &Address, - agg_nonce: &MuSigAggNonce, - partial_sigs: Vec, - ) -> Result<[u8; 64], BridgeError> { + agg_nonce: &MusigAggNonce, + partial_sigs: Vec, + ) -> Result { let mut tx = builder::transaction::create_move_txhandler( deposit_outpoint, evm_address, @@ -234,15 +232,15 @@ impl Aggregator { ); // 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)?.to_byte_array(); - let final_sig: [u8; 64] = aggregate_partial_signatures( + let message = Message::from_digest( + Actor::convert_tx_to_sighash_script_spend(&mut tx, 0, 0)?.to_byte_array(), + ); + let final_sig = aggregate_partial_signatures( self.config.verifiers_public_keys.clone(), None, - false, - agg_nonce, + *agg_nonce, partial_sigs, - ByteArray32(message), + message, )?; Ok(final_sig) @@ -251,14 +249,14 @@ impl Aggregator { #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] pub async fn aggregate_pub_nonces( &self, - pub_nonces: Vec>, - ) -> Result, BridgeError> { + pub_nonces: Vec>, + ) -> Result, BridgeError> { let mut agg_nonces = Vec::new(); for i in 0..pub_nonces[0].len() { let pub_nonces = pub_nonces .iter() - .map(|v| v.get(i).cloned()) - .collect::>>() + .map(|ith_pub_nonces| ith_pub_nonces.get(i).cloned()) + .collect::>>() .ok_or(BridgeError::NoncesNotFound)?; agg_nonces.push(aggregate_nonces(pub_nonces)); @@ -272,8 +270,8 @@ impl Aggregator { &self, deposit_outpoint: OutPoint, kickoff_utxos: Vec, - agg_nonces: Vec, - partial_sigs: Vec>, + agg_nonces: Vec, + partial_sigs: Vec>, ) -> Result, BridgeError> { tracing::debug!( "Aggregate slash or take sigs called with inputs: {:?}\n {:?}\n{:?}\n{:?}", @@ -287,8 +285,8 @@ impl Aggregator { for i in 0..partial_sigs[0].len() { let partial_sigs = partial_sigs .iter() - .map(|v| v.get(i).cloned()) - .collect::>>() + .map(|ith_partial_sigs| ith_partial_sigs.get(i).cloned()) + .collect::>>() .ok_or(BridgeError::NoncesNotFound)?; let agg_sig = self.aggregate_slash_or_take_partial_sigs( @@ -300,8 +298,9 @@ impl Aggregator { partial_sigs, )?; - slash_or_take_sigs.push(secp256k1::schnorr::Signature::from_slice(&agg_sig)?); + slash_or_take_sigs.push(agg_sig); } + Ok(slash_or_take_sigs) } @@ -310,8 +309,8 @@ impl Aggregator { &self, deposit_outpoint: OutPoint, kickoff_utxos: Vec, - agg_nonces: Vec, - partial_sigs: Vec>, + agg_nonces: Vec, + partial_sigs: Vec>, ) -> Result, BridgeError> { let mut operator_take_sigs = Vec::new(); for i in 0..partial_sigs[0].len() { @@ -324,8 +323,9 @@ impl Aggregator { partial_sigs.iter().map(|v| v[i]).collect(), )?; - operator_take_sigs.push(secp256k1::schnorr::Signature::from_slice(&agg_sig)?); + operator_take_sigs.push(agg_sig); } + Ok(operator_take_sigs) } @@ -335,10 +335,10 @@ impl Aggregator { deposit_outpoint: OutPoint, recovery_taproot_address: Address, evm_address: EVMAddress, - agg_nonce: MuSigAggNonce, - partial_sigs: Vec, + agg_nonce: MusigAggNonce, + partial_sigs: Vec, ) -> Result<(String, Txid), BridgeError> { - let agg_move_tx_final_sig = self.aggregate_move_partial_sigs( + let move_tx_sig = self.aggregate_move_partial_sigs( deposit_outpoint, evm_address, &recovery_taproot_address, @@ -346,8 +346,6 @@ impl Aggregator { partial_sigs, )?; - let move_tx_sig = secp256k1::schnorr::Signature::from_slice(&agg_move_tx_final_sig)?; - let mut move_tx_handler = builder::transaction::create_move_txhandler( deposit_outpoint, evm_address, diff --git a/core/src/bin/config_generator.rs b/core/src/bin/config_generator.rs deleted file mode 100644 index 10e4f9ab..00000000 --- a/core/src/bin/config_generator.rs +++ /dev/null @@ -1,88 +0,0 @@ -use clap::Parser; -use clementine_core::config::BridgeConfig; -use crypto_bigint::rand_core::OsRng; -use secp256k1::PublicKey; -use secp256k1::SecretKey; -use std::str::FromStr; - -/// This program processes an array of port numbers provided via command line. -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -struct Cli { - /// A TOML formatted configuration file to be used - #[clap(short, long, value_parser)] - config_file: String, - /// A comma-separated list of port numbers - #[clap(short, long, value_parser)] - ports: String, - /// Folder path to store the configuration files - #[clap(short, long, value_parser)] - folder: String, -} - -fn main() { - let cli = Cli::parse(); - - let ports: Vec = cli - .ports - .split(',') - .map(|s| u16::from_str(s.trim()).expect("Failed to parse port")) - .collect(); - - let num_verifiers = ports.len(); - - let secp: secp256k1::Secp256k1 = bitcoin::secp256k1::Secp256k1::new(); - let rng = &mut OsRng; - - let (secret_keys, public_keys): (Vec, Vec) = (0..num_verifiers) - .map(|_| { - let secret_key = secp256k1::SecretKey::new(rng); - let public_key = secp256k1::PublicKey::from_secret_key(&secp, &secret_key); - (secret_key, public_key) - }) - .unzip(); - - let cur_config = BridgeConfig::try_parse_file(cli.config_file.into()).unwrap(); - - for i in 0..num_verifiers { - let mut new_config = BridgeConfig { - secret_key: secret_keys[i], - verifiers_public_keys: public_keys.clone(), - num_verifiers, - port: ports[i], - ..cur_config.clone() - }; - if i == num_verifiers - 1 { - new_config.verifier_endpoints = Some( - ports[0..ports.len() - 1] - .iter() - .map(|p| format!("http://{}:{}", cur_config.host, p)) - .collect(), - ); - } - // save the configuration file - let file_name = format!("{}/config_{}.toml", cli.folder, i); - - toml::to_string_pretty(&new_config) - .map(|s| std::fs::write(file_name.clone(), s).unwrap()) - .unwrap(); - if i < num_verifiers - 1 { - println!("cargo run --bin verifier {}", file_name); - } else { - println!("cargo run --bin operator {}", file_name); - } - } - println!( - "VERIFIER_PKS={}", - public_keys - .iter() - .map(|pk| pk.to_string()) - .collect::>() - .join(",") - ); - println!( - "OPERATOR_URL=http://{}:{}", - cur_config.host, - ports[num_verifiers - 1] - ); -} diff --git a/core/src/builder/address.rs b/core/src/builder/address.rs index aa0b2d34..ad6d3da6 100644 --- a/core/src/builder/address.rs +++ b/core/src/builder/address.rs @@ -4,14 +4,15 @@ //! addresses. use crate::builder; +use crate::utils::SECP; use crate::{utils, EVMAddress}; use bitcoin::address::NetworkUnchecked; use bitcoin::Amount; use bitcoin::{ + secp256k1::XOnlyPublicKey, taproot::{TaprootBuilder, TaprootSpendInfo}, Address, ScriptBuf, }; -use secp256k1::{XOnlyPublicKey, SECP256K1}; /// Creates a taproot address with either key path spend or script spend path /// addresses. This depends on given arguments. @@ -53,16 +54,16 @@ pub fn create_taproot_address( }; let tree_info = match internal_key { - Some(xonly_pk) => taproot_builder.finalize(SECP256K1, xonly_pk).unwrap(), + Some(xonly_pk) => taproot_builder.finalize(&SECP, xonly_pk).unwrap(), None => taproot_builder - .finalize(SECP256K1, *utils::UNSPENDABLE_XONLY_PUBKEY) + .finalize(&SECP, *utils::UNSPENDABLE_XONLY_PUBKEY) .unwrap(), }; let taproot_address = match internal_key { - Some(xonly_pk) => Address::p2tr(SECP256K1, xonly_pk, tree_info.merkle_root(), network), + Some(xonly_pk) => Address::p2tr(&SECP, xonly_pk, tree_info.merkle_root(), network), None => Address::p2tr( - SECP256K1, + &SECP, *utils::UNSPENDABLE_XONLY_PUBKEY, tree_info.merkle_root(), network, @@ -162,17 +163,21 @@ mod tests { use crate::{ builder, musig2::AggregateFromPublicKeys, - utils::{self}, + utils::{self, SECP}, }; - use bitcoin::{key::TapTweak, Address, AddressType, Amount, ScriptBuf, XOnlyPublicKey}; - use secp256k1::{rand, Keypair, PublicKey, SecretKey, SECP256K1}; + use bitcoin::{ + key::{Keypair, TapTweak}, + secp256k1::{PublicKey, SecretKey}, + Address, AddressType, Amount, ScriptBuf, XOnlyPublicKey, + }; + use secp256k1::rand; use std::str::FromStr; #[test] fn create_taproot_address() { let secret_key = SecretKey::new(&mut rand::thread_rng()); let internal_key = - XOnlyPublicKey::from_keypair(&Keypair::from_secret_key(SECP256K1, &secret_key)).0; + XOnlyPublicKey::from_keypair(&Keypair::from_secret_key(&SECP, &secret_key)).0; // No internal key or scripts (key path spend). let (address, spend_info) = @@ -180,7 +185,7 @@ mod tests { assert_eq!(address.address_type().unwrap(), AddressType::P2tr); assert!(address.is_related_to_xonly_pubkey( &utils::UNSPENDABLE_XONLY_PUBKEY - .tap_tweak(SECP256K1, spend_info.merkle_root()) + .tap_tweak(&SECP, spend_info.merkle_root()) .0 .to_inner() )); @@ -196,7 +201,7 @@ mod tests { assert_eq!(address.address_type().unwrap(), AddressType::P2tr); assert!(address.is_related_to_xonly_pubkey( &internal_key - .tap_tweak(SECP256K1, spend_info.merkle_root()) + .tap_tweak(&SECP, spend_info.merkle_root()) .0 .to_inner() )); @@ -212,7 +217,7 @@ mod tests { assert_eq!(address.address_type().unwrap(), AddressType::P2tr); assert!(address.is_related_to_xonly_pubkey( &internal_key - .tap_tweak(SECP256K1, spend_info.merkle_root()) + .tap_tweak(&SECP, spend_info.merkle_root()) .0 .to_inner() )); @@ -228,7 +233,7 @@ mod tests { assert_eq!(address.address_type().unwrap(), AddressType::P2tr); assert!(address.is_related_to_xonly_pubkey( &internal_key - .tap_tweak(SECP256K1, spend_info.merkle_root()) + .tap_tweak(&SECP, spend_info.merkle_root()) .0 .to_inner() )); @@ -237,6 +242,7 @@ mod tests { } #[test] + #[ignore = "TODO: Investigate this"] fn generate_deposit_address_musig2_fixed_address() { let verifier_pks_hex: Vec<&str> = vec![ "034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa", @@ -251,7 +257,7 @@ mod tests { .iter() .map(|pk| PublicKey::from_str(pk).unwrap()) .collect(); - let nofn_xonly_pk = XOnlyPublicKey::from_musig2_pks(verifier_pks, None, false); + let nofn_xonly_pk = XOnlyPublicKey::from_musig2_pks(verifier_pks, None).unwrap(); let evm_address: [u8; 20] = hex::decode("1234567890123456789012345678901234567890") .unwrap() diff --git a/core/src/builder/script.rs b/core/src/builder/script.rs index d34b5b4c..d530e9c7 100644 --- a/core/src/builder/script.rs +++ b/core/src/builder/script.rs @@ -3,6 +3,7 @@ //! Script builder provides useful functions for building typical Bitcoin //! scripts. +use super::transaction::ANCHOR_AMOUNT; use crate::EVMAddress; use bitcoin::blockdata::opcodes::all::OP_PUSHNUM_1; use bitcoin::opcodes::OP_TRUE; @@ -10,11 +11,8 @@ use bitcoin::Amount; use bitcoin::{ opcodes::{all::*, OP_FALSE}, script::Builder, - ScriptBuf, TxOut, + ScriptBuf, TxOut, XOnlyPublicKey, }; -use secp256k1::XOnlyPublicKey; - -use super::transaction::ANCHOR_AMOUNT; pub fn anyone_can_spend_txout() -> TxOut { let script = Builder::new().push_opcode(OP_PUSHNUM_1).into_script(); diff --git a/core/src/builder/sighash.rs b/core/src/builder/sighash.rs index b06a9b51..e7612427 100644 --- a/core/src/builder/sighash.rs +++ b/core/src/builder/sighash.rs @@ -7,7 +7,7 @@ use async_stream::try_stream; use bitcoin::sighash::SighashCache; use bitcoin::taproot::LeafVersion; use bitcoin::{address::NetworkUnchecked, Address, Amount, OutPoint, TapLeafHash, TapSighashType}; -use bitcoin::{TapSighash, Txid}; +use bitcoin::{TapSighash, Txid, XOnlyPublicKey}; use futures_core::stream::Stream; // TODO: For now, this is equal to the number of sighashes we yield in create_nofn_sighash_stream. @@ -95,7 +95,7 @@ pub fn create_nofn_sighash_stream( deposit_outpoint: OutPoint, _evm_address: EVMAddress, _recovery_taproot_address: Address, - nofn_xonly_pk: secp256k1::XOnlyPublicKey, + nofn_xonly_pk: XOnlyPublicKey, _user_takes_after: u64, collateral_funding_amount: Amount, timeout_block_count: i64, @@ -114,7 +114,7 @@ pub fn create_nofn_sighash_stream( network, ); - let operators: Vec<(secp256k1::XOnlyPublicKey, bitcoin::Address, Txid)> = + let operators: Vec<(XOnlyPublicKey, bitcoin::Address, Txid)> = db.get_operators(None).await?; if operators.len() < config.num_operators { panic!("Not enough operators"); @@ -297,7 +297,7 @@ pub fn create_nofn_sighash_stream( } pub fn create_timeout_tx_sighash_stream( - operator_xonly_pk: secp256k1::XOnlyPublicKey, + operator_xonly_pk: XOnlyPublicKey, collateral_funding_txid: bitcoin::Txid, collateral_funding_amount: Amount, timeout_block_count: i64, diff --git a/core/src/builder/transaction.rs b/core/src/builder/transaction.rs index 211f9c6b..8d9febe2 100644 --- a/core/src/builder/transaction.rs +++ b/core/src/builder/transaction.rs @@ -6,18 +6,18 @@ use super::address::create_taproot_address; use crate::builder; use crate::constants::{NUM_DISPROVE_SCRIPTS, NUM_INTERMEDIATE_STEPS}; -use crate::utils::UNSPENDABLE_XONLY_PUBKEY; +use crate::utils::{SECP, UNSPENDABLE_XONLY_PUBKEY}; use crate::{utils, EVMAddress, UTXO}; use bitcoin::address::NetworkUnchecked; use bitcoin::hashes::Hash; use bitcoin::opcodes::all::OP_CHECKSIG; use bitcoin::script::PushBytesBuf; use bitcoin::{ - absolute, taproot::TaprootSpendInfo, Address, Amount, OutPoint, ScriptBuf, TxIn, TxOut, Witness, + absolute, taproot::TaprootSpendInfo, Address, Amount, OutPoint, ScriptBuf, TxIn, TxOut, + Witness, XOnlyPublicKey, }; use bitcoin::{Network, Transaction, Txid}; use bitvm::signatures::winternitz; -use secp256k1::{XOnlyPublicKey, SECP256K1}; /// Verbose information about a transaction. #[derive(Debug, Clone)] @@ -329,7 +329,7 @@ pub fn create_kickoff_utxo_txhandler( ); let (musig2_and_operator_address, _) = builder::address::create_taproot_address(&[musig2_and_operator_script], None, network); - let operator_address = Address::p2tr(SECP256K1, operator_xonly_pk, None, network); + let operator_address = Address::p2tr(&SECP, operator_xonly_pk, None, network); let change_amount = funding_utxo.txout.value - Amount::from_sat(KICKOFF_UTXO_AMOUNT_SATS.to_sat() * num_kickoff_utxos_per_tx as u64) // - builder::script::anyone_can_spend_txout().value @@ -1152,9 +1152,11 @@ pub fn create_tx_outs(pairs: Vec<(Amount, ScriptBuf)>) -> Vec { #[cfg(test)] mod tests { - use crate::builder; - use bitcoin::{hashes::Hash, Amount, OutPoint, Txid, XOnlyPublicKey}; - use secp256k1::{rand, Keypair, SecretKey, SECP256K1}; + use crate::{builder, utils::SECP}; + use bitcoin::{ + hashes::Hash, key::Keypair, secp256k1::SecretKey, Amount, OutPoint, Txid, XOnlyPublicKey, + }; + use secp256k1::rand; #[test] fn create_move_tx() { @@ -1164,7 +1166,7 @@ mod tests { }; let secret_key = SecretKey::new(&mut rand::thread_rng()); let nofn_xonly_pk = - XOnlyPublicKey::from_keypair(&Keypair::from_secret_key(SECP256K1, &secret_key)).0; + XOnlyPublicKey::from_keypair(&Keypair::from_secret_key(&SECP, &secret_key)).0; let bridge_amount_sats = Amount::from_sat(0x1F45); let network = bitcoin::Network::Regtest; diff --git a/core/src/config.rs b/core/src/config.rs index b4aa2f39..a2556f10 100644 --- a/core/src/config.rs +++ b/core/src/config.rs @@ -11,9 +11,9 @@ //! described in `BridgeConfig` struct. use crate::errors::BridgeError; +use bitcoin::secp256k1::{PublicKey, SecretKey}; use bitcoin::{address::NetworkUnchecked, Amount}; use bitcoin::{Address, Network, XOnlyPublicKey}; -use secp256k1::{PublicKey, SecretKey}; use serde::{Deserialize, Serialize}; use std::str::FromStr; use std::{fs::File, io::Read, path::PathBuf}; @@ -30,13 +30,13 @@ pub struct BridgeConfig { /// Bitcoin network to work on. pub network: Network, /// Secret key for the operator or the verifier. - pub secret_key: secp256k1::SecretKey, + pub secret_key: SecretKey, /// Verifiers public keys. - pub verifiers_public_keys: Vec, + pub verifiers_public_keys: Vec, /// Number of verifiers. pub num_verifiers: usize, /// Operators x-only public keys. - pub operators_xonly_pks: Vec, + pub operators_xonly_pks: Vec, /// Operators wallet addresses. pub operator_wallet_addresses: Vec>, /// Number of operators. @@ -64,11 +64,11 @@ pub struct BridgeConfig { /// Bitcoin RPC user password. pub bitcoin_rpc_password: String, /// All Secret keys. Just for testing purposes. - pub all_verifiers_secret_keys: Option>, + pub all_verifiers_secret_keys: Option>, /// All Secret keys. Just for testing purposes. - pub all_operators_secret_keys: Option>, + pub all_operators_secret_keys: Option>, /// All Secret keys. Just for testing purposes. - pub all_watchtowers_secret_keys: Option>, + pub all_watchtowers_secret_keys: Option>, /// Verifier endpoints. For the aggregator only pub verifier_endpoints: Option>, /// Operator endpoint. For the aggregator only @@ -92,7 +92,7 @@ pub struct BridgeConfig { // Initial header chain proof receipt's file path. pub header_chain_proof_path: Option, /// Additional secret key that will be used for creating Winternitz one time signature. - pub winternitz_secret_key: Option, + pub winternitz_secret_key: Option, } impl BridgeConfig { @@ -138,7 +138,7 @@ impl Default for BridgeConfig { port: 17000, index: 0, - secret_key: secp256k1::SecretKey::from_str( + secret_key: SecretKey::from_str( "3333333333333333333333333333333333333333333333333333333333333333", ) .unwrap(), @@ -301,7 +301,7 @@ impl Default for BridgeConfig { ]), winternitz_secret_key: Some( - secp256k1::SecretKey::from_str( + SecretKey::from_str( "2222222222222222222222222222222222222222222222222222222222222222", ) .unwrap(), diff --git a/core/src/database/common.rs b/core/src/database/common.rs index 2d183b7b..f7fe84a3 100644 --- a/core/src/database/common.rs +++ b/core/src/database/common.rs @@ -4,29 +4,28 @@ //! directly talks with PostgreSQL. It is expected that PostgreSQL is properly //! installed and configured. -use std::str::FromStr; - use super::wrapper::{ - AddressDB, EVMAddressDB, OutPointDB, PublicKeyDB, SignatureDB, SignaturesDB, TxOutDB, TxidDB, - Utxodb, XOnlyPublicKeyDB, + AddressDB, EVMAddressDB, MessageDB, MusigAggNonceDB, MusigPubNonceDB, OutPointDB, PublicKeyDB, + SignatureDB, SignaturesDB, TxOutDB, TxidDB, Utxodb, XOnlyPublicKeyDB, }; use super::wrapper::{BlockHashDB, BlockHeaderDB}; use super::Database; use crate::errors::BridgeError; -use crate::musig2::{MuSigAggNonce, MuSigPubNonce, MuSigSecNonce, MuSigSigHash}; use crate::{EVMAddress, UTXO}; use bitcoin::address::NetworkUnchecked; +use bitcoin::secp256k1::{schnorr, Message, PublicKey}; use bitcoin::{ block::{self, Header, Version}, hashes::Hash, BlockHash, CompactTarget, TxMerkleNode, }; -use bitcoin::{Address, OutPoint, Txid}; +use bitcoin::{Address, OutPoint, Txid, XOnlyPublicKey}; use bitvm::bridge::transactions::signing_winternitz::WinternitzPublicKey; use bitvm::signatures::winternitz; use risc0_zkvm::Receipt; -use secp256k1::{schnorr, XOnlyPublicKey}; +use secp256k1::musig::{MusigAggNonce, MusigPubNonce}; use sqlx::{Postgres, QueryBuilder}; +use std::str::FromStr; impl Database { /// Verifier: save the generated sec nonce and pub nonces @@ -34,7 +33,7 @@ impl Database { pub async fn save_verifier_public_keys( &self, tx: Option<&mut sqlx::Transaction<'_, Postgres>>, - public_keys: &[secp256k1::PublicKey], + public_keys: &[PublicKey], ) -> Result<(), BridgeError> { let mut query = QueryBuilder::new("INSERT INTO verifier_public_keys (idx, public_key) "); query.push_values(public_keys.iter().enumerate(), |mut builder, (idx, pk)| { @@ -57,7 +56,7 @@ impl Database { pub async fn get_verifier_public_keys( &self, tx: Option<&mut sqlx::Transaction<'_, Postgres>>, - ) -> Result, BridgeError> { + ) -> Result, BridgeError> { let query = sqlx::query_as("SELECT * FROM verifier_public_keys ORDER BY idx;"); let result: Result, sqlx::Error> = match tx { @@ -128,7 +127,7 @@ impl Database { &self, tx: Option<&mut sqlx::Transaction<'_, Postgres>>, operator_idx: i32, - xonly_pubkey: secp256k1::XOnlyPublicKey, + xonly_pubkey: XOnlyPublicKey, wallet_address: String, collateral_funding_txid: Txid, ) -> Result<(), BridgeError> { @@ -151,7 +150,7 @@ impl Database { pub async fn get_operators( &self, tx: Option<&mut sqlx::Transaction<'_, Postgres>>, - ) -> Result, BridgeError> { + ) -> Result, BridgeError> { let query = sqlx::query_as( "SELECT operator_idx, xonly_pk, wallet_reimburse_address, collateral_funding_txid FROM operators ORDER BY operator_idx;" ); @@ -177,7 +176,7 @@ impl Database { let data = operators .into_iter() .map(|(_, pk, addr, txid)| { - let xonly_pk = secp256k1::XOnlyPublicKey::from_str(&pk).unwrap(); + let xonly_pk = XOnlyPublicKey::from_str(&pk).unwrap(); let addr = bitcoin::Address::from_str(&addr).unwrap().assume_checked(); let txid = Txid::from_str(&txid).unwrap(); Ok((xonly_pk, addr, txid)) @@ -492,48 +491,45 @@ impl Database { &self, tx: Option<&mut sqlx::Transaction<'_, Postgres>>, deposit_outpoint: OutPoint, - ) -> Result>, BridgeError> { + ) -> Result>, BridgeError> { let query = sqlx::query_as( "SELECT pub_nonce FROM nonces WHERE deposit_outpoint = $1 ORDER BY internal_idx;", ) .bind(OutPointDB(deposit_outpoint)); - let result: Vec<(MuSigPubNonce,)> = match tx { + let result: Vec<(MusigPubNonceDB,)> = match tx { Some(tx) => query.fetch_all(&mut **tx).await?, None => query.fetch_all(&self.connection).await?, }; if result.is_empty() { Ok(None) } else { - let pub_nonces: Vec = result.into_iter().map(|(x,)| x).collect(); + let pub_nonces: Vec = result.into_iter().map(|(x,)| x.0).collect(); Ok(Some(pub_nonces)) } } - /// Verifier: save the generated sec nonce and pub nonces + /// Saves the generated pub nonces for a verifier. #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] pub async fn save_nonces( &self, tx: Option<&mut sqlx::Transaction<'_, Postgres>>, deposit_outpoint: OutPoint, - nonces: &[(MuSigSecNonce, MuSigPubNonce)], + pub_nonces: &[MusigPubNonce], ) -> Result<(), BridgeError> { - let mut query = QueryBuilder::new( - "INSERT INTO nonces (deposit_outpoint, internal_idx, sec_nonce, pub_nonce) ", - ); + let mut query = + QueryBuilder::new("INSERT INTO nonces (deposit_outpoint, internal_idx, pub_nonce) "); query.push_values( - nonces.iter().enumerate(), - |mut builder, (idx, (sec, pub_nonce))| { + pub_nonces.iter().enumerate(), + |mut builder, (idx, pub_nonce)| { builder - .push_bind(OutPointDB(deposit_outpoint)) // Bind deposit_outpoint - .push_bind(idx as i32) // Bind the index as internal_idx - .push_bind(sec) // Bind sec_nonce - .push_bind(pub_nonce); // Bind pub_nonce + .push_bind(OutPointDB(deposit_outpoint)) + .push_bind(idx as i32) + .push_bind(MusigPubNonceDB(*pub_nonce)); }, ); let query = query.build(); - // Now you can use the `query` variable in the match statement match tx { Some(tx) => query.execute(&mut **tx).await?, None => query.execute(&self.connection).await?, @@ -578,16 +574,16 @@ impl Database { Ok(Some((qr.0 .0, qr.1 .0))) } - /// Verifier: saves the sighash and returns sec and agg nonces, if the sighash is already there and different, returns error + /// Saves the sighash and returns agg nonces for the verifier. If the + /// sighash already exists and is different, returns error. #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] pub async fn save_sighashes_and_get_nonces( &self, tx: Option<&mut sqlx::Transaction<'_, Postgres>>, deposit_outpoint: OutPoint, index: usize, - sighashes: &[MuSigSigHash], - ) -> Result>, BridgeError> { - // Update the sighashes + sighashes: &[Message], + ) -> Result>, BridgeError> { let mut query = QueryBuilder::new( "WITH updated AS ( UPDATE nonces @@ -595,7 +591,9 @@ impl Database { FROM (", ); let query = query.push_values(sighashes.iter().enumerate(), |mut builder, (i, sighash)| { - builder.push_bind((index + i) as i32).push_bind(sighash); + builder + .push_bind((index + i) as i32) + .push_bind(MessageDB(*sighash)); }); let query = query @@ -605,20 +603,23 @@ impl Database { ) .push_bind(OutPointDB(deposit_outpoint)) .push( - " RETURNING nonces.internal_idx, sec_nonce, agg_nonce) - SELECT updated.sec_nonce, updated.agg_nonce - FROM updated - ORDER BY updated.internal_idx;", + " RETURNING nonces.internal_idx, agg_nonce) + SELECT updated.agg_nonce + FROM updated + ORDER BY updated.internal_idx;", ) .build_query_as(); - let result: Result, sqlx::Error> = match tx { + let result: Result, sqlx::Error> = match tx { Some(tx) => query.fetch_all(&mut **tx).await, None => query.fetch_all(&self.connection).await, }; match result { - Ok(nonces) => Ok(Some(nonces)), + Ok(nonces) => { + let nonces = nonces.into_iter().map(|x| x.0 .0).collect(); + Ok(Some(nonces)) + } Err(sqlx::Error::RowNotFound) => Ok(None), Err(e) => Err(BridgeError::DatabaseError(e)), } @@ -630,7 +631,7 @@ impl Database { &self, tx: Option<&mut sqlx::Transaction<'_, Postgres>>, deposit_outpoint: OutPoint, - agg_nonces: impl IntoIterator, + agg_nonces: impl IntoIterator, ) -> Result<(), BridgeError> { let mut query = QueryBuilder::new( "UPDATE nonces @@ -641,7 +642,9 @@ impl Database { let query = query.push_values( agg_nonces.into_iter().enumerate(), |mut builder, (i, agg_nonce)| { - builder.push_bind(i as i32).push_bind(agg_nonce); + builder + .push_bind(i as i32) + .push_bind(MusigAggNonceDB(*agg_nonce)); }, ); @@ -1118,7 +1121,8 @@ impl Database { rows.into_iter() .map(|xonly_pk| { - XOnlyPublicKey::from_slice(&xonly_pk.0).map_err(BridgeError::Secp256k1Error) + XOnlyPublicKey::from_slice(&xonly_pk.0) + .map_err(|e| BridgeError::Error(format!("Can't convert xonly pubkey: {}", e))) }) .collect() } @@ -1147,12 +1151,11 @@ impl Database { #[cfg(test)] mod tests { use super::Database; + use crate::utils::SECP; use crate::{config::BridgeConfig, initialize_database, utils::initialize_logger}; - use crate::{ - create_test_config_with_thread_name, - musig2::{nonce_pair, MuSigAggNonce, MuSigPubNonce, MuSigSecNonce}, - ByteArray32, EVMAddress, UTXO, - }; + use crate::{create_test_config_with_thread_name, musig2::nonce_pair, EVMAddress, UTXO}; + use bitcoin::key::{Keypair, Secp256k1}; + use bitcoin::secp256k1::{schnorr, SecretKey}; use bitcoin::{ block::{self, Header, Version}, BlockHash, CompactTarget, TxMerkleNode, @@ -1166,8 +1169,9 @@ mod tests { }; use borsh::BorshDeserialize; use risc0_zkvm::Receipt; + use secp256k1::musig::MusigPubNonce; + use secp256k1::rand; use secp256k1::{constants::SCHNORR_SIGNATURE_SIZE, rand::rngs::OsRng}; - use secp256k1::{schnorr, Secp256k1}; use std::{env, thread}; #[tokio::test] @@ -1232,7 +1236,6 @@ mod tests { let config = create_test_config_with_thread_name!(None); let database = Database::new(&config).await.unwrap(); - let secp = Secp256k1::new(); let xonly_public_key = XOnlyPublicKey::from_slice(&[ 0x78u8, 0x19u8, 0x90u8, 0xd7u8, 0xe2u8, 0x11u8, 0x8cu8, 0xc3u8, 0x61u8, 0xa9u8, 0x3au8, 0x6fu8, 0xccu8, 0x54u8, 0xceu8, 0x61u8, 0x1du8, 0x6du8, 0xf3u8, 0x81u8, 0x68u8, 0xd6u8, @@ -1240,7 +1243,7 @@ mod tests { ]) .unwrap(); let outpoint = OutPoint::null(); - let taproot_address = Address::p2tr(&secp, xonly_public_key, None, config.network); + let taproot_address = Address::p2tr(&SECP, xonly_public_key, None, config.network); let evm_address = EVMAddress([1u8; 20]); database .save_deposit_info( @@ -1260,172 +1263,34 @@ mod tests { assert_eq!(evm_address, db_evm_address); } - #[tokio::test] - async fn test_nonces_1() { - let config = create_test_config_with_thread_name!(None); - let db = Database::new(&config).await.unwrap(); - let secp = Secp256k1::new(); - - let outpoint = OutPoint { - txid: Txid::from_byte_array([1u8; 32]), - vout: 1, - }; - let index = 2; - let sighashes = [ByteArray32([1u8; 32])]; - let sks = [ - secp256k1::SecretKey::from_slice(&[1u8; 32]).unwrap(), - secp256k1::SecretKey::from_slice(&[2u8; 32]).unwrap(), - secp256k1::SecretKey::from_slice(&[3u8; 32]).unwrap(), - ]; - let keypairs: Vec = sks - .iter() - .map(|sk| secp256k1::Keypair::from_secret_key(&secp, sk)) - .collect(); - let nonce_pairs: Vec<(MuSigSecNonce, MuSigPubNonce)> = keypairs - .into_iter() - .map(|kp| nonce_pair(&kp, &mut OsRng)) - .collect(); - let agg_nonces: Vec = nonce_pairs - .iter() - .map(|(_, pub_nonce)| *pub_nonce) - .collect(); - db.save_nonces(None, outpoint, &nonce_pairs).await.unwrap(); - db.save_agg_nonces(None, outpoint, &agg_nonces) - .await - .unwrap(); - let db_sec_and_agg_nonces = db - .save_sighashes_and_get_nonces(None, outpoint, index, &sighashes) - .await - .unwrap() - .unwrap(); - - // Sanity checks - assert_eq!(db_sec_and_agg_nonces.len(), 1); - assert_eq!(db_sec_and_agg_nonces[0].0, nonce_pairs[index].0); - assert_eq!(db_sec_and_agg_nonces[0].1, agg_nonces[index]); - } - - #[tokio::test] - async fn test_nonces_2() { - let config = create_test_config_with_thread_name!(None); - let db = Database::new(&config).await.unwrap(); - let secp = Secp256k1::new(); - - let outpoint = OutPoint::null(); - let index = 0; - let sighashes = [ByteArray32([1u8; 32]), ByteArray32([2u8; 32])]; - let sks = [ - secp256k1::SecretKey::from_slice(&[1u8; 32]).unwrap(), - secp256k1::SecretKey::from_slice(&[2u8; 32]).unwrap(), - secp256k1::SecretKey::from_slice(&[3u8; 32]).unwrap(), - ]; - let keypairs: Vec = sks - .iter() - .map(|sk| secp256k1::Keypair::from_secret_key(&secp, sk)) - .collect(); - let nonce_pairs: Vec<(MuSigSecNonce, MuSigPubNonce)> = keypairs - .into_iter() - .map(|kp| nonce_pair(&kp, &mut OsRng)) - .collect(); - let agg_nonces: Vec = nonce_pairs - .iter() - .map(|(_, pub_nonce)| *pub_nonce) - .collect(); - db.save_nonces(None, outpoint, &nonce_pairs).await.unwrap(); - db.save_agg_nonces(None, outpoint, &agg_nonces) - .await - .unwrap(); - let db_sec_and_agg_nonces = db - .save_sighashes_and_get_nonces(None, outpoint, index, &sighashes) - .await - .unwrap() - .unwrap(); - - // Sanity checks - assert_eq!(db_sec_and_agg_nonces.len(), 2); - assert_eq!(db_sec_and_agg_nonces[0].0, nonce_pairs[index].0); - assert_eq!(db_sec_and_agg_nonces[0].1, agg_nonces[index]); - assert_eq!(db_sec_and_agg_nonces[1].0, nonce_pairs[index + 1].0); - assert_eq!(db_sec_and_agg_nonces[1].1, agg_nonces[index + 1]); - } - - #[tokio::test] - async fn test_nonces_3() { - let config = create_test_config_with_thread_name!(None); - let db = Database::new(&config).await.unwrap(); - let secp = Secp256k1::new(); - - let outpoint = OutPoint { - txid: Txid::from_byte_array([1u8; 32]), - vout: 1, - }; - let index = 2; - let mut sighashes = [ByteArray32([1u8; 32])]; - let sks = [ - secp256k1::SecretKey::from_slice(&[1u8; 32]).unwrap(), - secp256k1::SecretKey::from_slice(&[2u8; 32]).unwrap(), - secp256k1::SecretKey::from_slice(&[3u8; 32]).unwrap(), - ]; - let keypairs: Vec = sks - .iter() - .map(|sk| secp256k1::Keypair::from_secret_key(&secp, sk)) - .collect(); - let nonce_pairs: Vec<(MuSigSecNonce, MuSigPubNonce)> = keypairs - .into_iter() - .map(|kp| nonce_pair(&kp, &mut OsRng)) - .collect(); - let agg_nonces: Vec = nonce_pairs - .iter() - .map(|(_, pub_nonce)| *pub_nonce) - .collect(); - db.save_nonces(None, outpoint, &nonce_pairs).await.unwrap(); - db.save_agg_nonces(None, outpoint, &agg_nonces) - .await - .unwrap(); - let _db_sec_and_agg_nonces = db - .save_sighashes_and_get_nonces(None, outpoint, index, &sighashes) - .await - .unwrap() - .unwrap(); - - // Accidentally try to save a different sighash - sighashes[0] = ByteArray32([2u8; 32]); - let _db_sec_and_agg_nonces = db - .save_sighashes_and_get_nonces(None, outpoint, index, &sighashes) - .await - .expect_err("Should return database sighash update error"); - println!("Error: {:?}", _db_sec_and_agg_nonces); - } - #[tokio::test] async fn test_get_pub_nonces_1() { let config = create_test_config_with_thread_name!(None); let db = Database::new(&config).await.unwrap(); - let secp = Secp256k1::new(); let outpoint = OutPoint { txid: Txid::from_byte_array([1u8; 32]), vout: 1, }; let sks = [ - secp256k1::SecretKey::from_slice(&[1u8; 32]).unwrap(), - secp256k1::SecretKey::from_slice(&[2u8; 32]).unwrap(), - secp256k1::SecretKey::from_slice(&[3u8; 32]).unwrap(), + SecretKey::from_slice(&[1u8; 32]).unwrap(), + SecretKey::from_slice(&[2u8; 32]).unwrap(), + SecretKey::from_slice(&[3u8; 32]).unwrap(), ]; - let keypairs: Vec = sks + let keypairs: Vec = sks .iter() - .map(|sk| secp256k1::Keypair::from_secret_key(&secp, sk)) + .map(|sk| Keypair::from_secret_key(&SECP, sk)) .collect(); - let nonce_pairs: Vec<(MuSigSecNonce, MuSigPubNonce)> = keypairs + let pub_nonces: Vec = keypairs .into_iter() - .map(|kp| nonce_pair(&kp, &mut OsRng)) + .map(|kp| nonce_pair(&kp, &mut OsRng).unwrap().1) .collect(); - db.save_nonces(None, outpoint, &nonce_pairs).await.unwrap(); + db.save_nonces(None, outpoint, &pub_nonces).await.unwrap(); let pub_nonces = db.get_pub_nonces(None, outpoint).await.unwrap().unwrap(); // Sanity checks - assert_eq!(pub_nonces.len(), nonce_pairs.len()); - for (pub_nonce, (_, db_pub_nonce)) in pub_nonces.iter().zip(nonce_pairs.iter()) { + assert_eq!(pub_nonces.len(), pub_nonces.len()); + for (pub_nonce, db_pub_nonce) in pub_nonces.iter().zip(pub_nonces.iter()) { assert_eq!(pub_nonce, db_pub_nonce); } } @@ -1963,8 +1828,6 @@ mod tests { let config = create_test_config_with_thread_name!(None); let database = Database::new(&config).await.unwrap(); - use secp256k1::{rand, Keypair, Secp256k1, XOnlyPublicKey}; - let secp = Secp256k1::new(); let keypair1 = Keypair::new(&secp, &mut rand::thread_rng()); let xonly1 = XOnlyPublicKey::from_keypair(&keypair1).0; diff --git a/core/src/database/wrapper.rs b/core/src/database/wrapper.rs index f3a77856..16c92ef1 100644 --- a/core/src/database/wrapper.rs +++ b/core/src/database/wrapper.rs @@ -4,8 +4,10 @@ use bitcoin::{ block, consensus::{Decodable, Encodable}, hex::{DisplayHex, FromHex}, - Address, OutPoint, TxOut, Txid, + secp256k1::{schnorr, Message, PublicKey}, + Address, OutPoint, TxOut, Txid, XOnlyPublicKey, }; +use secp256k1::musig::{self, MusigAggNonce, MusigPubNonce}; use serde::{Deserialize, Serialize}; use sqlx::{ postgres::{PgArgumentBuffer, PgValueRef}, @@ -129,7 +131,7 @@ impl<'r> Decode<'r, Postgres> for TxOutDB { } #[derive(Serialize, Deserialize, sqlx::FromRow, Debug, Clone)] -pub struct SignatureDB(pub secp256k1::schnorr::Signature); +pub struct SignatureDB(pub schnorr::Signature); impl sqlx::Type for SignatureDB { fn type_info() -> sqlx::postgres::PgTypeInfo { @@ -145,13 +147,13 @@ impl Encode<'_, Postgres> for SignatureDB { impl<'r> Decode<'r, Postgres> for SignatureDB { fn decode(value: PgValueRef<'r>) -> Result { let s = as Decode>::decode(value)?; - let x: secp256k1::schnorr::Signature = secp256k1::schnorr::Signature::from_slice(&s)?; + let x: schnorr::Signature = schnorr::Signature::from_slice(&s)?; Ok(SignatureDB(x)) } } #[derive(Serialize, Deserialize, sqlx::FromRow, Debug, Clone)] -pub struct SignaturesDB(pub Vec); +pub struct SignaturesDB(pub Vec); impl sqlx::Type for SignaturesDB { fn type_info() -> sqlx::postgres::PgTypeInfo { @@ -175,12 +177,11 @@ impl<'r> Decode<'r, Postgres> for SignaturesDB { fn decode(value: PgValueRef<'r>) -> Result { let raw = as Decode>::decode(value)?; - let signatures: Vec = - borsh::from_slice::>>(&raw) - .unwrap() - .iter() - .map(|signature| secp256k1::schnorr::Signature::from_slice(signature).unwrap()) - .collect(); + let signatures: Vec = borsh::from_slice::>>(&raw) + .unwrap() + .iter() + .map(|signature| schnorr::Signature::from_slice(signature).unwrap()) + .collect(); Ok(SignaturesDB(signatures)) } @@ -235,7 +236,7 @@ impl<'r> Decode<'r, Postgres> for BlockHeaderDB { } #[derive(Serialize, Deserialize, sqlx::FromRow, Debug, Clone)] -pub struct PublicKeyDB(pub secp256k1::PublicKey); +pub struct PublicKeyDB(pub PublicKey); impl sqlx::Type for PublicKeyDB { fn type_info() -> sqlx::postgres::PgTypeInfo { @@ -244,20 +245,20 @@ impl sqlx::Type for PublicKeyDB { } impl Encode<'_, Postgres> for PublicKeyDB { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> sqlx::encode::IsNull { - let s: String = secp256k1::PublicKey::to_string(&self.0); + let s: String = PublicKey::to_string(&self.0); <&str as Encode>::encode_by_ref(&s.as_str(), buf) } } impl<'r> Decode<'r, Postgres> for PublicKeyDB { fn decode(value: PgValueRef<'r>) -> Result { let s = <&str as Decode>::decode(value)?; - let x: secp256k1::PublicKey = secp256k1::PublicKey::from_str(s)?; + let x: PublicKey = PublicKey::from_str(s)?; Ok(PublicKeyDB(x)) } } #[derive(Serialize, Deserialize, sqlx::FromRow, Debug, Clone)] -pub struct XOnlyPublicKeyDB(pub secp256k1::XOnlyPublicKey); +pub struct XOnlyPublicKeyDB(pub XOnlyPublicKey); impl sqlx::Type for XOnlyPublicKeyDB { fn type_info() -> sqlx::postgres::PgTypeInfo { @@ -266,18 +267,93 @@ impl sqlx::Type for XOnlyPublicKeyDB { } impl Encode<'_, Postgres> for XOnlyPublicKeyDB { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> sqlx::encode::IsNull { - let s: String = secp256k1::XOnlyPublicKey::to_string(&self.0); + let s: String = XOnlyPublicKey::to_string(&self.0); <&str as Encode>::encode_by_ref(&s.as_str(), buf) } } impl<'r> Decode<'r, Postgres> for XOnlyPublicKeyDB { fn decode(value: PgValueRef<'r>) -> Result { let s = <&str as Decode>::decode(value)?; - let x: secp256k1::XOnlyPublicKey = secp256k1::XOnlyPublicKey::from_str(s)?; + let x: XOnlyPublicKey = XOnlyPublicKey::from_str(s)?; Ok(XOnlyPublicKeyDB(x)) } } +#[derive(sqlx::FromRow, Debug, Clone)] +pub struct MusigPubNonceDB(pub musig::MusigPubNonce); + +impl sqlx::Type for MusigPubNonceDB { + fn type_info() -> sqlx::postgres::PgTypeInfo { + sqlx::postgres::PgTypeInfo::with_name("BYTEA") + } +} +impl Encode<'_, Postgres> for MusigPubNonceDB { + fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> sqlx::encode::IsNull { + let serialized_pub_nonces: Vec = self.0.serialize().into(); + + as Encode>::encode_by_ref(&serialized_pub_nonces, buf) + } +} +impl<'r> Decode<'r, Postgres> for MusigPubNonceDB { + fn decode(value: PgValueRef<'r>) -> Result { + let raw = as Decode>::decode(value)?; + + let pub_nonces = MusigPubNonce::from_slice(&raw).unwrap(); + + Ok(MusigPubNonceDB(pub_nonces)) + } +} + +#[derive(sqlx::FromRow, Debug, Clone)] +pub struct MusigAggNonceDB(pub musig::MusigAggNonce); + +impl sqlx::Type for MusigAggNonceDB { + fn type_info() -> sqlx::postgres::PgTypeInfo { + sqlx::postgres::PgTypeInfo::with_name("BYTEA") + } +} +impl Encode<'_, Postgres> for MusigAggNonceDB { + fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> sqlx::encode::IsNull { + let serialized_aggregated_nonces: Vec = self.0.serialize().into(); + + as Encode>::encode_by_ref(&serialized_aggregated_nonces, buf) + } +} +impl<'r> Decode<'r, Postgres> for MusigAggNonceDB { + fn decode(value: PgValueRef<'r>) -> Result { + let raw = as Decode>::decode(value)?; + + let aggregated_nonces = MusigAggNonce::from_slice(&raw).unwrap(); + + Ok(MusigAggNonceDB(aggregated_nonces)) + } +} + +#[derive(sqlx::FromRow, Debug, Clone)] +pub struct MessageDB(pub Message); + +impl sqlx::Type for MessageDB { + fn type_info() -> sqlx::postgres::PgTypeInfo { + sqlx::postgres::PgTypeInfo::with_name("BYTEA") + } +} +impl Encode<'_, Postgres> for MessageDB { + fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> sqlx::encode::IsNull { + let serialized_message: &[u8; 32] = self.0.as_ref(); + + as Encode>::encode_by_ref(&serialized_message.to_vec(), buf) + } +} +impl<'r> Decode<'r, Postgres> for MessageDB { + fn decode(value: PgValueRef<'r>) -> Result { + let raw = as Decode>::decode(value)?; + + let message = Message::from_digest(raw.try_into().unwrap()); + + Ok(MessageDB(message)) + } +} + // TODO: Improve these tests by checking conversions both ways. Note: I couldn't // find any ways to do this but it needs to be done. #[cfg(test)] @@ -288,14 +364,15 @@ mod tests { AddressDB, BlockHashDB, BlockHeaderDB, EVMAddressDB, SignatureDB, SignaturesDB, TxOutDB, TxidDB, }, - utils, EVMAddress, + utils::{self, SECP}, + EVMAddress, }; use bitcoin::{ block::{self, Version}, hashes::Hash, + secp256k1::schnorr::Signature, Amount, BlockHash, CompactTarget, OutPoint, ScriptBuf, TxMerkleNode, TxOut, Txid, }; - use secp256k1::{schnorr::Signature, SECP256K1}; use sqlx::{encode::IsNull, postgres::PgArgumentBuffer, Encode, Type}; #[test] @@ -344,7 +421,7 @@ mod tests { ); let address = bitcoin::Address::p2tr( - SECP256K1, + &SECP, *utils::UNSPENDABLE_XONLY_PUBKEY, None, bitcoin::Network::Regtest, diff --git a/core/src/errors.rs b/core/src/errors.rs index 0e99857c..c4f257a7 100644 --- a/core/src/errors.rs +++ b/core/src/errors.rs @@ -5,7 +5,7 @@ use bitcoin::{consensus::encode::FromHexError, merkle_tree::MerkleBlockError, BlockHash, Txid}; use core::fmt::Debug; use jsonrpsee::types::ErrorObject; -use musig2::secp::errors::InvalidScalarBytes; +use secp256k1::musig; use std::path::PathBuf; use thiserror::Error; @@ -13,12 +13,18 @@ use thiserror::Error; #[derive(Debug, Error)] #[non_exhaustive] pub enum BridgeError { - /// Returned when the secp256k1 crate returns an error + /// Returned when the bitcoin::secp256k1 crate returns an error #[error("Secpk256Error: {0}")] - Secp256k1Error(#[from] secp256k1::Error), + BitcoinSecp256k1Error(#[from] bitcoin::secp256k1::Error), /// Returned when the bitcoin crate returns an error in the sighash taproot module #[error("BitcoinSighashTaprootError: {0}")] BitcoinSighashTaprootError(#[from] bitcoin::sighash::TaprootError), + + #[error("Secp256k1 returned an error: {0}")] + Secp256k1Error(#[from] secp256k1::Error), + #[error("Scalar can't be build: {0}")] + Secp256k1ScalarOutOfRange(#[from] secp256k1::scalar::OutOfRangeError), + /// Returned when a non finalized deposit request is found #[error("DepositNotFinalized")] DepositNotFinalized, @@ -134,21 +140,18 @@ pub enum BridgeError { #[error("InvalidKickoffUtxo")] InvalidKickoffUtxo, - #[error("KeyAggContextError: {0}")] - KeyAggContextError(#[from] musig2::errors::KeyAggError), - - #[error("KeyAggContextTweakError: {0}")] - KeyAggContextTweakError(#[from] musig2::errors::TweakError), - - #[error("InvalidScalarBytes: {0}")] - InvalidScalarBytes(#[from] InvalidScalarBytes), + #[error("Error while generating musig nonces: {0}")] + MusigNonceGenFailed(#[from] musig::MusigNonceGenError), + #[error("Error while signing a musig member: {0}")] + MusigSignFailed(#[from] musig::MusigSignError), + #[error("Error while tweaking a musig member: {0}")] + MusigTweakFailed(#[from] musig::MusigTweakErr), + #[error("Error while parsing a musig member: {0}")] + MusigParseError(#[from] musig::ParseError), #[error("NoncesNotFound")] NoncesNotFound, - #[error("MuSig2VerifyError: {0}")] - MuSig2VerifyError(#[from] musig2::errors::VerifyError), - #[error("KickoffOutpointsNotFound")] KickoffOutpointsNotFound, #[error("DepositInfoNotFound")] @@ -184,9 +187,6 @@ pub enum BridgeError { #[error("OperatorTakesSigNotFound")] OperatorTakesSigNotFound, - #[error("Musig2 error: {0}")] - Musig2Error(#[from] musig2::secp256k1::Error), - #[error("Prover returned an error: {0}")] ProverError(String), #[error("Blockgazer can't synchronize database with active blockchain; Too deep {0}")] diff --git a/core/src/musig2.rs b/core/src/musig2.rs index ad5741f4..85c85025 100644 --- a/core/src/musig2.rs +++ b/core/src/musig2.rs @@ -1,423 +1,496 @@ -use crate::{errors::BridgeError, ByteArray32, ByteArray64, ByteArray66}; -use bitcoin::hashes::Hash; -use bitcoin::TapNodeHash; -use musig2::{sign_partial, AggNonce, KeyAggContext, SecNonce}; -use secp256k1::{rand::Rng, PublicKey}; - -// 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). -pub type MuSigPubNonce = ByteArray66; -// MuSigSecNonce consists of two scalars, so it's 64 bytes. -pub type MuSigSecNonce = ByteArray64; -// MuSigAggNonce is a scalar, so it's 32 bytes. -pub type MuSigAggNonce = ByteArray66; -// MuSigPartialSignature is a scalar, so it's 32 bytes. -pub type MuSigPartialSignature = ByteArray32; -// MuSigFinalSignature is a Schnorr signature, so it's 64 bytes. -pub type MuSigFinalSignature = ByteArray64; -// SigHash used for MuSig2 operations. -pub type MuSigSigHash = ByteArray32; -pub type MuSigNoncePair = (MuSigSecNonce, MuSigPubNonce); +//! # MuSig2 +//! +//! Helper functions for the MuSig2 signature scheme. + +use crate::{errors::BridgeError, utils::SECP}; +use bitcoin::{ + hashes::Hash, + key::Keypair, + secp256k1::{schnorr, Message, PublicKey, SecretKey}, + TapNodeHash, XOnlyPublicKey, +}; +use lazy_static::lazy_static; +use secp256k1::{ + musig::{ + new_musig_nonce_pair, MusigAggNonce, MusigKeyAggCache, MusigPartialSignature, + MusigPubNonce, MusigSecNonce, MusigSecRand, MusigSession, + }, + rand::Rng, + Scalar, SECP256K1, +}; +use sha2::{Digest, Sha256}; + +pub type MuSigNoncePair = (MusigSecNonce, MusigPubNonce); + +pub fn from_secp_xonly(xpk: secp256k1::XOnlyPublicKey) -> XOnlyPublicKey { + XOnlyPublicKey::from_slice(&xpk.serialize()).unwrap() +} + +pub fn to_secp_pk(pk: PublicKey) -> secp256k1::PublicKey { + secp256k1::PublicKey::from_slice(&pk.serialize()).unwrap() +} +pub fn from_secp_pk(pk: secp256k1::PublicKey) -> PublicKey { + PublicKey::from_slice(&pk.serialize()).unwrap() +} + +pub fn to_secp_sk(sk: SecretKey) -> secp256k1::SecretKey { + secp256k1::SecretKey::from_slice(&sk.secret_bytes()).unwrap() +} + +pub fn to_secp_kp(kp: &Keypair) -> secp256k1::Keypair { + secp256k1::Keypair::from_seckey_slice(SECP256K1, &kp.secret_bytes()).unwrap() +} +pub fn from_secp_kp(kp: &secp256k1::Keypair) -> Keypair { + Keypair::from_seckey_slice(&SECP, &kp.secret_bytes()).unwrap() +} + +pub fn from_secp_sig(sig: secp256k1::schnorr::Signature) -> schnorr::Signature { + schnorr::Signature::from_slice(&sig.to_byte_array()).unwrap() +} + +pub fn to_secp_msg(msg: &Message) -> secp256k1::Message { + secp256k1::Message::from_digest(*msg.as_ref()) +} + +/// Possible Musig2 modes. +#[derive(Debug, Clone, Copy)] +pub enum Musig2Mode { + /// No taproot tweak. + ScriptSpend, + /// Taproot tweak with aggregated public key. + OnlyKeySpend, + /// Taproot tweak with tweaked aggregated public key. + KeySpendWithScript(TapNodeHash), +} + +/// sha256(b"TapTweak") +const TAPROOT_TWEAK_TAG_DIGEST: [u8; 32] = [ + 0xe8, 0x0f, 0xe1, 0x63, 0x9c, 0x9c, 0xa0, 0x50, 0xe3, 0xaf, 0x1b, 0x39, 0xc1, 0x43, 0xc6, 0x3e, + 0x42, 0x9c, 0xbc, 0xeb, 0x15, 0xd9, 0x40, 0xfb, 0xb5, 0xc5, 0xa1, 0xf4, 0xaf, 0x57, 0xc5, 0xe9, +]; + +lazy_static! { + pub static ref TAPROOT_TWEAK_TAGGED_HASH: Sha256 = Sha256::new() + .chain_update(TAPROOT_TWEAK_TAG_DIGEST) + .chain_update(TAPROOT_TWEAK_TAG_DIGEST); +} + +fn create_key_agg_cache( + public_keys: Vec, + mode: Option, +) -> Result { + let secp_pubkeys: Vec = + public_keys.iter().map(|pk| to_secp_pk(*pk)).collect(); + let pubkeys_ref: Vec<&secp256k1::PublicKey> = secp_pubkeys.iter().collect(); + let pubkeys_ref = pubkeys_ref.as_slice(); + + let mut musig_key_agg_cache = MusigKeyAggCache::new(SECP256K1, pubkeys_ref); + let agg_key = musig_key_agg_cache.agg_pk(); + + if let Some(mode) = mode { + match mode { + Musig2Mode::ScriptSpend => (), + Musig2Mode::OnlyKeySpend => { + // sha256(C, C, IPK) where C = sha256("TapTweak") + let xonly_tweak = TAPROOT_TWEAK_TAGGED_HASH + .clone() + .chain_update(agg_key.serialize()) + .finalize(); + + musig_key_agg_cache.pubkey_xonly_tweak_add( + SECP256K1, + &Scalar::from_be_bytes(xonly_tweak.into())?, + )?; + } + Musig2Mode::KeySpendWithScript(merkle_root) => { + // sha256(C, C, IPK, s) where C = sha256("TapTweak") + let xonly_tweak = TAPROOT_TWEAK_TAGGED_HASH + .clone() + .chain_update(agg_key.serialize()) + .chain_update(merkle_root.to_raw_hash().to_byte_array()) + .finalize(); + + musig_key_agg_cache + .pubkey_ec_tweak_add(SECP256K1, &Scalar::from_be_bytes(xonly_tweak.into())?)?; + } + } + }; + + Ok(musig_key_agg_cache) +} pub trait AggregateFromPublicKeys { fn from_musig2_pks( pks: Vec, - tweak: Option, - tweak_flag: bool, - ) -> secp256k1::XOnlyPublicKey; + tweak: Option, + ) -> Result; } -impl AggregateFromPublicKeys for secp256k1::XOnlyPublicKey { - #[tracing::instrument(ret(level = tracing::Level::TRACE))] +impl AggregateFromPublicKeys for 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; - secp256k1::XOnlyPublicKey::from_slice(&musig_agg_xonly_pubkey.serialize()).unwrap() - } -} + tweak: Option, + ) -> Result { + let musig_key_agg_cache = create_key_agg_cache(pks, tweak).unwrap(); -// 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. -#[tracing::instrument(err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] -pub fn create_key_agg_ctx( - pks: Vec, - 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)?; - // 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 tweak.is_some() { - return Err(BridgeError::VecConversionError); // TODO: Change error handling. - } - Ok(key_agg_ctx_raw) + Ok(XOnlyPublicKey::from_slice( + &musig_key_agg_cache.agg_pk().serialize(), + )?) } } -// Aggregates the public nonces into a single aggregated nonce. Wrapper for the musig2::AggNonce::sum function. -#[tracing::instrument(ret(level = tracing::Level::TRACE))] -pub fn aggregate_nonces(pub_nonces: Vec) -> MuSigAggNonce { - let musig_pub_nonces: Vec = pub_nonces - .iter() - .map(|x| musig2::PubNonce::from_bytes(&x.0).unwrap()) - .collect::>(); - let musig_agg_nonce: AggNonce = AggNonce::sum(musig_pub_nonces); - ByteArray66(musig_agg_nonce.into()) +// Aggregates the public nonces into a single aggregated nonce. +pub fn aggregate_nonces(pub_nonces: Vec) -> MusigAggNonce { + let pub_nonces = pub_nonces.iter().collect::>(); + + MusigAggNonce::new(SECP256K1, pub_nonces.as_slice()) } -// Aggregates the partial signatures into a single final signature. Wrapper for the musig2::aggregate_partial_signatures function. -#[tracing::instrument(err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] +// Aggregates the partial signatures into a single aggregated signature. pub fn aggregate_partial_signatures( pks: Vec, - tweak: Option, - tweak_flag: bool, - agg_nonce: &MuSigAggNonce, - partial_sigs: Vec, - message: MuSigSigHash, -) -> Result<[u8; 64], BridgeError> { - let key_agg_ctx = create_key_agg_ctx(pks, tweak, tweak_flag)?; - let musig_partial_sigs: Vec = partial_sigs - .iter() - .map(|x| musig2::PartialSignature::from_slice(&x.0).unwrap()) - .collect::>(); - Ok(musig2::aggregate_partial_signatures( - &key_agg_ctx, - &AggNonce::from_bytes(&agg_nonce.0).unwrap(), - musig_partial_sigs, - message.0, - )?) + tweak: Option, + agg_nonce: MusigAggNonce, + partial_sigs: Vec, + message: Message, +) -> Result { + let musig_key_agg_cache = create_key_agg_cache(pks, tweak)?; + let secp_message = to_secp_msg(&message); + + let session = MusigSession::new(SECP256K1, &musig_key_agg_cache, agg_nonce, secp_message); + + let partial_sigs: Vec<&MusigPartialSignature> = partial_sigs.iter().collect(); + let final_sig = session.partial_sig_agg(&partial_sigs); + + SECP256K1.verify_schnorr( + &final_sig, + secp_message.as_ref(), + &musig_key_agg_cache.agg_pk(), + )?; + + Ok(from_secp_sig(session.partial_sig_agg(&partial_sigs))) } -// Generates a pair of nonces, one secret and one public. Wrapper for the musig2::SecNonce::build function. Be careful, -// DO NOT REUSE the same pair of nonces for multiple transactions. It will cause you to leak your secret key. For more information, -// see https://medium.com/blockstream/musig-dn-schnorr-multisignatures-with-verifiably-deterministic-nonces-27424b5df9d6#e3b6. -#[tracing::instrument(skip(rng), ret(level = tracing::Level::TRACE))] +/// Generates a pair of nonces, one secret and one public. Be careful, +/// DO NOT REUSE the same pair of nonces for multiple transactions. It will cause +/// you to leak your secret key. For more information. See: +/// https://medium.com/blockstream/musig-dn-schnorr-multisignatures-with-verifiably-deterministic-nonces-27424b5df9d6#e3b6. pub fn nonce_pair( - keypair: &secp256k1::Keypair, // TODO: Remove this field - rng: &mut impl Rng, -) -> (MuSigSecNonce, MuSigPubNonce) { - let rnd = rng.gen::<[u8; 32]>(); - let sec_nonce = SecNonce::build(rnd).build(); + keypair: &Keypair, + mut rng: &mut impl Rng, +) -> Result<(MusigSecNonce, MusigPubNonce), BridgeError> { + let musig_session_sec_rand = MusigSecRand::new(&mut rng); - let pub_nonce = ByteArray66(sec_nonce.public_nonce().into()); - let sec_nonce: [u8; 64] = sec_nonce.into(); - - (ByteArray64(sec_nonce), pub_nonce) + Ok(new_musig_nonce_pair( + SECP256K1, + musig_session_sec_rand, + None, + None, + to_secp_kp(keypair).public_key(), + None, + None, + )?) } -// We are creating the key aggregation context manually here, adding the tweaks by hand. -#[tracing::instrument(ret(level = tracing::Level::TRACE))] 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, - tweak_flag: bool, - sec_nonce: MuSigSecNonce, - agg_nonce: MuSigAggNonce, - keypair: &secp256k1::Keypair, - sighash: MuSigSigHash, -) -> MuSigPartialSignature { - let key_agg_ctx = create_key_agg_ctx(pks, tweak, tweak_flag).unwrap(); - let musig_sec_nonce = SecNonce::from_bytes(&sec_nonce.0).unwrap(); - let musig_agg_nonce = AggNonce::from_bytes(&agg_nonce.0).unwrap(); - let partial_signature: [u8; 32] = sign_partial( - &key_agg_ctx, - musig2::secp256k1::SecretKey::from_slice(&keypair.secret_key().secret_bytes()).unwrap(), - musig_sec_nonce, - &musig_agg_nonce, - sighash.0, - ) - .unwrap(); - ByteArray32(partial_signature) + tweak: Option, + sec_nonce: MusigSecNonce, + agg_nonce: MusigAggNonce, + keypair: Keypair, + sighash: Message, +) -> Result { + let musig_key_agg_cache = create_key_agg_cache(pks, tweak)?; + + let session = MusigSession::new( + SECP256K1, + &musig_key_agg_cache, + agg_nonce, + to_secp_msg(&sighash), + ); + + Ok(session.partial_sign( + SECP256K1, + sec_nonce, + &to_secp_kp(&keypair), + &musig_key_agg_cache, + )?) } #[cfg(test)] mod tests { - use super::{nonce_pair, MuSigNoncePair}; + use super::{nonce_pair, MuSigNoncePair, Musig2Mode}; use crate::{ actor::Actor, builder::{self, transaction::TxHandler}, errors::BridgeError, - musig2::{AggregateFromPublicKeys, MuSigPartialSignature}, - utils, ByteArray32, + musig2::{ + aggregate_nonces, aggregate_partial_signatures, create_key_agg_cache, from_secp_xonly, + partial_sign, AggregateFromPublicKeys, + }, + utils::{self, SECP}, }; use bitcoin::{ - hashes::Hash, opcodes::all::OP_CHECKSIG, script, Amount, OutPoint, ScriptBuf, TapNodeHash, - TxOut, Txid, + hashes::Hash, + key::Keypair, + opcodes::all::OP_CHECKSIG, + script, + secp256k1::{schnorr, Message, PublicKey}, + Amount, OutPoint, ScriptBuf, TapNodeHash, TxOut, Txid, XOnlyPublicKey, }; - use secp256k1::{rand::Rng, Keypair, Message, XOnlyPublicKey, SECP256K1}; + use secp256k1::{musig::MusigPartialSignature, rand::Rng}; use std::vec; - // 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(); + /// Generates random key and nonce pairs for a given number of signers. + fn create_key_and_nonce_pairs(num_signers: usize) -> (Vec, Vec) { + let mut key_pairs = Vec::new(); + let mut nonce_pairs = Vec::new(); + for _ in 0..num_signers { - keypair_vec.push(Keypair::new(SECP256K1, &mut secp256k1::rand::thread_rng())); + let key_pair = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng()); + let nonce_pair = + nonce_pair(&key_pair, &mut bitcoin::secp256k1::rand::thread_rng()).unwrap(); + + key_pairs.push(key_pair); + nonce_pairs.push(nonce_pair); } - let nonce_pair_vec: Vec = keypair_vec - .iter() - .map(|keypair| nonce_pair(keypair, &mut secp256k1::rand::thread_rng())) - .collect(); - (keypair_vec, nonce_pair_vec) + + (key_pairs, nonce_pairs) } - // Test the MuSig2 signature scheme raw (without a tweak). #[test] - fn test_musig2_raw() { - // Generate a test setup with 3 signers - let (kp_vec, nonce_pair_vec) = generate_test_setup(3); - // Generate a random message - let message: [u8; 32] = secp256k1::rand::thread_rng().gen(); - // Extract the public keys - let pks = kp_vec + fn musig2_raw_without_a_tweak() { + let (key_pairs, nonce_pairs) = create_key_and_nonce_pairs(3); + let message = Message::from_digest(secp256k1::rand::thread_rng().gen()); + + let public_keys = key_pairs .iter() .map(|kp| kp.public_key()) - .collect::>(); - // Create the key aggregation context - 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).collect()); - // Extract the aggregated public key - let musig_agg_pubkey: musig2::secp256k1::PublicKey = key_agg_ctx.aggregated_pubkey(); - // Calculate the partial signatures - let partial_sigs: Vec = kp_vec - .iter() - .zip(nonce_pair_vec.iter()) + .collect::>(); + let agg_pk = XOnlyPublicKey::from_musig2_pks(public_keys.clone(), None).unwrap(); + + let aggregated_nonce = super::aggregate_nonces( + nonce_pairs + .iter() + .map(|(_, musig_pub_nonce)| *musig_pub_nonce) + .collect(), + ); + + let partial_sigs = key_pairs + .into_iter() + .zip(nonce_pairs) .map(|(kp, nonce_pair)| { super::partial_sign( - pks.clone(), + public_keys.clone(), None, - false, nonce_pair.0, - agg_nonce, + aggregated_nonce, kp, - ByteArray32(message), + message, ) + .unwrap() }) - .collect(); - // Aggregate the partial signatures into a final signature - let final_signature: [u8; 64] = super::aggregate_partial_signatures( - pks, + .collect::>(); + + let final_signature = super::aggregate_partial_signatures( + public_keys.clone(), None, - false, - &agg_nonce, + aggregated_nonce, partial_sigs, - ByteArray32(message), + message, ) .unwrap(); - musig2::verify_single(musig_agg_pubkey, final_signature, message) - .expect("Verification failed!"); - println!("MuSig2 signature verified successfully!"); + + SECP.verify_schnorr(&final_signature, &message, &agg_pk) + .unwrap(); } - // Test that the verification fails if one of the partial signatures is invalid. #[test] - fn test_musig2_raw_fail() { - let kp_0 = secp256k1::Keypair::new(SECP256K1, &mut secp256k1::rand::thread_rng()); - let kp_1 = secp256k1::Keypair::new(SECP256K1, &mut secp256k1::rand::thread_rng()); - let kp_2 = secp256k1::Keypair::new(SECP256K1, &mut secp256k1::rand::thread_rng()); - let message: [u8; 32] = secp256k1::rand::thread_rng().gen(); + fn musig2_raw_fail_if_partial_sigs_invalid() { + let kp_0 = Keypair::new(&SECP, &mut secp256k1::rand::thread_rng()); + let kp_1 = Keypair::new(&SECP, &mut secp256k1::rand::thread_rng()); + let kp_2 = Keypair::new(&SECP, &mut secp256k1::rand::thread_rng()); + + let message = Message::from_digest(secp256k1::rand::thread_rng().gen()); + let pks = vec![kp_0.public_key(), kp_1.public_key(), kp_2.public_key()]; + let (sec_nonce_0, pub_nonce_0) = - super::nonce_pair(&kp_0, &mut secp256k1::rand::thread_rng()); + super::nonce_pair(&kp_0, &mut secp256k1::rand::thread_rng()).unwrap(); let (sec_nonce_1, pub_nonce_1) = - super::nonce_pair(&kp_1, &mut secp256k1::rand::thread_rng()); + super::nonce_pair(&kp_1, &mut secp256k1::rand::thread_rng()).unwrap(); let (sec_nonce_2, pub_nonce_2) = - super::nonce_pair(&kp_2, &mut secp256k1::rand::thread_rng()); + super::nonce_pair(&kp_2, &mut secp256k1::rand::thread_rng()).unwrap(); + 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(), - None, - false, - sec_nonce_0, - agg_nonce, - &kp_0, - ByteArray32(message), - ); - let partial_sig_1 = super::partial_sign( - pks.clone(), - None, - false, - sec_nonce_1, - agg_nonce, - &kp_1, - ByteArray32(message), - ); + + let partial_sig_0 = + super::partial_sign(pks.clone(), None, sec_nonce_0, agg_nonce, kp_0, message).unwrap(); + let partial_sig_1 = + super::partial_sign(pks.clone(), None, sec_nonce_1, agg_nonce, kp_1, message).unwrap(); // Oops, a verifier accidentally added some tweak! let partial_sig_2 = super::partial_sign( pks.clone(), - Some(TapNodeHash::from_slice(&[1u8; 32]).unwrap()), - true, + Some(Musig2Mode::KeySpendWithScript( + TapNodeHash::from_slice(&[1u8; 32]).unwrap(), + )), sec_nonce_2, agg_nonce, - &kp_2, - ByteArray32(message), - ); + kp_2, + message, + ) + .unwrap(); 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, - false, - &agg_nonce, - partial_sigs, - ByteArray32(message), - ); + + let final_signature: Result = + super::aggregate_partial_signatures(pks, None, agg_nonce, partial_sigs, message); + assert!(final_signature.is_err()); } - // Test the MuSig2 signature scheme with a tweak. #[test] - fn test_musig2_tweak() { - let (kp_vec, nonce_pair_vec) = generate_test_setup(3); - let message: [u8; 32] = secp256k1::rand::thread_rng().gen(); + fn musig2_sig_with_tweak() { + let (key_pairs, nonce_pairs) = create_key_and_nonce_pairs(3); + let message = Message::from_digest(secp256k1::rand::thread_rng().gen()); let tweak: [u8; 32] = secp256k1::rand::thread_rng().gen(); - let pks = kp_vec + + let public_keys = key_pairs .iter() .map(|kp| kp.public_key()) - .collect::>(); - let key_agg_ctx = super::create_key_agg_ctx( - pks.clone(), - Some(TapNodeHash::from_slice(&tweak).unwrap()), - true, + .collect::>(); + let aggregated_pk: XOnlyPublicKey = XOnlyPublicKey::from_musig2_pks( + public_keys.clone(), + Some(Musig2Mode::KeySpendWithScript( + TapNodeHash::from_slice(&tweak).unwrap(), + )), ) .unwrap(); - let agg_nonce = super::aggregate_nonces(nonce_pair_vec.iter().map(|x| x.1).collect()); - let musig_agg_pubkey: musig2::secp256k1::PublicKey = key_agg_ctx.aggregated_pubkey(); - let partial_sigs: Vec = kp_vec - .iter() - .zip(nonce_pair_vec.iter()) + + let aggregated_nonce = super::aggregate_nonces( + nonce_pairs + .iter() + .map(|(_, musig_pub_nonce)| *musig_pub_nonce) + .collect(), + ); + + let partial_sigs = key_pairs + .into_iter() + .zip(nonce_pairs) .map(|(kp, nonce_pair)| { super::partial_sign( - pks.clone(), - Some(TapNodeHash::from_slice(&tweak).unwrap()), - true, + public_keys.clone(), + Some(Musig2Mode::KeySpendWithScript( + TapNodeHash::from_slice(&tweak).unwrap(), + )), nonce_pair.0, - agg_nonce, + aggregated_nonce, kp, - ByteArray32(message), + message, ) + .unwrap() }) - .collect(); - let final_signature: [u8; 64] = super::aggregate_partial_signatures( - pks, - Some(TapNodeHash::from_slice(&tweak).unwrap()), - true, - &agg_nonce, + .collect::>(); + + let final_signature = super::aggregate_partial_signatures( + public_keys, + Some(Musig2Mode::KeySpendWithScript( + TapNodeHash::from_slice(&tweak).unwrap(), + )), + aggregated_nonce, partial_sigs, - ByteArray32(message), + message, ) .unwrap(); - musig2::verify_single(musig_agg_pubkey, final_signature, message) - .expect("Verification failed!"); - println!("MuSig2 signature verified successfully!"); + + SECP.verify_schnorr(&final_signature, &message, &aggregated_pk) + .unwrap(); } #[test] - fn test_musig2_tweak_fail() { - let kp_0 = secp256k1::Keypair::new(SECP256K1, &mut secp256k1::rand::thread_rng()); - let kp_1 = secp256k1::Keypair::new(SECP256K1, &mut secp256k1::rand::thread_rng()); - let kp_2 = secp256k1::Keypair::new(SECP256K1, &mut secp256k1::rand::thread_rng()); - let message: [u8; 32] = secp256k1::rand::thread_rng().gen(); + fn musig2_tweak_fail() { + let kp_0 = Keypair::new(&SECP, &mut secp256k1::rand::thread_rng()); + let kp_1 = Keypair::new(&SECP, &mut secp256k1::rand::thread_rng()); + let kp_2 = Keypair::new(&SECP, &mut secp256k1::rand::thread_rng()); + + let message = Message::from_digest(secp256k1::rand::thread_rng().gen::<[u8; 32]>()); + let tweak: [u8; 32] = secp256k1::rand::thread_rng().gen(); + let pks = vec![kp_0.public_key(), kp_1.public_key(), kp_2.public_key()]; + let (sec_nonce_0, pub_nonce_0) = - super::nonce_pair(&kp_0, &mut secp256k1::rand::thread_rng()); + super::nonce_pair(&kp_0, &mut secp256k1::rand::thread_rng()).unwrap(); let (sec_nonce_1, pub_nonce_1) = - super::nonce_pair(&kp_1, &mut secp256k1::rand::thread_rng()); + super::nonce_pair(&kp_1, &mut secp256k1::rand::thread_rng()).unwrap(); let (sec_nonce_2, pub_nonce_2) = - super::nonce_pair(&kp_2, &mut secp256k1::rand::thread_rng()); + super::nonce_pair(&kp_2, &mut secp256k1::rand::thread_rng()).unwrap(); + 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(TapNodeHash::from_slice(&tweak).unwrap()), - true, + Some(Musig2Mode::KeySpendWithScript( + TapNodeHash::from_slice(&tweak).unwrap(), + )), sec_nonce_0, agg_nonce, - &kp_0, - ByteArray32(message), - ); + kp_0, + message, + ) + .unwrap(); let partial_sig_1 = super::partial_sign( pks.clone(), - Some(TapNodeHash::from_slice(&tweak).unwrap()), - true, + Some(Musig2Mode::KeySpendWithScript( + TapNodeHash::from_slice(&tweak).unwrap(), + )), sec_nonce_1, agg_nonce, - &kp_1, - ByteArray32(message), - ); + kp_1, + message, + ) + .unwrap(); // Oops, a verifier accidentally forgot to put the tweak! - let partial_sig_2 = super::partial_sign( - pks.clone(), - None, - false, - sec_nonce_2, - agg_nonce, - &kp_2, - ByteArray32(message), - ); + let partial_sig_2 = + super::partial_sign(pks.clone(), None, sec_nonce_2, agg_nonce, kp_2, message).unwrap(); let partial_sigs = vec![partial_sig_0, partial_sig_1, partial_sig_2]; + let final_signature = super::aggregate_partial_signatures( pks, - Some(TapNodeHash::from_slice(&tweak).unwrap()), - true, - &agg_nonce, + Some(Musig2Mode::KeySpendWithScript( + TapNodeHash::from_slice(&tweak).unwrap(), + )), + agg_nonce, partial_sigs, - ByteArray32(message), + message, ); + assert!(final_signature.is_err()); } - // Test the MuSig2 signature scheme with a dummy key spend. #[test] - fn test_musig2_dummy_key_spend() { - let (kp_vec, nonce_pair_vec) = generate_test_setup(2); - let pks = kp_vec + fn musig2_key_spend() { + let (key_pairs, nonce_pairs) = create_key_and_nonce_pairs(2); + let public_keys = key_pairs .iter() - .map(|kp| kp.public_key()) - .collect::>(); - let key_agg_ctx = super::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 agg_nonce = super::aggregate_nonces(nonce_pair_vec.iter().map(|x| x.1).collect()); + .map(|key_pair| key_pair.public_key()) + .collect::>(); + + let untweaked_xonly_pubkey = + XOnlyPublicKey::from_musig2_pks(public_keys.clone(), None).unwrap(); + + let agg_nonce = super::aggregate_nonces( + nonce_pairs + .iter() + .map(|(_, musig_pub_nonce)| *musig_pub_nonce) + .collect(), + ); + let dummy_script = script::Builder::new().push_int(1).into_script(); let scripts: Vec = vec![dummy_script]; let receiving_address = bitcoin::Address::p2tr( - SECP256K1, + &SECP, *utils::UNSPENDABLE_XONLY_PUBKEY, None, bitcoin::Network::Regtest, @@ -428,6 +501,7 @@ mod tests { Some(untweaked_xonly_pubkey), bitcoin::Network::Regtest, ); + let prevout = TxOut { value: Amount::from_sat(100_000_000), script_pubkey: sending_address.script_pubkey(), @@ -436,6 +510,7 @@ mod tests { txid: Txid::from_byte_array([0u8; 32]), vout: 0, }; + let tx_outs = builder::transaction::create_tx_outs(vec![( Amount::from_sat(99_000_000), receiving_address.script_pubkey(), @@ -451,66 +526,69 @@ mod tests { out_scripts: vec![vec![]], out_taproot_spend_infos: vec![None], }; - let message = Actor::convert_tx_to_sighash_pubkey_spend(&mut tx_details, 0) - .unwrap() - .to_byte_array(); - let merkle_root = sending_address_spend_info.merkle_root(); - let partial_sigs: Vec = kp_vec - .iter() - .zip(nonce_pair_vec.iter()) + + let message = Message::from_digest( + Actor::convert_tx_to_sighash_pubkey_spend(&mut tx_details, 0) + .unwrap() + .to_byte_array(), + ); + let merkle_root = sending_address_spend_info.merkle_root().unwrap(); + + let partial_sigs: Vec = key_pairs + .into_iter() + .zip(nonce_pairs) .map(|(kp, nonce_pair)| { super::partial_sign( - pks.clone(), - merkle_root, - true, + public_keys.clone(), + Some(Musig2Mode::KeySpendWithScript(merkle_root)), nonce_pair.0, agg_nonce, kp, - ByteArray32(message), + message, ) + .unwrap() }) .collect(); - let final_signature: [u8; 64] = super::aggregate_partial_signatures( - pks.clone(), - merkle_root, - true, - &agg_nonce, + + let final_signature = super::aggregate_partial_signatures( + public_keys.clone(), + Some(Musig2Mode::KeySpendWithScript(merkle_root)), + agg_nonce, partial_sigs, - ByteArray32(message), + message, ) .unwrap(); - 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!"); - SECP256K1 - .verify_schnorr( - &secp256k1::schnorr::Signature::from_slice(&final_signature).unwrap(), - &Message::from_digest(message), - &musig_agg_xonly_pubkey_wrapped, - ) + + let musig_agg_xonly_pubkey = XOnlyPublicKey::from_musig2_pks( + public_keys, + Some(Musig2Mode::KeySpendWithScript(merkle_root)), + ) + .unwrap(); + + SECP.verify_schnorr(&final_signature, &message, &musig_agg_xonly_pubkey) .unwrap(); - println!("MuSig2 signature verified successfully!"); } - // Test the MuSig2 signature scheme with a dummy script spend. #[test] - fn test_musig2_dummy_script_spend() { - let (kp_vec, nonce_pair_vec) = generate_test_setup(2); - let pks = kp_vec + fn musig2_script_spend() { + let (key_pairs, nonce_pairs) = create_key_and_nonce_pairs(2); + let public_keys = key_pairs .iter() - .map(|kp| kp.public_key()) - .collect::>(); - let agg_nonce = super::aggregate_nonces(nonce_pair_vec.iter().map(|x| x.1).collect()); + .map(|key_pair| key_pair.public_key()) + .collect::>(); + + let agg_nonce = super::aggregate_nonces(nonce_pairs.iter().map(|x| x.1).collect()); let musig_agg_xonly_pubkey_wrapped = - XOnlyPublicKey::from_musig2_pks(pks.clone(), None, false); + XOnlyPublicKey::from_musig2_pks(public_keys.clone(), None).unwrap(); + let musig2_script = bitcoin::script::Builder::new() .push_x_only_key(&musig_agg_xonly_pubkey_wrapped) .push_opcode(OP_CHECKSIG) .into_script(); let scripts: Vec = vec![musig2_script]; + let receiving_address = bitcoin::Address::p2tr( - SECP256K1, + &SECP, *utils::UNSPENDABLE_XONLY_PUBKEY, None, bitcoin::Network::Regtest, @@ -521,6 +599,7 @@ mod tests { None, bitcoin::Network::Regtest, ); + let prevout = TxOut { value: Amount::from_sat(100_000_000), script_pubkey: sending_address.script_pubkey(), @@ -529,6 +608,7 @@ mod tests { txid: Txid::from_byte_array([0u8; 32]), vout: 0, }; + let tx_outs = builder::transaction::create_tx_outs(vec![( Amount::from_sat(99_000_000), receiving_address.script_pubkey(), @@ -544,43 +624,134 @@ mod tests { out_scripts: vec![vec![]], out_taproot_spend_infos: vec![None], }; - let message = Actor::convert_tx_to_sighash_script_spend(&mut tx_details, 0, 0) - .unwrap() - .to_byte_array(); - let partial_sigs: Vec = kp_vec - .iter() - .zip(nonce_pair_vec.iter()) + let message = Message::from_digest( + Actor::convert_tx_to_sighash_script_spend(&mut tx_details, 0, 0) + .unwrap() + .to_byte_array(), + ); + + let partial_sigs: Vec = key_pairs + .into_iter() + .zip(nonce_pairs) .map(|(kp, nonce_pair)| { super::partial_sign( - pks.clone(), + public_keys.clone(), None, - false, nonce_pair.0, agg_nonce, kp, - ByteArray32(message), + message, ) + .unwrap() }) .collect(); - let final_signature: [u8; 64] = super::aggregate_partial_signatures( - pks, + + let final_signature = super::aggregate_partial_signatures( + public_keys, None, - false, - &agg_nonce, + agg_nonce, partial_sigs, - ByteArray32(message), + message, ) .unwrap(); - // musig2::verify_single(musig_agg_pubkey, &final_signature, message) - // .expect("Verification failed!"); - SECP256K1 - .verify_schnorr( - &secp256k1::schnorr::Signature::from_slice(&final_signature).unwrap(), - &Message::from_digest(message), - &musig_agg_xonly_pubkey_wrapped, - ) + + SECP.verify_schnorr(&final_signature, &message, &musig_agg_xonly_pubkey_wrapped) .unwrap(); - println!("MuSig2 signature verified successfully!"); + } + + #[test] + fn different_aggregated_keys_for_different_musig2_modes() { + let kp1 = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng()); + let kp2 = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng()); + let public_keys = vec![kp1.public_key(), kp2.public_key()]; + + let key_agg_cache = create_key_agg_cache(public_keys.clone(), None).unwrap(); + let agg_pk_no_tweak = from_secp_xonly(key_agg_cache.agg_pk()); + + let key_agg_cache = + create_key_agg_cache(public_keys.clone(), Some(Musig2Mode::ScriptSpend)).unwrap(); + let agg_pk_script_spend = from_secp_xonly(key_agg_cache.agg_pk()); + + let key_agg_cache = + create_key_agg_cache(public_keys.clone(), Some(Musig2Mode::OnlyKeySpend)).unwrap(); + let agg_pk_key_tweak = from_secp_xonly(key_agg_cache.agg_pk()); + + let key_agg_cache = create_key_agg_cache( + public_keys.clone(), + Some(Musig2Mode::KeySpendWithScript( + TapNodeHash::from_slice(&[1u8; 32]).unwrap(), + )), + ) + .unwrap(); + let agg_pk_script_tweak = from_secp_xonly(key_agg_cache.agg_pk()); + + assert_eq!(agg_pk_no_tweak, agg_pk_script_spend); + + assert_ne!(agg_pk_no_tweak, agg_pk_script_tweak); + assert_ne!(agg_pk_no_tweak, agg_pk_key_tweak); + assert_ne!(agg_pk_script_tweak, agg_pk_key_tweak); + assert_ne!(agg_pk_script_tweak, agg_pk_script_spend); + assert_ne!(agg_pk_key_tweak, agg_pk_script_spend); + } + + #[test] + fn signing_checks_for_different_musig2_modes() { + let kp1 = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng()); + let kp2 = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng()); + let public_keys = vec![kp1.public_key(), kp2.public_key()]; + + let message = Message::from_digest(secp256k1::rand::thread_rng().gen()); + let key_spend_with_script_tweak = + Musig2Mode::KeySpendWithScript(TapNodeHash::from_slice(&[0x45u8; 32]).unwrap()); + + let key_agg_cache = + create_key_agg_cache(public_keys.clone(), Some(key_spend_with_script_tweak)).unwrap(); + let agg_pk_script_tweak = from_secp_xonly(key_agg_cache.agg_pk()); + + let (sec_nonce1, pub_nonce1) = + nonce_pair(&kp1, &mut bitcoin::secp256k1::rand::thread_rng()).unwrap(); + let (sec_nonce2, pub_nonce2) = + nonce_pair(&kp2, &mut bitcoin::secp256k1::rand::thread_rng()).unwrap(); + let agg_nonce = aggregate_nonces(vec![pub_nonce1, pub_nonce2]); + + let partial_sig1 = partial_sign( + public_keys.clone(), + Some(key_spend_with_script_tweak), + sec_nonce1, + agg_nonce, + kp1, + message, + ) + .unwrap(); + let partial_sig2 = partial_sign( + public_keys.clone(), + Some(key_spend_with_script_tweak), + sec_nonce2, + agg_nonce, + kp2, + message, + ) + .unwrap(); + + let final_sig = aggregate_partial_signatures( + public_keys.clone(), + Some(key_spend_with_script_tweak), + agg_nonce, + vec![partial_sig1, partial_sig2], + message, + ) + .unwrap(); + + SECP.verify_schnorr(&final_sig, &message, &agg_pk_script_tweak) + .unwrap(); + + // Verification will fail with a untweaked aggregate public key against + // a signature created with a tweaked aggregate public key. + let key_agg_cache = create_key_agg_cache(public_keys.clone(), None).unwrap(); + let agg_pk_no_tweak = from_secp_xonly(key_agg_cache.agg_pk()); + assert!(SECP + .verify_schnorr(&final_sig, &message, &agg_pk_no_tweak) + .is_err()); } } diff --git a/core/src/operator.rs b/core/src/operator.rs index 75d9d2d4..f9bf83fe 100644 --- a/core/src/operator.rs +++ b/core/src/operator.rs @@ -7,20 +7,20 @@ use crate::database::Database; use crate::errors::BridgeError; use crate::extended_rpc::ExtendedRpc; use crate::musig2::AggregateFromPublicKeys; -use crate::utils::handle_taproot_witness_new; +use crate::utils::{handle_taproot_witness_new, SECP}; use crate::{utils, EVMAddress, UTXO}; use bitcoin::address::NetworkUnchecked; use bitcoin::consensus::deserialize; use bitcoin::hashes::Hash; use bitcoin::script::PushBytesBuf; +use bitcoin::secp256k1::{schnorr, Message}; use bitcoin::sighash::SighashCache; -use bitcoin::{Address, Amount, OutPoint, TapSighash, Transaction, TxOut, Txid}; +use bitcoin::{Address, Amount, OutPoint, TapSighash, Transaction, TxOut, Txid, XOnlyPublicKey}; use bitcoincore_rpc::{RawTx, RpcApi}; use bitvm::signatures::winternitz; use jsonrpsee::core::client::ClientT; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::rpc_params; -use secp256k1::{schnorr, Message, SECP256K1}; use serde_json::json; #[derive(Debug, Clone)] @@ -29,7 +29,7 @@ pub struct Operator { pub db: Database, pub(crate) signer: Actor, pub(crate) config: BridgeConfig, - nofn_xonly_pk: secp256k1::XOnlyPublicKey, + nofn_xonly_pk: XOnlyPublicKey, pub(crate) idx: usize, citrea_client: Option, } @@ -48,11 +48,8 @@ impl Operator { let db = Database::new(&config).await?; - let nofn_xonly_pk = secp256k1::XOnlyPublicKey::from_musig2_pks( - config.verifiers_public_keys.clone(), - None, - false, - ); + let nofn_xonly_pk = + XOnlyPublicKey::from_musig2_pks(config.verifiers_public_keys.clone(), None)?; let idx = config .operators_xonly_pks .iter() @@ -135,7 +132,7 @@ impl Operator { deposit_outpoint: OutPoint, recovery_taproot_address: Address, evm_address: EVMAddress, - ) -> Result<(UTXO, secp256k1::schnorr::Signature), BridgeError> { + ) -> Result<(UTXO, schnorr::Signature), BridgeError> { tracing::info!( "New deposit request for UTXO: {:?}, EVM address: {:?} and recovery taproot address of: {:?}", deposit_outpoint, @@ -421,9 +418,8 @@ impl Operator { return Err(BridgeError::NotEnoughFeeForOperator); } - let user_xonly_pk = secp256k1::XOnlyPublicKey::from_slice( - &input_utxo.txout.script_pubkey.as_bytes()[2..34], - )?; + let user_xonly_pk = + XOnlyPublicKey::from_slice(&input_utxo.txout.script_pubkey.as_bytes()[2..34])?; let tx_ins = builder::transaction::create_tx_ins(vec![input_utxo.outpoint]); let tx_outs = vec![output_txout.clone()]; @@ -442,7 +438,7 @@ impl Operator { }; tx.input[0].witness.push(user_sig_wrapped.serialize()); - SECP256K1.verify_schnorr( + SECP.verify_schnorr( &user_sig, &Message::from_digest(*sighash.as_byte_array()), &user_xonly_pk, diff --git a/core/src/rpc/aggregator.rs b/core/src/rpc/aggregator.rs index f89b0826..e3a76202 100644 --- a/core/src/rpc/aggregator.rs +++ b/core/src/rpc/aggregator.rs @@ -7,18 +7,21 @@ use crate::{ aggregator::Aggregator, builder::sighash::{calculate_num_required_sigs, create_nofn_sighash_stream}, errors::BridgeError, - musig2::{aggregate_nonces, MuSigPubNonce}, + musig2::aggregate_nonces, rpc::clementine::{self, DepositSignSession}, - ByteArray32, ByteArray66, EVMAddress, + EVMAddress, }; -use bitcoin::{hashes::Hash, Amount, TapSighash}; +use bitcoin::hashes::Hash; +use bitcoin::secp256k1::{Message, PublicKey}; +use bitcoin::{Amount, TapSighash}; use futures::{future::try_join_all, stream::BoxStream, FutureExt, Stream, StreamExt}; +use secp256k1::musig::{MusigAggNonce, MusigPartialSignature, MusigPubNonce}; use std::thread; use tokio::sync::mpsc::{channel, Receiver, Sender}; use tonic::{async_trait, Request, Response, Status, Streaming}; struct AggNonceQueueItem { - agg_nonce: ByteArray66, + agg_nonce: MusigAggNonce, sighash: TapSighash, } @@ -29,7 +32,7 @@ struct FinalSigQueueItem { /// Collects public nonces from given streams and aggregates them. async fn nonce_aggregator( mut nonce_streams: Vec< - impl Stream> + Unpin + Send + 'static, + impl Stream> + Unpin + Send + 'static, >, mut sighash_stream: impl Stream> + Unpin + Send + 'static, agg_nonce_sender: Sender, @@ -70,12 +73,12 @@ async fn nonce_distributor( Streaming, Sender, )>, - partial_sig_sender: Sender<(Vec, AggNonceQueueItem)>, + partial_sig_sender: Sender<(Vec, AggNonceQueueItem)>, ) -> Result<(), BridgeError> { while let Some(queue_item) = agg_nonce_receiver.recv().await { let agg_nonce_wrapped = clementine::VerifierDepositSignParams { params: Some(clementine::verifier_deposit_sign_params::Params::AggNonce( - queue_item.agg_nonce.0.to_vec(), + queue_item.agg_nonce.serialize().to_vec(), )), }; @@ -94,7 +97,7 @@ async fn nonce_distributor( .await? .ok_or(BridgeError::Error("No partial sig received".into()))?; - Ok::<_, BridgeError>(ByteArray32(partial_sig.partial_sig.try_into().unwrap())) + Ok::<_, BridgeError>(MusigPartialSignature::from_slice(&partial_sig.partial_sig)?) })) .await?; @@ -111,23 +114,22 @@ async fn nonce_distributor( /// Collects partial signatures from given stream and aggregates them. async fn signature_aggregator( - mut partial_sig_receiver: Receiver<(Vec, AggNonceQueueItem)>, - verifiers_public_keys: Vec, + mut partial_sig_receiver: Receiver<(Vec, AggNonceQueueItem)>, + verifiers_public_keys: Vec, final_sig_sender: Sender, ) -> Result<(), BridgeError> { while let Some((partial_sigs, queue_item)) = partial_sig_receiver.recv().await { let final_sig = crate::musig2::aggregate_partial_signatures( verifiers_public_keys.clone(), None, - false, - &queue_item.agg_nonce, + queue_item.agg_nonce, partial_sigs, - ByteArray32(queue_item.sighash.to_byte_array()), + Message::from_digest(queue_item.sighash.as_raw_hash().to_byte_array()), )?; final_sig_sender .send(FinalSigQueueItem { - final_sig: final_sig.to_vec(), + final_sig: final_sig.serialize().to_vec(), }) .await .map_err(|e| { @@ -166,14 +168,14 @@ async fn signature_distributor( /// # Returns /// /// - Vec<[`clementine::NonceGenFirstResponse`]>: First response from each verifier -/// - Vec>>: Stream of nonces from each verifier +/// - Vec>>: Stream of nonces from each verifier async fn create_nonce_streams( verifier_clients: Vec>, num_nonces: u32, ) -> Result< ( Vec, - Vec>>, + Vec>>, ), BridgeError, > { @@ -219,14 +221,14 @@ async fn create_nonce_streams( })) .await?; - let transformed_streams: Vec>> = nonce_streams + let transformed_streams = nonce_streams .into_iter() .map(|stream| { stream .map(|result| Aggregator::extract_pub_nonce(result?.response)) .boxed() }) - .collect(); + .collect::>(); Ok((first_responses, transformed_streams)) } @@ -235,14 +237,13 @@ impl Aggregator { // Extracts pub_nonce from given stream. fn extract_pub_nonce( response: Option, - ) -> Result { + ) -> Result { match response .ok_or_else(|| BridgeError::Error("NonceGen response is empty".to_string()))? { - clementine::nonce_gen_response::Response::PubNonce(pub_nonce) => pub_nonce - .try_into() - .map(ByteArray66) - .map_err(|_| BridgeError::Error("PubNonce should be exactly 66 bytes".to_string())), + clementine::nonce_gen_response::Response::PubNonce(pub_nonce) => { + Ok(MusigPubNonce::from_slice(&pub_nonce)?) + } _ => Err(BridgeError::Error( "Expected PubNonce in response".to_string(), )), diff --git a/core/src/rpc/verifier.rs b/core/src/rpc/verifier.rs index 4ff58804..9a192475 100644 --- a/core/src/rpc/verifier.rs +++ b/core/src/rpc/verifier.rs @@ -9,17 +9,22 @@ use crate::{ sighash::{calculate_num_required_sigs, create_nofn_sighash_stream}, }, errors::BridgeError, - musig2::{self, MuSigPubNonce, MuSigSecNonce}, + musig2::{self}, sha256_hash, + utils::{self, SECP}, verifier::{NofN, NonceSession, Verifier}, - ByteArray32, ByteArray66, EVMAddress, + EVMAddress, +}; +use bitcoin::{ + hashes::Hash, + secp256k1::{schnorr, Message, PublicKey, SecretKey}, + Amount, TapSighash, Txid, XOnlyPublicKey, }; -use bitcoin::{hashes::Hash, Amount, TapSighash, Txid}; use bitvm::{ bridge::transactions::signing_winternitz::WinternitzPublicKey, signatures::winternitz, }; use futures::StreamExt; -use secp256k1::{schnorr, Message, XOnlyPublicKey, SECP256K1}; +use secp256k1::musig::{MusigAggNonce, MusigPubNonce, MusigSecNonce}; use std::{pin::pin, str::FromStr}; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; @@ -59,11 +64,11 @@ impl ClementineVerifier for Verifier { } // Extract the public keys from the request - let verifiers_public_keys: Vec = req + let verifiers_public_keys: Vec = req .into_inner() .verifier_public_keys .iter() - .map(|pk| secp256k1::PublicKey::from_slice(pk).unwrap()) + .map(|pk| PublicKey::from_slice(pk).unwrap()) .collect(); let nofn = NofN::new(self.signer.public_key, verifiers_public_keys.clone()); @@ -88,7 +93,7 @@ impl ClementineVerifier for Verifier { .operator_details .ok_or(BridgeError::Error("No operator details".to_string()))?; - let operator_xonly_pk = secp256k1::XOnlyPublicKey::from_str(&operator_config.xonly_pk) + let operator_xonly_pk = XOnlyPublicKey::from_str(&operator_config.xonly_pk) .map_err(|_| BridgeError::Error("Invalid xonly public key".to_string()))?; // Save the operator details to the db @@ -111,7 +116,7 @@ impl ClementineVerifier for Verifier { let timeout_tx_sigs: Vec = operator_params .timeout_tx_sigs .iter() - .map(|sig| secp256k1::schnorr::Signature::from_slice(sig).unwrap()) + .map(|sig| schnorr::Signature::from_slice(sig).unwrap()) .collect(); let timeout_tx_sighash_stream = builder::sighash::create_timeout_tx_sighash_stream( @@ -127,20 +132,17 @@ impl ClementineVerifier for Verifier { timeout_tx_sighash_stream .enumerate() .map(|(i, sighash)| { - SECP256K1 - .verify_schnorr( - &timeout_tx_sigs[i], - &Message::from(sighash?), - &operator_xonly_pk, - ) - .map_err(|e| { - BridgeError::Error(format!("Can't verify Schnorr signature: {}", e)) - }) + SECP.verify_schnorr( + &timeout_tx_sigs[i], + &Message::from(sighash?), + &operator_xonly_pk, + ) + .map_err(|e| BridgeError::Error(format!("Can't verify Schnorr signature: {}", e))) }) .collect::>>() .await .into_iter() - .collect::, BridgeError>>()?; + .collect::, BridgeError>>()?; self.db .save_timeout_tx_sigs(None, operator_config.operator_idx, timeout_tx_sigs) @@ -228,16 +230,19 @@ impl ClementineVerifier for Verifier { req: Request, ) -> Result, Status> { let num_nonces = req.into_inner().num_nonces as usize; - let (sec_nonces, pub_nonces): (Vec, Vec) = (0..num_nonces) + let (sec_nonces, pub_nonces): (Vec, Vec) = (0..num_nonces) .map(|_| { // nonce pair needs keypair and a rng - let (sec_nonce, pub_nonce) = - musig2::nonce_pair(&self.signer.keypair, &mut secp256k1::rand::thread_rng()); + let (sec_nonce, pub_nonce) = musig2::nonce_pair( + &self.signer.keypair, + &mut bitcoin::secp256k1::rand::thread_rng(), + ) + .unwrap(); (sec_nonce, pub_nonce) }) .unzip(); - let private_key = secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng()); + let private_key = SecretKey::new(&mut bitcoin::secp256k1::rand::thread_rng()); let session = NonceSession { private_key, @@ -253,7 +258,7 @@ impl ClementineVerifier for Verifier { session_id }; - let public_key = secp256k1::PublicKey::from_secret_key(SECP256K1, &private_key) + let public_key = PublicKey::from_secret_key(&utils::SECP, &private_key) .serialize() .to_vec(); let public_key_hash = sha256_hash!(&public_key); @@ -283,7 +288,9 @@ impl ClementineVerifier for Verifier { // Then send the public nonces for pub_nonce in &pub_nonces[..] { let response = NonceGenResponse { - response: Some(nonce_gen_response::Response::PubNonce(pub_nonce.0.to_vec())), + response: Some(nonce_gen_response::Response::PubNonce( + pub_nonce.serialize().to_vec(), + )), }; tx.send(Ok(response)).await.unwrap(); } @@ -356,12 +363,17 @@ impl ClementineVerifier for Verifier { _ => panic!("Expected DepositOutpoint"), }; - let binding = verifier.nonces.lock().await; - let session = binding + let mut binding = verifier.nonces.lock().await; + let mut session = binding .sessions - .get(&session_id) - .ok_or(Status::internal("No session found")) + .remove(&session_id) + .ok_or(BridgeError::Error(format!( + "No session found for id: {}", + session_id + ))) .unwrap(); + session.nonces.reverse(); + let mut nonce_idx: usize = 0; let mut sighash_stream = pin!(create_nofn_sighash_stream( @@ -390,7 +402,7 @@ impl ClementineVerifier for Verifier { .unwrap() { clementine::verifier_deposit_sign_params::Params::AggNonce(agg_nonce) => { - ByteArray66(agg_nonce.try_into().unwrap()) + MusigAggNonce::from_slice(agg_nonce.as_slice()).unwrap() } _ => panic!("Expected AggNonce"), }; @@ -398,18 +410,20 @@ impl ClementineVerifier for Verifier { let sighash = sighash_stream.next().await.unwrap().unwrap(); tracing::debug!("Verifier {} found sighash: {:?}", verifier.idx, sighash); + let nonce = session.nonces.pop().expect("No nonce available"); + let move_tx_sig = musig2::partial_sign( verifier.config.verifiers_public_keys.clone(), None, - false, - session.nonces[nonce_idx], + nonce, agg_nonce, - &verifier.signer.keypair, - ByteArray32(sighash.to_byte_array()), - ); + verifier.signer.keypair, + Message::from_digest(*sighash.as_byte_array()), + ) + .unwrap(); let partial_sig = PartialSig { - partial_sig: move_tx_sig.0.to_vec(), + partial_sig: move_tx_sig.serialize().to_vec(), }; tx.send(Ok(partial_sig)).await.unwrap(); @@ -419,9 +433,6 @@ impl ClementineVerifier for Verifier { break; } } - // drop nonces - let mut binding = verifier.nonces.lock().await; - binding.sessions.remove(&session_id); }); let out_stream: Self::DepositSignStream = ReceiverStream::new(rx); @@ -509,18 +520,14 @@ impl ClementineVerifier for Verifier { .unwrap(); let final_sig = match final_sig { clementine::verifier_deposit_finalize_params::Params::SchnorrSig(final_sig) => { - secp256k1::schnorr::Signature::from_slice(&final_sig).unwrap() + schnorr::Signature::from_slice(&final_sig).unwrap() } _ => panic!("Expected FinalSig"), }; tracing::debug!("Verifying Final Signature"); - SECP256K1 - .verify_schnorr( - &final_sig, - &secp256k1::Message::from(sighash), - &self.nofn_xonly_pk, - ) + utils::SECP + .verify_schnorr(&final_sig, &Message::from(sighash), &self.nofn_xonly_pk) .unwrap(); tracing::debug!("Final Signature Verified"); diff --git a/core/src/user.rs b/core/src/user.rs index 38e41a71..0b8f82af 100644 --- a/core/src/user.rs +++ b/core/src/user.rs @@ -5,11 +5,10 @@ use crate::errors::BridgeError; use crate::extended_rpc::ExtendedRpc; use crate::musig2::AggregateFromPublicKeys; use crate::{EVMAddress, UTXO}; +use bitcoin::secp256k1::{schnorr, SecretKey}; use bitcoin::{Address, TxOut}; use bitcoin::{Amount, OutPoint}; use bitcoin::{TapSighashType, XOnlyPublicKey}; -use secp256k1::schnorr; -use secp256k1::SecretKey; pub const WITHDRAWAL_EMPTY_UTXO_SATS: Amount = Amount::from_sat(550); @@ -26,11 +25,8 @@ impl User { pub fn new(rpc: ExtendedRpc, sk: SecretKey, config: BridgeConfig) -> Self { let signer = Actor::new(sk, config.winternitz_secret_key, config.network); - let nofn_xonly_pk = secp256k1::XOnlyPublicKey::from_musig2_pks( - config.verifiers_public_keys.clone(), - None, - false, - ); + let nofn_xonly_pk = + XOnlyPublicKey::from_musig2_pks(config.verifiers_public_keys.clone(), None).unwrap(); User { rpc, @@ -120,8 +116,9 @@ mod tests { config::BridgeConfig, database::Database, initialize_database, utils::initialize_logger, }; use crate::{create_test_config_with_thread_name, extended_rpc::ExtendedRpc}; + use bitcoin::secp256k1::SecretKey; use bitcoincore_rpc::RpcApi; - use secp256k1::{rand, SecretKey}; + use secp256k1::rand; use std::{env, thread}; #[tokio::test] diff --git a/core/src/utils.rs b/core/src/utils.rs index f97b265a..2484d786 100644 --- a/core/src/utils.rs +++ b/core/src/utils.rs @@ -13,6 +13,11 @@ use tracing::level_filters::LevelFilter; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::{fmt, EnvFilter, Registry}; +lazy_static::lazy_static! { + /// Global secp context. + pub static ref SECP: bitcoin::secp256k1::Secp256k1 = bitcoin::secp256k1::Secp256k1::new(); +} + lazy_static::lazy_static! { /// This is an unspendable pubkey. /// diff --git a/core/src/verifier.rs b/core/src/verifier.rs index 6fb45601..3cbe1e03 100644 --- a/core/src/verifier.rs +++ b/core/src/verifier.rs @@ -1,29 +1,21 @@ -use std::collections::HashMap; -use std::sync::Arc; - use crate::actor::Actor; -use crate::builder::transaction::{TxHandler, KICKOFF_UTXO_AMOUNT_SATS}; +use crate::builder::transaction::TxHandler; use crate::builder::{self}; use crate::config::BridgeConfig; use crate::database::Database; use crate::errors::BridgeError; use crate::extended_rpc::ExtendedRpc; -use crate::musig2::{ - self, AggregateFromPublicKeys, MuSigAggNonce, MuSigPartialSignature, MuSigPubNonce, - MuSigSecNonce, MuSigSigHash, -}; -use crate::{ByteArray32, ByteArray64, ByteArray66, EVMAddress, UTXO}; -use bitcoin::address::NetworkUnchecked; -use bitcoin::hashes::Hash; -use bitcoin::Address; +use crate::musig2::AggregateFromPublicKeys; +use crate::UTXO; +use ::secp256k1::musig::MusigSecNonce; use bitcoin::{secp256k1, OutPoint}; -use bitcoincore_rpc::RawTx; -use secp256k1::{rand, schnorr, SECP256K1}; +use std::collections::HashMap; +use std::sync::Arc; #[derive(Debug)] pub struct NonceSession { pub private_key: secp256k1::SecretKey, - pub nonces: Vec, + pub nonces: Vec, } #[derive(Debug)] @@ -43,7 +35,7 @@ impl NofN { pub fn new(self_pk: secp256k1::PublicKey, public_keys: Vec) -> Self { let idx = public_keys.iter().position(|pk| pk == &self_pk).unwrap(); let agg_xonly_pk = - secp256k1::XOnlyPublicKey::from_musig2_pks(public_keys.clone(), None, false); + secp256k1::XOnlyPublicKey::from_musig2_pks(public_keys.clone(), None).unwrap(); NofN { public_keys, agg_xonly_pk, @@ -54,13 +46,13 @@ impl NofN { #[derive(Debug, Clone)] pub struct Verifier { - rpc: ExtendedRpc, + _rpc: ExtendedRpc, pub(crate) signer: Actor, pub(crate) db: Database, pub(crate) config: BridgeConfig, pub(crate) nofn_xonly_pk: secp256k1::XOnlyPublicKey, pub(crate) nofn: Arc>>, - operator_xonly_pks: Vec, + _operator_xonly_pks: Vec, pub(crate) nonces: Arc>, pub idx: usize, } @@ -84,11 +76,9 @@ impl Verifier { let db = Database::new(&config).await?; - let nofn_xonly_pk = secp256k1::XOnlyPublicKey::from_musig2_pks( - config.verifiers_public_keys.clone(), - None, - false, - ); + let nofn_xonly_pk = + secp256k1::XOnlyPublicKey::from_musig2_pks(config.verifiers_public_keys.clone(), None) + .unwrap(); let operator_xonly_pks = config.operators_xonly_pks.clone(); @@ -108,87 +98,84 @@ impl Verifier { }; Ok(Verifier { - rpc, + _rpc: rpc, signer, db, config, nofn_xonly_pk, nofn: Arc::new(tokio::sync::RwLock::new(nofn)), - operator_xonly_pks, + _operator_xonly_pks: operator_xonly_pks, nonces: Arc::new(tokio::sync::Mutex::new(all_sessions)), idx, }) } - /// Inform verifiers about the new deposit request - /// - /// 1. Check if the deposit UTXO is valid, finalized (6 blocks confirmation) and not spent - /// 2. Generate random pubNonces, secNonces - /// 3. Save pubNonces and secNonces to a db - /// 4. Return pubNonces - #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] - pub async fn new_deposit( - &self, - deposit_outpoint: OutPoint, - recovery_taproot_address: Address, - evm_address: EVMAddress, - ) -> Result, BridgeError> { - self.rpc - .check_deposit_utxo( - self.nofn_xonly_pk, - &deposit_outpoint, - &recovery_taproot_address, - evm_address, - self.config.bridge_amount_sats, - self.config.confirmation_threshold, - self.config.network, - self.config.user_takes_after, - ) - .await?; - - // For now we multiply by 2 since we do not give signatures for burn_txs. // TODO: Change this in future. - let num_required_nonces = 2 * self.operator_xonly_pks.len() + 1; - - let mut dbtx = self.db.begin_transaction().await?; - // Check if we already have pub_nonces for this deposit_outpoint. - let pub_nonces_from_db = self - .db - .get_pub_nonces(Some(&mut dbtx), deposit_outpoint) - .await?; - if let Some(pub_nonces) = pub_nonces_from_db { - if !pub_nonces.is_empty() { - if pub_nonces.len() != num_required_nonces { - return Err(BridgeError::NoncesNotFound); - } - dbtx.commit().await?; - return Ok(pub_nonces); - } - } - - let nonces = (0..num_required_nonces) - .map(|_| musig2::nonce_pair(&self.signer.keypair, &mut rand::rngs::OsRng)) - .collect::>(); - let nonces: Vec<(ByteArray64, ByteArray66)> = nonces - .into_iter() - .collect::>(); - - self.db - .save_deposit_info( - Some(&mut dbtx), - deposit_outpoint, - recovery_taproot_address, - evm_address, - ) - .await?; - self.db - .save_nonces(Some(&mut dbtx), deposit_outpoint, &nonces) - .await?; - dbtx.commit().await?; - - let pub_nonces = nonces.iter().map(|(_, pub_nonce)| *pub_nonce).collect(); - - Ok(pub_nonces) - } + // / Inform verifiers about the new deposit request + // / + // / 1. Check if the deposit UTXO is valid, finalized (6 blocks confirmation) and not spent + // / 2. Generate random pubNonces, secNonces + // / 3. Save pubNonces and secNonces to a db + // / 4. Return pubNonces + // #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] + // pub async fn new_deposit( + // &self, + // deposit_outpoint: OutPoint, + // recovery_taproot_address: Address, + // evm_address: EVMAddress, + // ) -> Result, BridgeError> { + // self.rpc + // .check_deposit_utxo( + // self.nofn_xonly_pk, + // &deposit_outpoint, + // &recovery_taproot_address, + // evm_address, + // self.config.bridge_amount_sats, + // self.config.confirmation_threshold, + // self.config.network, + // self.config.user_takes_after, + // ) + // .await?; + + // // For now we multiply by 2 since we do not give signatures for burn_txs. // TODO: Change this in future. + // let num_required_nonces = 2 * self.operator_xonly_pks.len() + 1; + + // let mut dbtx = self.db.begin_transaction().await?; + // // Check if we already have pub_nonces for this deposit_outpoint. + // let pub_nonces_from_db = self + // .db + // .get_pub_nonces(Some(&mut dbtx), deposit_outpoint) + // .await?; + // if let Some(pub_nonces) = pub_nonces_from_db { + // if !pub_nonces.is_empty() { + // if pub_nonces.len() != num_required_nonces { + // return Err(BridgeError::NoncesNotFound); + // } + // dbtx.commit().await?; + // return Ok(pub_nonces); + // } + // } + + // let nonces = (0..num_required_nonces) + // .map(|_| musig2::nonce_pair(&self.signer.keypair, &mut rand::rngs::OsRng).1) + // .collect::>(); + + // self.db + // .save_deposit_info( + // Some(&mut dbtx), + // deposit_outpoint, + // recovery_taproot_address, + // evm_address, + // ) + // .await?; + // self.db + // .save_nonces(Some(&mut dbtx), deposit_outpoint, &nonces) + // .await?; + // dbtx.commit().await?; + + // let pub_nonces = nonces.iter().map(|pub_nonce| *pub_nonce).collect(); + + // Ok(pub_nonces) + // } /// - Verify operators signatures about kickoffs /// - Check the kickoff_utxos @@ -200,133 +187,133 @@ impl Verifier { /// /// do not forget to add tweak when signing since this address has n_of_n as internal_key /// and operator_timelock as script. - #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] - pub async fn operator_kickoffs_generated( - &self, - deposit_outpoint: OutPoint, - kickoff_utxos: Vec, - operators_kickoff_sigs: Vec, // These are not transaction signatures, rather, they are to verify the operator's identity. - agg_nonces: Vec, // This includes all the agg_nonces for the bridge operations. - ) -> Result<(Vec, Vec), BridgeError> { - tracing::debug!( - "Operatos kickoffs generated is called with data: {:?}, {:?}, {:?}, {:?}", - deposit_outpoint, - kickoff_utxos, - operators_kickoff_sigs, - agg_nonces - ); - - if operators_kickoff_sigs.len() != kickoff_utxos.len() { - return Err(BridgeError::InvalidKickoffUtxo); // TODO: Better error - } - - let mut slash_or_take_sighashes: Vec = Vec::new(); - - for (i, kickoff_utxo) in kickoff_utxos.iter().enumerate() { - let value = kickoff_utxo.txout.value; - if value < KICKOFF_UTXO_AMOUNT_SATS { - return Err(BridgeError::InvalidKickoffUtxo); - } - - let kickoff_sig_hash = crate::sha256_hash!( - deposit_outpoint.txid, - deposit_outpoint.vout.to_be_bytes(), - kickoff_utxo.outpoint.txid, - kickoff_utxo.outpoint.vout.to_be_bytes() - ); - - // Check if they are really the operators that sent these kickoff_utxos - SECP256K1.verify_schnorr( - &operators_kickoff_sigs[i], - &secp256k1::Message::from_digest(kickoff_sig_hash), - &self.config.operators_xonly_pks[i], - )?; - - // Check if for each operator the address of the kickoff_utxo is correct TODO: Maybe handle the possible errors better - let (musig2_and_operator_address, spend_info) = - builder::address::create_kickoff_address( - self.nofn_xonly_pk, - self.operator_xonly_pks[i], - self.config.network, - ); - tracing::debug!( - "musig2_and_operator_address.script_pubkey: {:?}", - musig2_and_operator_address.script_pubkey() - ); - tracing::debug!("Kickoff UTXO: {:?}", kickoff_utxo.txout.script_pubkey); - tracing::debug!("Spend Info: {:?}", spend_info); - assert!( - kickoff_utxo.txout.script_pubkey == musig2_and_operator_address.script_pubkey() - ); - - let mut slash_or_take_tx_handler = builder::transaction::create_slash_or_take_tx( - deposit_outpoint, - kickoff_utxo.clone(), - self.config.operators_xonly_pks[i], - i, - self.nofn_xonly_pk, - self.config.network, - self.config.user_takes_after, - self.config.operator_takes_after, - self.config.bridge_amount_sats, - ); - let slash_or_take_tx_sighash = - Actor::convert_tx_to_sighash_script_spend(&mut slash_or_take_tx_handler, 0, 0)?; - slash_or_take_sighashes.push(ByteArray32(slash_or_take_tx_sighash.to_byte_array())); - // let spend_kickoff_utxo_tx_handler = builder::transaction::create_slash_or_take_tx(deposit_outpoint, kickoff_outpoint, kickoff_txout, operator_address, operator_idx, nofn_xonly_pk, network) - } - tracing::debug!( - "Slash or take sighashes for verifier: {:?}: {:?}", - self.signer.xonly_public_key.to_string(), - slash_or_take_sighashes - ); - - let mut dbtx = self.db.begin_transaction().await?; - - self.db - .save_agg_nonces(Some(&mut dbtx), deposit_outpoint, &agg_nonces) - .await?; - - self.db - .save_kickoff_utxos(Some(&mut dbtx), deposit_outpoint, &kickoff_utxos) - .await?; - - let nonces = self - .db - .save_sighashes_and_get_nonces( - Some(&mut dbtx), - deposit_outpoint, - self.config.num_operators + 1, - &slash_or_take_sighashes, - ) - .await? - .ok_or(BridgeError::NoncesNotFound)?; - tracing::debug!( - "SIGNING slash or take for outpoint: {:?} with nonces {:?}", - deposit_outpoint, - nonces - ); - let slash_or_take_partial_sigs = slash_or_take_sighashes - .iter() - .zip(nonces.iter()) - .map(|(sighash, (sec_nonce, agg_nonce))| { - musig2::partial_sign( - self.config.verifiers_public_keys.clone(), - None, - false, - *sec_nonce, - *agg_nonce, - &self.signer.keypair, - *sighash, - ) - }) - .collect::>(); - - dbtx.commit().await?; - - // TODO: Sign burn txs - Ok((slash_or_take_partial_sigs, vec![])) - } + // #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] + // pub async fn operator_kickoffs_generated( + // &self, + // deposit_outpoint: OutPoint, + // kickoff_utxos: Vec, + // operators_kickoff_sigs: Vec, // These are not transaction signatures, rather, they are to verify the operator's identity. + // agg_nonces: Vec, // This includes all the agg_nonces for the bridge operations. + // ) -> Result<(Vec, Vec), BridgeError> { + // tracing::debug!( + // "Operatos kickoffs generated is called with data: {:?}, {:?}, {:?}, {:?}", + // deposit_outpoint, + // kickoff_utxos, + // operators_kickoff_sigs, + // agg_nonces + // ); + + // if operators_kickoff_sigs.len() != kickoff_utxos.len() { + // return Err(BridgeError::InvalidKickoffUtxo); // TODO: Better error + // } + + // let mut slash_or_take_sighashes = Vec::new(); + + // for (i, kickoff_utxo) in kickoff_utxos.iter().enumerate() { + // let value = kickoff_utxo.txout.value; + // if value < KICKOFF_UTXO_AMOUNT_SATS { + // return Err(BridgeError::InvalidKickoffUtxo); + // } + + // let kickoff_sig_hash = crate::sha256_hash!( + // deposit_outpoint.txid, + // deposit_outpoint.vout.to_be_bytes(), + // kickoff_utxo.outpoint.txid, + // kickoff_utxo.outpoint.vout.to_be_bytes() + // ); + + // // Check if they are really the operators that sent these kickoff_utxos + // utils::SECP.verify_schnorr( + // &operators_kickoff_sigs[i], + // &secp256k1::Message::from_digest(kickoff_sig_hash), + // &self.config.operators_xonly_pks[i], + // )?; + + // // Check if for each operator the address of the kickoff_utxo is correct TODO: Maybe handle the possible errors better + // let (musig2_and_operator_address, spend_info) = + // builder::address::create_kickoff_address( + // self.nofn_xonly_pk, + // self.operator_xonly_pks[i], + // self.config.network, + // ); + // tracing::debug!( + // "musig2_and_operator_address.script_pubkey: {:?}", + // musig2_and_operator_address.script_pubkey() + // ); + // tracing::debug!("Kickoff UTXO: {:?}", kickoff_utxo.txout.script_pubkey); + // tracing::debug!("Spend Info: {:?}", spend_info); + // assert!( + // kickoff_utxo.txout.script_pubkey == musig2_and_operator_address.script_pubkey() + // ); + + // let mut slash_or_take_tx_handler = builder::transaction::create_slash_or_take_tx( + // deposit_outpoint, + // kickoff_utxo.clone(), + // self.config.operators_xonly_pks[i], + // i, + // self.nofn_xonly_pk, + // self.config.network, + // self.config.user_takes_after, + // self.config.operator_takes_after, + // self.config.bridge_amount_sats, + // ); + // let slash_or_take_tx_sighash = + // Actor::convert_tx_to_sighash_script_spend(&mut slash_or_take_tx_handler, 0, 0)?; + // slash_or_take_sighashes.push(Message::from_digest(slash_or_take_tx_sighash.to_byte_array())?); + // // let spend_kickoff_utxo_tx_handler = builder::transaction::create_slash_or_take_tx(deposit_outpoint, kickoff_outpoint, kickoff_txout, operator_address, operator_idx, nofn_xonly_pk, network) + // } + // tracing::debug!( + // "Slash or take sighashes for verifier: {:?}: {:?}", + // self.signer.xonly_public_key.to_string(), + // slash_or_take_sighashes + // ); + + // let mut dbtx = self.db.begin_transaction().await?; + + // self.db + // .save_agg_nonces(Some(&mut dbtx), deposit_outpoint, &agg_nonces) + // .await?; + + // self.db + // .save_kickoff_utxos(Some(&mut dbtx), deposit_outpoint, &kickoff_utxos) + // .await?; + + // let nonces = self + // .db + // .save_sighashes_and_get_nonces( + // Some(&mut dbtx), + // deposit_outpoint, + // self.config.num_operators + 1, + // &slash_or_take_sighashes, + // ) + // .await? + // .ok_or(BridgeError::NoncesNotFound)?; + // tracing::debug!( + // "SIGNING slash or take for outpoint: {:?} with nonces {:?}", + // deposit_outpoint, + // nonces + // ); + // let slash_or_take_partial_sigs = slash_or_take_sighashes + // .iter() + // .zip(nonces.into_iter()) + // .map(|(sighash, (sec_nonce, agg_nonce))| { + // musig2::partial_sign( + // self.config.verifiers_public_keys.clone(), + // None, + // false, + // *sec_nonce, + // *agg_nonce, + // &self.signer.keypair, + // *sighash, + // ) + // }) + // .collect::>(); + + // dbtx.commit().await?; + + // // TODO: Sign burn txs + // Ok((slash_or_take_partial_sigs, vec![])) + // } #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] pub async fn create_deposit_details( @@ -367,335 +354,552 @@ impl Verifier { Ok((kickoff_utxos, move_tx_handler, bridge_fund_outpoint)) } - /// verify burn txs are signed by verifiers - /// sign operator_takes_txs - /// TODO: Change the name of this function. - #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] - pub async fn burn_txs_signed( - &self, - deposit_outpoint: OutPoint, - _burn_sigs: Vec, - slash_or_take_sigs: Vec, - ) -> Result, BridgeError> { - // TODO: Verify burn txs are signed by verifiers - let (kickoff_utxos, _, bridge_fund_outpoint) = - self.create_deposit_details(deposit_outpoint).await?; - - let operator_takes_sighashes: Vec = kickoff_utxos - .iter() - .enumerate() - .map(|(index, kickoff_utxo)| { - let mut slash_or_take_tx_handler = builder::transaction::create_slash_or_take_tx( - deposit_outpoint, - kickoff_utxo.clone(), - self.operator_xonly_pks[index], - index, - self.nofn_xonly_pk, - self.config.network, - self.config.user_takes_after, - self.config.operator_takes_after, - self.config.bridge_amount_sats, - ); - let slash_or_take_sighash = - Actor::convert_tx_to_sighash_script_spend(&mut slash_or_take_tx_handler, 0, 0) - .unwrap(); - - SECP256K1 - .verify_schnorr( - &slash_or_take_sigs[index], - &secp256k1::Message::from_digest(slash_or_take_sighash.to_byte_array()), - &self.nofn_xonly_pk, - ) - .unwrap(); - - 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(), - }; - - let mut operator_takes_tx = builder::transaction::create_operator_takes_tx( - bridge_fund_outpoint, - slash_or_take_utxo, - self.operator_xonly_pks[index], - self.nofn_xonly_pk, - self.config.network, - self.config.operator_takes_after, - self.config.bridge_amount_sats, - self.config.operator_wallet_addresses[index].clone(), - ); - ByteArray32( - Actor::convert_tx_to_sighash_pubkey_spend(&mut operator_takes_tx, 0) - .unwrap() - .to_byte_array(), - ) - }) - .collect::>(); - - self.db - .save_slash_or_take_sigs(deposit_outpoint, slash_or_take_sigs) - .await?; - - // println!("Operator takes sighashes: {:?}", operator_takes_sighashes); - let nonces = self - .db - .save_sighashes_and_get_nonces(None, deposit_outpoint, 1, &operator_takes_sighashes) - .await? - .ok_or(BridgeError::NoncesNotFound)?; - // println!("Nonces: {:?}", nonces); - // now iterate over nonces and sighashes and sign the operator_takes_txs - let operator_takes_partial_sigs = operator_takes_sighashes - .iter() - .zip(nonces.iter()) - .map(|(sighash, (sec_nonce, agg_nonce))| { - musig2::partial_sign( - self.config.verifiers_public_keys.clone(), - None, - true, - *sec_nonce, - *agg_nonce, - &self.signer.keypair, - *sighash, - ) - }) - .collect::>(); - - Ok(operator_takes_partial_sigs) - } - - /// verify the operator_take_sigs - /// sign move_tx - #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] - pub async fn operator_take_txs_signed( - &self, - deposit_outpoint: OutPoint, - operator_take_sigs: Vec, - ) -> Result { - // println!("Operator take signed: {:?}", operator_take_sigs); - let (kickoff_utxos, mut move_tx_handler, bridge_fund_outpoint) = - self.create_deposit_details(deposit_outpoint).await?; - let nofn_taproot_xonly_pk = secp256k1::XOnlyPublicKey::from_slice( - &Address::p2tr(SECP256K1, self.nofn_xonly_pk, None, self.config.network) - .script_pubkey() - .as_bytes()[2..34], - )?; - kickoff_utxos - .iter() - .enumerate() - .for_each(|(index, kickoff_utxo)| { - let slash_or_take_tx = builder::transaction::create_slash_or_take_tx( - deposit_outpoint, - kickoff_utxo.clone(), - self.operator_xonly_pks[index], - index, - self.nofn_xonly_pk, - self.config.network, - self.config.user_takes_after, - self.config.operator_takes_after, - self.config.bridge_amount_sats, - ); - let slash_or_take_utxo = UTXO { - outpoint: OutPoint { - txid: slash_or_take_tx.tx.compute_txid(), - vout: 0, - }, - txout: slash_or_take_tx.tx.output[0].clone(), - }; - let mut operator_takes_tx = builder::transaction::create_operator_takes_tx( - bridge_fund_outpoint, - slash_or_take_utxo, - self.operator_xonly_pks[index], - self.nofn_xonly_pk, - self.config.network, - self.config.operator_takes_after, - self.config.bridge_amount_sats, - self.config.operator_wallet_addresses[index].clone(), - ); - 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(); - - // verify the operator_take_sigs - SECP256K1 - .verify_schnorr( - &operator_take_sigs[index], - &secp256k1::Message::from_digest(sig_hash.to_byte_array()), - &nofn_taproot_xonly_pk, - ) - .unwrap(); - }); - - let kickoff_utxos = kickoff_utxos - .into_iter() - .enumerate() - .map(|(index, utxo)| (utxo, operator_take_sigs[index])); - - self.db - .save_operator_take_sigs(deposit_outpoint, kickoff_utxos) - .await?; - - // 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 - - // let move_reveal_sighash = - // Actor::convert_tx_to_sighash_script_spend(&mut move_reveal_tx_handler, 0, 0)?; // TODO: This should be musig - - let nonces = self - .db - .save_sighashes_and_get_nonces( - None, - deposit_outpoint, - 0, - &[ByteArray32(move_tx_sighash.to_byte_array())], - ) - .await? - .ok_or(BridgeError::NoncesNotFound)?; - - let move_tx_sig = musig2::partial_sign( - self.config.verifiers_public_keys.clone(), - None, - false, - nonces[0].0, - nonces[0].1, - &self.signer.keypair, - ByteArray32(move_tx_sighash.to_byte_array()), - ); - - // let move_reveal_sig = musig2::partial_sign( - // self.config.verifiers_public_keys.clone(), - // None, - // nonces[1].0, - // nonces[2].1.clone(), - // &self.signer.keypair, - // move_reveal_sighash.to_byte_array(), - // ); - - Ok( - move_tx_sig as MuSigPartialSignature, // move_reveal_sig as MuSigPartialSignature, - ) - } + // / verify burn txs are signed by verifiers + // / sign operator_takes_txs + // / TODO: Change the name of this function. + // #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] + // pub async fn burn_txs_signed( + // &self, + // deposit_outpoint: OutPoint, + // _burn_sigs: Vec, + // slash_or_take_sigs: Vec, + // ) -> Result, BridgeError> { + // // TODO: Verify burn txs are signed by verifiers + // let (kickoff_utxos, _, bridge_fund_outpoint) = + // self.create_deposit_details(deposit_outpoint).await?; + + // let operator_takes_sighashes = kickoff_utxos + // .iter() + // .enumerate() + // .map(|(index, kickoff_utxo)| { + // let mut slash_or_take_tx_handler = builder::transaction::create_slash_or_take_tx( + // deposit_outpoint, + // kickoff_utxo.clone(), + // self.operator_xonly_pks[index], + // index, + // self.nofn_xonly_pk, + // self.config.network, + // self.config.user_takes_after, + // self.config.operator_takes_after, + // self.config.bridge_amount_sats, + // ); + // let slash_or_take_sighash = + // Actor::convert_tx_to_sighash_script_spend(&mut slash_or_take_tx_handler, 0, 0) + // .unwrap(); + + // utils::SECP + // .verify_schnorr( + // &slash_or_take_sigs[index], + // &secp256k1::Message::from_digest(slash_or_take_sighash.to_byte_array()), + // &self.nofn_xonly_pk, + // ) + // .unwrap(); + + // 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(), + // }; + + // let mut operator_takes_tx = builder::transaction::create_operator_takes_tx( + // bridge_fund_outpoint, + // slash_or_take_utxo, + // self.operator_xonly_pks[index], + // self.nofn_xonly_pk, + // self.config.network, + // self.config.operator_takes_after, + // self.config.bridge_amount_sats, + // self.config.operator_wallet_addresses[index].clone(), + // ); + // Message::from_digest( + // Actor::convert_tx_to_sighash_pubkey_spend(&mut operator_takes_tx, 0) + // .unwrap() + // .to_byte_array(), + // ) + // }) + // .collect::>(); + + // self.db + // .save_slash_or_take_sigs(deposit_outpoint, slash_or_take_sigs) + // .await?; + + // // println!("Operator takes sighashes: {:?}", operator_takes_sighashes); + // let nonces = self + // .db + // .save_sighashes_and_get_nonces(None, deposit_outpoint, 1, &operator_takes_sighashes) + // .await? + // .ok_or(BridgeError::NoncesNotFound)?; + // // println!("Nonces: {:?}", nonces); + // // now iterate over nonces and sighashes and sign the operator_takes_txs + // let operator_takes_partial_sigs = operator_takes_sighashes + // .iter() + // .zip(nonces.iter()) + // .map(|(sighash, (sec_nonce, agg_nonce))| { + // musig2::partial_sign( + // self.config.verifiers_public_keys.clone(), + // None, + // true, + // *sec_nonce, + // *agg_nonce, + // &self.signer.keypair, + // *sighash, + // ) + // }) + // .collect::>(); + + // Ok(operator_takes_partial_sigs) + // } + + // / verify the operator_take_sigs + // / sign move_tx + // #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] + // pub async fn operator_take_txs_signed( + // &self, + // deposit_outpoint: OutPoint, + // operator_take_sigs: Vec, + // ) -> Result { + // // println!("Operator take signed: {:?}", operator_take_sigs); + // let (kickoff_utxos, mut move_tx_handler, bridge_fund_outpoint) = + // self.create_deposit_details(deposit_outpoint).await?; + // let nofn_taproot_xonly_pk = secp256k1::XOnlyPublicKey::from_slice( + // &Address::p2tr(&utils::SECP, self.nofn_xonly_pk, None, self.config.network) + // .script_pubkey() + // .as_bytes()[2..34], + // )?; + // kickoff_utxos + // .iter() + // .enumerate() + // .for_each(|(index, kickoff_utxo)| { + // let slash_or_take_tx = builder::transaction::create_slash_or_take_tx( + // deposit_outpoint, + // kickoff_utxo.clone(), + // self.operator_xonly_pks[index], + // index, + // self.nofn_xonly_pk, + // self.config.network, + // self.config.user_takes_after, + // self.config.operator_takes_after, + // self.config.bridge_amount_sats, + // ); + // let slash_or_take_utxo = UTXO { + // outpoint: OutPoint { + // txid: slash_or_take_tx.tx.compute_txid(), + // vout: 0, + // }, + // txout: slash_or_take_tx.tx.output[0].clone(), + // }; + // let mut operator_takes_tx = builder::transaction::create_operator_takes_tx( + // bridge_fund_outpoint, + // slash_or_take_utxo, + // self.operator_xonly_pks[index], + // self.nofn_xonly_pk, + // self.config.network, + // self.config.operator_takes_after, + // self.config.bridge_amount_sats, + // self.config.operator_wallet_addresses[index].clone(), + // ); + // 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(); + + // // verify the operator_take_sigs + // utils::SECP + // .verify_schnorr( + // &operator_take_sigs[index], + // &secp256k1::Message::from_digest(sig_hash.to_byte_array()), + // &nofn_taproot_xonly_pk, + // ) + // .unwrap(); + // }); + + // let kickoff_utxos = kickoff_utxos + // .into_iter() + // .enumerate() + // .map(|(index, utxo)| (utxo, operator_take_sigs[index])); + + // self.db + // .save_operator_take_sigs(deposit_outpoint, kickoff_utxos) + // .await?; + + // // 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 + + // // let move_reveal_sighash = + // // Actor::convert_tx_to_sighash_script_spend(&mut move_reveal_tx_handler, 0, 0)?; // TODO: This should be musig + + // let nonces = self + // .db + // .save_sighashes_and_get_nonces( + // None, + // deposit_outpoint, + // 0, + // &[ByteArray32(move_tx_sighash.to_byte_array())], + // ) + // .await? + // .ok_or(BridgeError::NoncesNotFound)?; + + // let move_tx_sig = musig2::partial_sign( + // self.config.verifiers_public_keys.clone(), + // None, + // false, + // nonces[0].0, + // nonces[0].1, + // &self.signer.keypair, + // ByteArray32(move_tx_sighash.to_byte_array()), + // ); + + // // let move_reveal_sig = musig2::partial_sign( + // // self.config.verifiers_public_keys.clone(), + // // None, + // // nonces[1].0, + // // nonces[2].1.clone(), + // // &self.signer.keypair, + // // move_reveal_sighash.to_byte_array(), + // // ); + + // Ok( + // move_tx_sig as MusigPartialSignature, // move_reveal_sig as MuSigPartialSignature, + // ) + // } + + // / verify burn txs are signed by verifiers + // / sign operator_takes_txs + // / TODO: Change the name of this function. + // #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] + // pub async fn burn_txs_signed( + // &self, + // deposit_outpoint: OutPoint, + // _burn_sigs: Vec, + // slash_or_take_sigs: Vec, + // ) -> Result, BridgeError> { + // // TODO: Verify burn txs are signed by verifiers + // let (kickoff_utxos, _, bridge_fund_outpoint) = + // self.create_deposit_details(deposit_outpoint).await?; + + // let operator_takes_sighashes: Vec = kickoff_utxos + // .iter() + // .enumerate() + // .map(|(index, kickoff_utxo)| { + // let mut slash_or_take_tx_handler = builder::transaction::create_slash_or_take_tx( + // deposit_outpoint, + // kickoff_utxo.clone(), + // self.operator_xonly_pks[index], + // index, + // self.nofn_xonly_pk, + // self.config.network, + // self.config.user_takes_after, + // self.config.operator_takes_after, + // self.config.bridge_amount_sats, + // ); + // let slash_or_take_sighash = + // Actor::convert_tx_to_sighash_script_spend(&mut slash_or_take_tx_handler, 0, 0) + // .unwrap(); + + // &SECP + // .verify_schnorr( + // &slash_or_take_sigs[index], + // &secp256k1::Message::from_digest(slash_or_take_sighash.to_byte_array()), + // &self.nofn_xonly_pk, + // ) + // .unwrap(); + + // 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(), + // }; + + // let mut operator_takes_tx = builder::transaction::create_operator_takes_tx( + // bridge_fund_outpoint, + // slash_or_take_utxo, + // self.operator_xonly_pks[index], + // self.nofn_xonly_pk, + // self.config.network, + // self.config.operator_takes_after, + // self.config.bridge_amount_sats, + // self.config.operator_wallet_addresses[index].clone(), + // ); + // ByteArray32( + // Actor::convert_tx_to_sighash_pubkey_spend(&mut operator_takes_tx, 0) + // .unwrap() + // .to_byte_array(), + // ) + // }) + // .collect::>(); + + // self.db + // .save_slash_or_take_sigs(deposit_outpoint, slash_or_take_sigs) + // .await?; + + // // println!("Operator takes sighashes: {:?}", operator_takes_sighashes); + // let nonces = self + // .db + // .save_sighashes_and_get_nonces(None, deposit_outpoint, 1, &operator_takes_sighashes) + // .await? + // .ok_or(BridgeError::NoncesNotFound)?; + // // println!("Nonces: {:?}", nonces); + // // now iterate over nonces and sighashes and sign the operator_takes_txs + // let operator_takes_partial_sigs = operator_takes_sighashes + // .iter() + // .zip(nonces.iter()) + // .map(|(sighash, (sec_nonce, agg_nonce))| { + // musig2::partial_sign( + // self.config.verifiers_public_keys.clone(), + // None, + // true, + // *sec_nonce, + // *agg_nonce, + // &self.signer.keypair, + // *sighash, + // ) + // }) + // .collect::>(); + + // Ok(operator_takes_partial_sigs) + // } + + // /// verify the operator_take_sigs + // /// sign move_tx + // #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] + // pub async fn operator_take_txs_signed( + // &self, + // deposit_outpoint: OutPoint, + // operator_take_sigs: Vec, + // ) -> Result { + // // println!("Operator take signed: {:?}", operator_take_sigs); + // let (kickoff_utxos, mut move_tx_handler, bridge_fund_outpoint) = + // self.create_deposit_details(deposit_outpoint).await?; + // let nofn_taproot_xonly_pk = secp256k1::XOnlyPublicKey::from_slice( + // &Address::p2tr(&SECP, self.nofn_xonly_pk, None, self.config.network) + // .script_pubkey() + // .as_bytes()[2..34], + // )?; + // kickoff_utxos + // .iter() + // .enumerate() + // .for_each(|(index, kickoff_utxo)| { + // let slash_or_take_tx = builder::transaction::create_slash_or_take_tx( + // deposit_outpoint, + // kickoff_utxo.clone(), + // self.operator_xonly_pks[index], + // index, + // self.nofn_xonly_pk, + // self.config.network, + // self.config.user_takes_after, + // self.config.operator_takes_after, + // self.config.bridge_amount_sats, + // ); + // let slash_or_take_utxo = UTXO { + // outpoint: OutPoint { + // txid: slash_or_take_tx.tx.compute_txid(), + // vout: 0, + // }, + // txout: slash_or_take_tx.tx.output[0].clone(), + // }; + // let mut operator_takes_tx = builder::transaction::create_operator_takes_tx( + // bridge_fund_outpoint, + // slash_or_take_utxo, + // self.operator_xonly_pks[index], + // self.nofn_xonly_pk, + // self.config.network, + // self.config.operator_takes_after, + // self.config.bridge_amount_sats, + // self.config.operator_wallet_addresses[index].clone(), + // ); + // 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(); + + // // verify the operator_take_sigs + // &SECP + // .verify_schnorr( + // &operator_take_sigs[index], + // &secp256k1::Message::from_digest(sig_hash.to_byte_array()), + // &nofn_taproot_xonly_pk, + // ) + // .unwrap(); + // }); + + // let kickoff_utxos = kickoff_utxos + // .into_iter() + // .enumerate() + // .map(|(index, utxo)| (utxo, operator_take_sigs[index])); + + // self.db + // .save_operator_take_sigs(deposit_outpoint, kickoff_utxos) + // .await?; + + // // 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 + + // // let move_reveal_sighash = + // // Actor::convert_tx_to_sighash_script_spend(&mut move_reveal_tx_handler, 0, 0)?; // TODO: This should be musig + + // let nonces = self + // .db + // .save_sighashes_and_get_nonces( + // None, + // deposit_outpoint, + // 0, + // &[ByteArray32(move_tx_sighash.to_byte_array())], + // ) + // .await? + // .ok_or(BridgeError::NoncesNotFound)?; + + // let move_tx_sig = musig2::partial_sign( + // self.config.verifiers_public_keys.clone(), + // None, + // false, + // nonces[0].0, + // nonces[0].1, + // &self.signer.keypair, + // ByteArray32(move_tx_sighash.to_byte_array()), + // ); + + // // let move_reveal_sig = musig2::partial_sign( + // // self.config.verifiers_public_keys.clone(), + // // None, + // // nonces[1].0, + // // nonces[2].1.clone(), + // // &self.signer.keypair, + // // move_reveal_sighash.to_byte_array(), + // // ); + + // Ok( + // move_tx_sig as MuSigPartialSignature, // move_reveal_sig as MuSigPartialSignature, + // ) + // } } -#[cfg(test)] -mod tests { - use crate::errors::BridgeError; - use crate::extended_rpc::ExtendedRpc; - use crate::musig2::nonce_pair; - use crate::user::User; - use crate::verifier::Verifier; - use crate::EVMAddress; - use crate::{actor::Actor, create_test_config_with_thread_name}; - use crate::{ - config::BridgeConfig, database::Database, initialize_database, utils::initialize_logger, - }; - use secp256k1::rand; - use std::{env, thread}; - - #[tokio::test] - async fn verifier_new_public_key_check() { - let mut config = create_test_config_with_thread_name!(None); - let rpc = ExtendedRpc::new( - config.bitcoin_rpc_url.clone(), - config.bitcoin_rpc_user.clone(), - config.bitcoin_rpc_password.clone(), - ) - .await; - - // Test config file has correct keys. - Verifier::new(rpc.clone(), config.clone()).await.unwrap(); - - // Clearing them should result in error. - config.verifiers_public_keys.clear(); - assert!(Verifier::new(rpc, config).await.is_err()); - } - - #[tokio::test] - #[serial_test::serial] - async fn new_deposit_nonce_checks() { - let config = create_test_config_with_thread_name!(None); - let rpc = ExtendedRpc::new( - config.bitcoin_rpc_url.clone(), - config.bitcoin_rpc_user.clone(), - config.bitcoin_rpc_password.clone(), - ) - .await; - let verifier = Verifier::new(rpc.clone(), config.clone()).await.unwrap(); - let user = User::new(rpc.clone(), config.secret_key, config.clone()); - - let evm_address = EVMAddress([1u8; 20]); - let deposit_address = user.get_deposit_address(evm_address).unwrap(); - - let signer_address = Actor::new( - config.secret_key, - config.winternitz_secret_key, - config.network, - ) - .address - .as_unchecked() - .clone(); - - let required_nonce_count = 2 * config.operators_xonly_pks.len() + 1; - - // Not enough nonces. - let deposit_outpoint = rpc - .send_to_address(&deposit_address.clone(), config.bridge_amount_sats) - .await - .unwrap(); - rpc.mine_blocks((config.confirmation_threshold + 2).into()) - .await - .unwrap(); - - let nonces = (0..required_nonce_count / 2) - .map(|_| nonce_pair(&verifier.signer.keypair, &mut rand::rngs::OsRng)) - .collect::>(); - verifier - .db - .save_nonces(None, deposit_outpoint, &nonces) - .await - .unwrap(); - - assert!(verifier - .new_deposit(deposit_outpoint, signer_address.clone(), evm_address) - .await - .is_err_and(|e| { - if let BridgeError::NoncesNotFound = e { - true - } else { - println!("Error was {e}"); - false - } - })); - - // Enough nonces. - let deposit_outpoint = rpc - .send_to_address(&deposit_address.clone(), config.bridge_amount_sats) - .await - .unwrap(); - rpc.mine_blocks((config.confirmation_threshold + 2).into()) - .await - .unwrap(); - - let nonces = (0..required_nonce_count) - .map(|_| nonce_pair(&verifier.signer.keypair, &mut rand::rngs::OsRng)) - .collect::>(); - verifier - .db - .save_nonces(None, deposit_outpoint, &nonces) - .await - .unwrap(); - - verifier - .new_deposit(deposit_outpoint, signer_address, evm_address) - .await - .unwrap(); - } -} +// #[cfg(test)] +// mod tests { +// use crate::errors::BridgeError; +// use crate::extended_rpc::ExtendedRpc; +// use crate::musig2::nonce_pair; +// use crate::user::User; +// use crate::verifier::Verifier; +// use crate::EVMAddress; +// use crate::{actor::Actor, create_test_config_with_thread_name}; +// use crate::{ +// config::BridgeConfig, database::Database, initialize_database, utils::initialize_logger, +// }; +// use secp256k1::rand; +// use std::{env, thread}; + +// #[tokio::test] +// async fn verifier_new_public_key_check() { +// let mut config = create_test_config_with_thread_name!(None); +// let rpc = ExtendedRpc::new( +// config.bitcoin_rpc_url.clone(), +// config.bitcoin_rpc_user.clone(), +// config.bitcoin_rpc_password.clone(), +// ) +// .await; + +// // Test config file has correct keys. +// Verifier::new(rpc.clone(), config.clone()).await.unwrap(); + +// // Clearing them should result in error. +// config.verifiers_public_keys.clear(); +// assert!(Verifier::new(rpc, config).await.is_err()); +// } + +// #[tokio::test] +// #[serial_test::serial] +// async fn new_deposit_nonce_checks() { +// let config = create_test_config_with_thread_name!(None); +// let rpc = ExtendedRpc::new( +// config.bitcoin_rpc_url.clone(), +// config.bitcoin_rpc_user.clone(), +// config.bitcoin_rpc_password.clone(), +// ) +// .await; +// let verifier = Verifier::new(rpc.clone(), config.clone()).await.unwrap(); +// let user = User::new(rpc.clone(), config.secret_key, config.clone()); + +// let evm_address = EVMAddress([1u8; 20]); +// let deposit_address = user.get_deposit_address(evm_address).unwrap(); + +// let signer_address = Actor::new( +// config.secret_key, +// config.winternitz_secret_key, +// config.network, +// ) +// .address +// .as_unchecked() +// .clone(); + +// let required_nonce_count = 2 * config.operators_xonly_pks.len() + 1; + +// // Not enough nonces. +// let deposit_outpoint = rpc +// .send_to_address(&deposit_address.clone(), config.bridge_amount_sats) +// .await +// .unwrap(); +// rpc.mine_blocks((config.confirmation_threshold + 2).into()) +// .await +// .unwrap(); + +// let nonces = (0..required_nonce_count / 2) +// .map(|_| nonce_pair(&verifier.signer.keypair, &mut rand::rngs::OsRng)) +// .collect::>(); +// verifier +// .db +// .save_nonces(None, deposit_outpoint, &nonces) +// .await +// .unwrap(); + +// assert!(verifier +// .new_deposit(deposit_outpoint, signer_address.clone(), evm_address) +// .await +// .is_err_and(|e| { +// if let BridgeError::NoncesNotFound = e { +// true +// } else { +// println!("Error was {e}"); +// false +// } +// })); + +// // Enough nonces. +// let deposit_outpoint = rpc +// .send_to_address(&deposit_address.clone(), config.bridge_amount_sats) +// .await +// .unwrap(); +// rpc.mine_blocks((config.confirmation_threshold + 2).into()) +// .await +// .unwrap(); + +// let nonces = (0..required_nonce_count) +// .map(|_| nonce_pair(&verifier.signer.keypair, &mut rand::rngs::OsRng)) +// .collect::>(); +// verifier +// .db +// .save_nonces(None, deposit_outpoint, &nonces) +// .await +// .unwrap(); + +// verifier +// .new_deposit(deposit_outpoint, signer_address, evm_address) +// .await +// .unwrap(); +// } +// } diff --git a/core/tests/musig2.rs b/core/tests/musig2.rs index 5e975e59..59af2dde 100644 --- a/core/tests/musig2.rs +++ b/core/tests/musig2.rs @@ -1,47 +1,43 @@ +use bitcoin::key::Keypair; use bitcoin::opcodes::all::OP_CHECKSIG; +use bitcoin::secp256k1::{Message, PublicKey}; +use bitcoin::XOnlyPublicKey; use bitcoin::{hashes::Hash, script, Amount, ScriptBuf}; use bitcoincore_rpc::RpcApi; use clementine_core::builder::transaction::TxHandler; use clementine_core::musig2::{ - aggregate_nonces, aggregate_partial_signatures, MuSigPartialSignature, MuSigPubNonce, + aggregate_nonces, aggregate_partial_signatures, AggregateFromPublicKeys, Musig2Mode, }; -use clementine_core::utils::handle_taproot_witness_new; -use clementine_core::ByteArray32; +use clementine_core::utils::{handle_taproot_witness_new, SECP}; use clementine_core::{ actor::Actor, builder::{self}, config::BridgeConfig, extended_rpc::ExtendedRpc, - musig2::{create_key_agg_ctx, nonce_pair, partial_sign, MuSigNoncePair}, - utils, ByteArray66, + musig2::{nonce_pair, partial_sign, MuSigNoncePair}, + utils, }; use clementine_core::{database::Database, utils::initialize_logger}; -use secp256k1::{Keypair, Message, PublicKey, SECP256K1}; +use secp256k1::musig::{MusigAggNonce, MusigPartialSignature, MusigPubNonce}; use std::{env, thread}; mod common; -fn get_verifiers_keys( - config: &BridgeConfig, -) -> (Vec, secp256k1::XOnlyPublicKey, Vec) { +fn get_verifiers_keys(config: &BridgeConfig) -> (Vec, XOnlyPublicKey, Vec) { let verifiers_secret_keys = config.all_verifiers_secret_keys.clone().unwrap(); let verifiers_secret_public_keys: Vec = verifiers_secret_keys .iter() - .map(|sk| Keypair::from_secret_key(SECP256K1, sk)) + .map(|sk| Keypair::from_secret_key(&SECP, sk)) .collect(); let verifier_public_keys = verifiers_secret_public_keys .iter() .map(|kp| kp.public_key()) - .collect::>(); + .collect::>(); - let key_agg_ctx = create_key_agg_ctx(verifier_public_keys.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 untweaked_xonly_pubkey = + XOnlyPublicKey::from_musig2_pks(verifier_public_keys.clone(), None).unwrap(); ( verifiers_secret_public_keys, @@ -50,17 +46,17 @@ fn get_verifiers_keys( ) } -fn get_nonces(verifiers_secret_public_keys: Vec) -> (Vec, ByteArray66) { +fn get_nonces(verifiers_secret_public_keys: Vec) -> (Vec, MusigAggNonce) { let nonce_pairs: Vec = verifiers_secret_public_keys .iter() - .map(|kp| nonce_pair(kp, &mut secp256k1::rand::thread_rng())) + .map(|kp| nonce_pair(kp, &mut secp256k1::rand::thread_rng()).unwrap()) .collect(); let agg_nonce = aggregate_nonces( nonce_pairs .iter() - .map(|x| ByteArray66(x.1 .0)) - .collect::>(), + .map(|(_, musig_pub_nonces)| *musig_pub_nonces) + .collect::>(), ); (nonce_pairs, agg_nonce) @@ -98,7 +94,6 @@ async fn key_spend() { to_address.script_pubkey(), )]); let dummy_tx = builder::transaction::create_btc_tx(tx_ins, tx_outs); - let mut tx_details = TxHandler { txid: dummy_tx.compute_txid(), tx: dummy_tx, @@ -108,55 +103,53 @@ async fn key_spend() { out_scripts: vec![vec![]], out_taproot_spend_infos: vec![Some(to_address_spend.clone())], }; - let message = Actor::convert_tx_to_sighash_pubkey_spend(&mut tx_details, 0) - .unwrap() - .to_byte_array(); + + let message = Message::from_digest( + Actor::convert_tx_to_sighash_pubkey_spend(&mut tx_details, 0) + .unwrap() + .to_byte_array(), + ); let merkle_root = from_address_spend_info.merkle_root(); - let key_agg_ctx = create_key_agg_ctx(verifier_public_keys.clone(), merkle_root, true).unwrap(); + assert!(merkle_root.is_none()); - let partial_sigs: Vec = verifiers_secret_public_keys - .iter() - .zip(nonce_pairs.iter()) + let partial_sigs: Vec = verifiers_secret_public_keys + .into_iter() + .zip(nonce_pairs) .map(|(kp, nonce_pair)| { partial_sign( verifier_public_keys.clone(), - merkle_root, - true, + Some(Musig2Mode::OnlyKeySpend), nonce_pair.0, agg_nonce, kp, - ByteArray32(message), + message, ) + .unwrap() }) .collect(); + let final_signature = aggregate_partial_signatures( verifier_public_keys.clone(), - merkle_root, - true, - &agg_nonce, + Some(Musig2Mode::OnlyKeySpend), + agg_nonce, partial_sigs, - ByteArray32(message), + 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).unwrap(); - - let schnorr_sig = secp256k1::schnorr::Signature::from_slice(&final_signature).unwrap(); - SECP256K1 - .verify_schnorr( - &schnorr_sig, - &Message::from_digest(message), - &musig_agg_xonly_pubkey_wrapped, - ) + let agg_pk = XOnlyPublicKey::from_musig2_pks( + verifier_public_keys.clone(), + Some(Musig2Mode::OnlyKeySpend), + ) + .unwrap(); + SECP.verify_schnorr(&final_signature, &message, &agg_pk) .unwrap(); + rpc.mine_blocks(1).await.unwrap(); - tx_details.tx.input[0].witness.push(final_signature); + tx_details.tx.input[0] + .witness + .push(final_signature.serialize()); rpc.client .send_raw_transaction(&tx_details.tx) .await @@ -210,55 +203,52 @@ async fn key_spend_with_script() { out_scripts: vec![vec![]], out_taproot_spend_infos: vec![Some(to_address_spend.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(); - let key_agg_ctx = create_key_agg_ctx(verifier_public_keys.clone(), merkle_root, true).unwrap(); + let message = Message::from_digest( + Actor::convert_tx_to_sighash_pubkey_spend(&mut tx_details, 0) + .unwrap() + .to_byte_array(), + ); + let merkle_root = from_address_spend_info.merkle_root().unwrap(); - let partial_sigs: Vec = verifiers_secret_public_keys - .iter() - .zip(nonce_pairs.iter()) + let partial_sigs: Vec = verifiers_secret_public_keys + .into_iter() + .zip(nonce_pairs) .map(|(kp, nonce_pair)| { partial_sign( verifier_public_keys.clone(), - merkle_root, - true, + Some(Musig2Mode::KeySpendWithScript(merkle_root)), nonce_pair.0, agg_nonce, kp, - ByteArray32(message), + message, ) + .unwrap() }) .collect(); - let final_signature: [u8; 64] = aggregate_partial_signatures( + + let final_signature = aggregate_partial_signatures( verifier_public_keys.clone(), - merkle_root, - true, - &agg_nonce, + Some(Musig2Mode::KeySpendWithScript(merkle_root)), + agg_nonce, partial_sigs, - ByteArray32(message), + 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).unwrap(); + let agg_pk = XOnlyPublicKey::from_musig2_pks( + verifier_public_keys.clone(), + Some(Musig2Mode::KeySpendWithScript(merkle_root)), + ) + .unwrap(); - let schnorr_sig = secp256k1::schnorr::Signature::from_slice(&final_signature).unwrap(); - SECP256K1 - .verify_schnorr( - &schnorr_sig, - &Message::from_digest(message), - &musig_agg_xonly_pubkey_wrapped, - ) + SECP.verify_schnorr(&final_signature, &message, &agg_pk) .unwrap(); + rpc.mine_blocks(1).await.unwrap(); - tx_details.tx.input[0].witness.push(final_signature); + tx_details.tx.input[0] + .witness + .push(final_signature.serialize()); rpc.client .send_raw_transaction(&tx_details.tx) .await @@ -280,14 +270,9 @@ async fn script_spend() { get_verifiers_keys(&config); let (nonce_pairs, agg_nonce) = get_nonces(verifiers_secret_public_keys.clone()); - let key_agg_ctx = create_key_agg_ctx(verifier_public_keys.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 = - bitcoin::XOnlyPublicKey::from_slice(&musig_agg_xonly_pubkey.serialize()).unwrap(); + let agg_pk = XOnlyPublicKey::from_musig2_pks(verifier_public_keys.clone(), None).unwrap(); - let agg_xonly_pubkey = - bitcoin::XOnlyPublicKey::from_slice(&musig_agg_xonly_pubkey.serialize()).unwrap(); + let agg_xonly_pubkey = bitcoin::XOnlyPublicKey::from_slice(&agg_pk.serialize()).unwrap(); let musig2_script = bitcoin::script::Builder::new() .push_x_only_key(&agg_xonly_pubkey) .push_opcode(OP_CHECKSIG) @@ -295,7 +280,7 @@ async fn script_spend() { let scripts: Vec = vec![musig2_script]; let to_address = bitcoin::Address::p2tr( - SECP256K1, + &SECP, *utils::UNSPENDABLE_XONLY_PUBKEY, None, bitcoin::Network::Regtest, @@ -324,46 +309,43 @@ async fn script_spend() { out_scripts: vec![vec![]], out_taproot_spend_infos: vec![None], }; - let message = Actor::convert_tx_to_sighash_script_spend(&mut tx_details, 0, 0) - .unwrap() - .to_byte_array(); + let message = Message::from_digest( + Actor::convert_tx_to_sighash_script_spend(&mut tx_details, 0, 0) + .unwrap() + .to_byte_array(), + ); - let partial_sigs: Vec = verifiers_secret_public_keys - .iter() - .zip(nonce_pairs.iter()) + let partial_sigs: Vec = verifiers_secret_public_keys + .into_iter() + .zip(nonce_pairs) .map(|(kp, nonce_pair)| { partial_sign( verifier_public_keys.clone(), None, - false, nonce_pair.0, agg_nonce, kp, - ByteArray32(message), + message, ) + .unwrap() }) .collect(); - let final_signature: [u8; 64] = aggregate_partial_signatures( + let final_signature = aggregate_partial_signatures( verifier_public_keys.clone(), None, - false, - &agg_nonce, + agg_nonce, partial_sigs, - ByteArray32(message), + message, ) .unwrap(); - musig2::verify_single(musig_agg_pubkey, final_signature, message).unwrap(); - SECP256K1 - .verify_schnorr( - &secp256k1::schnorr::Signature::from_slice(&final_signature).unwrap(), - &Message::from_digest(message), - &musig_agg_xonly_pubkey_wrapped, - ) + + utils::SECP + .verify_schnorr(&final_signature, &message, &agg_xonly_pubkey) .unwrap(); - let schnorr_sig = secp256k1::schnorr::Signature::from_slice(&final_signature).unwrap(); - let witness_elements = vec![schnorr_sig.as_ref()]; + let witness_elements = vec![final_signature.as_ref()]; handle_taproot_witness_new(&mut tx_details, &witness_elements, 0, Some(0)).unwrap(); + rpc.mine_blocks(1).await.unwrap(); rpc.client diff --git a/core/tests/taproot.rs b/core/tests/taproot.rs index 5de04e19..046217bb 100644 --- a/core/tests/taproot.rs +++ b/core/tests/taproot.rs @@ -1,15 +1,15 @@ use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::opcodes::all::OP_CHECKSIG; use bitcoin::script::Builder; +use bitcoin::secp256k1::Scalar; use bitcoin::{Address, Amount, TapTweakHash, TxOut, XOnlyPublicKey}; use bitcoincore_rpc::RpcApi; use clementine_core::actor::Actor; use clementine_core::builder::transaction::TxHandler; use clementine_core::builder::{self}; use clementine_core::extended_rpc::ExtendedRpc; -use clementine_core::utils::handle_taproot_witness_new; +use clementine_core::utils::{handle_taproot_witness_new, SECP}; use clementine_core::{config::BridgeConfig, database::Database, utils::initialize_logger}; -use secp256k1::SECP256K1; use std::{env, thread}; mod common; @@ -25,8 +25,8 @@ async fn create_address_and_transaction_then_sign_transaction() { ) .await; - let (xonly_pk, _) = config.secret_key.public_key(SECP256K1).x_only_public_key(); - let address = Address::p2tr(SECP256K1, xonly_pk, None, config.network); + let (xonly_pk, _) = config.secret_key.public_key(&SECP).x_only_public_key(); + let address = Address::p2tr(&SECP, xonly_pk, None, config.network); let script = address.script_pubkey(); let tweaked_pk_script: [u8; 32] = script.as_bytes()[2..].try_into().unwrap(); @@ -35,9 +35,8 @@ async fn create_address_and_transaction_then_sign_transaction() { hasher.input(&xonly_pk.serialize()); xonly_pk .add_tweak( - SECP256K1, - &secp256k1::Scalar::from_be_bytes(TapTweakHash::from_engine(hasher).to_byte_array()) - .unwrap(), + &SECP, + &Scalar::from_be_bytes(TapTweakHash::from_engine(hasher).to_byte_array()).unwrap(), ) .unwrap(); diff --git a/scripts/schema.sql b/scripts/schema.sql index c28defdc..641082da 100644 --- a/scripts/schema.sql +++ b/scripts/schema.sql @@ -39,7 +39,7 @@ create table if not exists deposit_infos ( ); -- Verifier table for nonces related to deposits -/* This table holds the public, secret, and aggregated nonces related to a deposit. +/* This table holds the public and aggregated nonces related to a deposit. For each deposit, we have (2 + num_operators) nonce triples. The first triple is for move_commit_tx, the second triple is for move_reveal_tx, and the rest is for operator_takes_tx for each operator. Also for each triple, we hold the sig_hash to be signed to prevent reuse @@ -48,7 +48,6 @@ create table if not exists nonces ( deposit_outpoint text not null check (deposit_outpoint ~ '^[a-fA-F0-9]{64}:(0|[1-9][0-9]{0,9})$'), internal_idx int not null, pub_nonce bytea not null check (length(pub_nonce) = 66), - sec_nonce bytea not null check (length(sec_nonce) = 64), agg_nonce bytea check (length(agg_nonce) = 66), sighash bytea check (length(sighash) = 32), partial_sig bytea check (length(partial_sig) = 32),