From 91749df1f7db47b9417aa160199719ec3eb34a88 Mon Sep 17 00:00:00 2001 From: 0xatomFusion <179967211+0xatomFusion@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:15:05 +0100 Subject: [PATCH 1/4] test: connect the musig2 withdrawal test to regtest --- docker-compose-via.yml | 21 ++- via_verifier/lib/via_musig2/Cargo.toml | 11 +- .../lib/via_musig2/examples/withdrawal.rs | 135 +++++++++++------- 3 files changed, 112 insertions(+), 55 deletions(-) diff --git a/docker-compose-via.yml b/docker-compose-via.yml index 977e5d751..eaf4b3e49 100644 --- a/docker-compose-via.yml +++ b/docker-compose-via.yml @@ -50,6 +50,10 @@ services: bitcoin-cli $${RPC_ARGS} -rpcwallet=Alice sendtoaddress $${TEST_ADDRESS} 300 echo "Sent 300 BTC to TEST_ADDRESS: $${TEST_ADDRESS}" + echo "TEST_ADDRESS_2: $${TEST_ADDRESS_2}" + bitcoin-cli $${RPC_ARGS} -rpcwallet=Alice sendtoaddress $${TEST_ADDRESS_2} 300 + echo "Sent 300 BTC to TEST_ADDRESS_2: $${TEST_ADDRESS_2}" + echo "VERIFIER_1_ADDRESS: $${VERIFIER_1_ADDRESS}" bitcoin-cli $${RPC_ARGS} -rpcwallet=Alice sendtoaddress $${VERIFIER_1_ADDRESS} 300 echo "Sent 300 BTC to VERIFIER_1_ADDRESS: $${VERIFIER_1_ADDRESS}" @@ -62,6 +66,14 @@ services: bitcoin-cli $${RPC_ARGS} -rpcwallet=Alice sendtoaddress $${VERIFIER_3_ADDRESS} 300 echo "Sent 300 BTC to VERIFIER_3_ADDRESS: $${VERIFIER_3_ADDRESS}" + echo "BRIDGE_ADDRESS: $${BRIDGE_ADDRESS}" + bitcoin-cli $${RPC_ARGS} -rpcwallet=Alice sendtoaddress $${BRIDGE_ADDRESS} 300 + echo "Sent 300 BTC to BRIDGE_ADDRESS: $${BRIDGE_ADDRESS}" + + echo "BRIDGE_ADDRESS_TEST: $${BRIDGE_ADDRESS_TEST}" + bitcoin-cli $${RPC_ARGS} -rpcwallet=Alice sendtoaddress $${BRIDGE_ADDRESS_TEST} 300 + echo "Sent 300 BTC to BRIDGE_ADDRESS_TEST: $${BRIDGE_ADDRESS_TEST}" + bitcoin-cli $${RPC_ARGS} generatetoaddress 6 $${ALICE_ADDRESS} RAW_DESCRIPTOR=$$(bitcoin-cli $${RPC_ARGS} -rpcwallet=Alice getdescriptorinfo "addr($${TEST_ADDRESS})") @@ -89,9 +101,12 @@ services: environment: - BITCOIN_DATA=/home/bitcoin/.bitcoin - TEST_ADDRESS=bcrt1qx2lk0unukm80qmepjp49hwf9z6xnz0s73k9j56 + - TEST_ADDRESS_2=bcrt1quw8xktufw6v4vjl32ledaenfzghs9ckyujcc5z - VERIFIER_1_ADDRESS=bcrt1qw2mvkvm6alfhe86yf328kgvr7mupdx4vln7kpv - VERIFIER_2_ADDRESS=bcrt1qk8mkhrmgtq24nylzyzejznfzws6d98g4kmuuh4 - VERIFIER_3_ADDRESS=bcrt1q23lgaa90s85jvtl6dsrkvn0g949cwjkwuyzwdm + - BRIDGE_ADDRESS_TEST=bcrt1pdsrfe4xu50j9yl8k3t2x3zsl3u6nzg4eleg3udw406ev2kn2hmpqwvr44v + - BRIDGE_ADDRESS=bcrt1pu2js99s96f97ecngk8nz7hkaf7fgnjnpv8rxpaft7zd3h9q5affqdz0dch - RPC_ARGS=-chain=regtest -rpcconnect=bitcoind -rpcwait -rpcuser=rpcuser -rpcpassword=rpcpassword - SLEEP_SECONDS=5 @@ -108,18 +123,18 @@ services: - POSTGRES_PASSWORD=notsecurepassword celestia-node: - image: "ghcr.io/celestiaorg/celestia-node:v0.20.2-arabica" + image: "ghcr.io/celestiaorg/celestia-node:v0.20.4-mocha" container_name: celestia-node volumes: - type: bind source: ./volumes/celestia target: /home/celestia - command: celestia light start --headers.trusted-hash ${VIA_CELESTIA_CLIENT_TRUSTED_BLOCK_HASH} --core.ip validator-2.celestia-arabica-11.com --p2p.network arabica + command: celestia light start --headers.trusted-hash ${VIA_CELESTIA_CLIENT_TRUSTED_BLOCK_HASH} --core.ip consensus-full-mocha-4.celestia-mocha.com --p2p.network mocha ports: - '26658:26658' environment: - NODE_TYPE=light - - P2P_NETWORK=arabica + - P2P_NETWORK=mocha volumes: bitcoin_data: diff --git a/via_verifier/lib/via_musig2/Cargo.toml b/via_verifier/lib/via_musig2/Cargo.toml index b40d36994..1b06ed844 100644 --- a/via_verifier/lib/via_musig2/Cargo.toml +++ b/via_verifier/lib/via_musig2/Cargo.toml @@ -23,13 +23,18 @@ reqwest.workspace = true bitcoincore-rpc = "0.19.0" bitcoin = { version = "0.32.2", features = ["serde"] } musig2 = "0.2.0" -secp256k1_musig2 = { package = "secp256k1", version = "0.30.0", features = ["rand"]} +secp256k1_musig2 = { package = "secp256k1", version = "0.30.0", features = [ + "rand", + "hashes", +] } tokio = { version = "1.0", features = ["full"] } axum = "0.6" uuid = { version = "1.3", features = ["v4"] } hyper = { version = "0.14", features = ["full"] } base64 = "0.21" +[dev-dependencies] +bitcoincore-rpc = "0.19.0" [[example]] @@ -45,3 +50,7 @@ path = "examples/withdrawal.rs" [[example]] name = "coordinator" path = "examples/coordinator.rs" + +[[example]] +name = "generate_btc_pk" +path = "examples/generate_btc_pk.rs" diff --git a/via_verifier/lib/via_musig2/examples/withdrawal.rs b/via_verifier/lib/via_musig2/examples/withdrawal.rs index 108609ac6..e9f15ce39 100644 --- a/via_verifier/lib/via_musig2/examples/withdrawal.rs +++ b/via_verifier/lib/via_musig2/examples/withdrawal.rs @@ -1,32 +1,55 @@ use bitcoin::{ absolute, hashes::Hash, + secp256k1, sighash::{Prevouts, SighashCache}, taproot::TaprootSpendInfo, - Address as BitcoinAddress, Amount, Network, OutPoint, TapSighashType, Transaction, TxIn, TxOut, - Witness, XOnlyPublicKey, + Address as BitcoinAddress, Amount, CompressedPublicKey, Network, PrivateKey, TapSighashType, + Transaction, TxIn, TxOut, Witness, XOnlyPublicKey, }; -// use bitcoincore_rpc::Auth; use musig2::{ verify_single, CompactSignature, FirstRound, KeyAggContext, PartialSignature, SecNonceSpices, }; -use rand::{rngs::OsRng, Rng}; -use secp256k1_musig2::{PublicKey, Secp256k1, SecretKey}; +use rand::Rng; +use secp256k1_musig2::{schnorr, PublicKey, Secp256k1, SecretKey}; +use via_btc_client::{inscriber::Inscriber, types::NodeAuth}; + +const RPC_URL: &str = "http://0.0.0.0:18443"; +const RPC_USERNAME: &str = "rpcuser"; +const RPC_PASSWORD: &str = "rpcpassword"; +const NETWORK: Network = Network::Regtest; +const PK: &str = "cRaUbRSn8P8cXUcg6cMZ7oTZ1wbDjktYTsbdGw62tuqqD9ttQWMm"; #[tokio::main] async fn main() -> Result<(), Box> { // ------------------------------------------- // Setup: Create secret and public keys for three participants // ------------------------------------------- - let mut rng = OsRng; - let secret_key_1 = SecretKey::new(&mut rng); - let secret_key_2 = SecretKey::new(&mut rng); - let secret_key_3 = SecretKey::new(&mut rng); + + let private_key_1 = PrivateKey::from_wif(&String::from( + "cVZduZu265sWeAqFYygoDEE1FZ7wV9rpW5qdqjRkUehjaUMWLT1R", + ))?; + + let private_key_2 = PrivateKey::from_wif(&String::from( + "cUWA5dZXc6NwLovW3Kr9DykfY5ysFigKZM5Annzty7J8a43Fe2YF", + ))?; + + let private_key_3 = PrivateKey::from_wif(&String::from( + "cRaUbRSn8P8cXUcg6cMZ7oTZ1wbDjktYTsbdGw62tuqqD9ttQWMm", + ))?; let secp = Secp256k1::new(); - let public_key_1 = PublicKey::from_secret_key(&secp, &secret_key_1); - let public_key_2 = PublicKey::from_secret_key(&secp, &secret_key_2); - let public_key_3 = PublicKey::from_secret_key(&secp, &secret_key_3); + let secret_key_1 = SecretKey::from_byte_array(&private_key_1.inner.secret_bytes())?; + let public_key_1 = secp256k1_musig2::PublicKey::from_secret_key(&secp, &secret_key_1); + + let com_public_key_1 = CompressedPublicKey::from_slice(&public_key_1.serialize().to_vec())?; + let address_1 = BitcoinAddress::p2wpkh(&com_public_key_1, Network::Regtest); + + let secret_key_2 = SecretKey::from_byte_array(&private_key_2.inner.secret_bytes())?; + let public_key_2 = secp256k1_musig2::PublicKey::from_secret_key(&secp, &secret_key_2); + + let secret_key_3 = SecretKey::from_byte_array(&private_key_3.inner.secret_bytes())?; + let public_key_3 = secp256k1_musig2::PublicKey::from_secret_key(&secp, &secret_key_3); // ------------------------------------------- // Key aggregation (MuSig2) @@ -48,46 +71,30 @@ async fn main() -> Result<(), Box> { // ------------------------------------------- // Connect to Bitcoin node (adjust RPC credentials and URL) // ------------------------------------------- - // NOTE: Update these with real RPC credentials and URL - let _rpc_url = "http://127.0.0.1:18443"; - let _rpc_user = "user"; - let _rpc_pass = "pass"; - - // let client = BitcoinClient::new(rpc_url, Network::Regtest, Auth::UserPass(rpc_user.into(), rpc_pass.into()))?; + let inscriber = Inscriber::new( + RPC_URL, + NETWORK, + NodeAuth::UserPass(RPC_USERNAME.to_string(), RPC_PASSWORD.to_string()), + PK, + None, + ) + .await?; + let client = inscriber.get_client().await; // ------------------------------------------- - // Instead of fetching UTXOs from node, use a fake constant UTXO for demonstration - // Comment out real fetching code + // Fetching UTXOs from node // ------------------------------------------- - // let utxos = client.fetch_utxos(&address).await?; - // if utxos.is_empty() { - // eprintln!("No UTXOs found for this address. Please fund it first."); - // return Ok(()); - // } - - // We'll use a fake UTXO here - let fake_utxo_txid = bitcoin::Txid::from_slice(&[0x11; 32]).unwrap(); // just a dummy 32-byte txid - let fake_vout = 0; - let fake_utxo_amount = Amount::from_btc(1.0).unwrap(); // 1 BTC in the fake utxo - let fake_utxo = (fake_utxo_txid, fake_vout, fake_utxo_amount); + let utxos = client.fetch_utxos(&address).await?; // ------------------------------------------- - // Create a transaction spending this fake UTXO + // Create a transaction spending the UTXO // ------------------------------------------- - let send_amount = Amount::from_btc(0.1).unwrap(); // amount to send + let send_amount = Amount::from_btc(0.1).unwrap(); let fee_amount = Amount::from_btc(0.0001).unwrap(); - let change_amount = fake_utxo_amount - send_amount - fee_amount; - - // The recipient address - for demonstration let's send to the same aggregated address - // or another regtest address. - let recipient_address = address.clone(); - let change_address = address.clone(); + let change_amount = utxos[0].1.value - send_amount - fee_amount; let txin = TxIn { - previous_output: OutPoint { - txid: fake_utxo.0, - vout: fake_utxo.1, - }, + previous_output: utxos[0].0, sequence: bitcoin::Sequence(0xFFFFFFFF), witness: Witness::new(), script_sig: bitcoin::Script::new().into(), @@ -95,12 +102,12 @@ async fn main() -> Result<(), Box> { let txout_recipient = TxOut { value: send_amount, - script_pubkey: recipient_address.script_pubkey(), + script_pubkey: address_1.script_pubkey(), }; let txout_change = TxOut { value: change_amount, - script_pubkey: change_address.script_pubkey(), + script_pubkey: address.script_pubkey(), }; let mut unsigned_tx = Transaction { @@ -120,8 +127,8 @@ async fn main() -> Result<(), Box> { let sighash = sighash_cache.taproot_key_spend_signature_hash( 0, &Prevouts::All(&[TxOut { - value: fake_utxo_amount, - script_pubkey: recipient_address.script_pubkey(), + value: utxos[0].1.value, + script_pubkey: utxos[0].1.script_pubkey.clone(), }]), sighash_type, )?; @@ -203,8 +210,33 @@ async fn main() -> Result<(), Box> { let mut final_sig_with_hashtype = final_signature.serialize().to_vec(); final_sig_with_hashtype.push(sighash_type as u8); // For SIGHASH_DEFAULT this is 0x00 + let signature = bitcoin::taproot::Signature { + sighash_type, + signature: secp256k1::schnorr::Signature::from_slice(&final_signature.serialize())?, + }; + // For a key-path spend in taproot, the witness is just the signature - unsigned_tx.input[0].witness = Witness::from(vec![final_sig_with_hashtype]); + unsigned_tx.input[0].witness.push(signature.to_vec()); + + let secp = Secp256k1::new(); + + // ------------------------------------------- + // Verify the Schnorr signature + // ------------------------------------------- + let array: [u8; 64] = final_signature.serialize().try_into().unwrap(); + let sig = schnorr::Signature::from_byte_array(array); + + match secp.verify_schnorr( + &sig, + message.as_byte_array(), + &aggregated_pubkey.x_only_public_key().0, + ) { + Ok(_) => println!("Valid schnorr sig!"), + Err(e) => { + println!("Invalid schnorr sig: {:?}", e); + return Ok(()); + } + } // ------------------------------------------- // Print the signed raw transaction (in hex) @@ -212,9 +244,10 @@ async fn main() -> Result<(), Box> { let signed_raw_tx = bitcoin::consensus::encode::serialize_hex(&unsigned_tx); println!("Signed raw transaction (hex): {}", signed_raw_tx); - // NOTE: In real scenario, you could broadcast this transaction using the Bitcoin node RPC: - // client.broadcast_raw_tx(&signed_raw_tx).await?; - // Here we just printed it. + // ------------------------------------------- + // Broadcast the signed transation + // ------------------------------------------- + client.broadcast_signed_transaction(&signed_raw_tx).await?; Ok(()) } From 49af7c1b5bcfe9fc607779008ecad9d8dad9d424 Mon Sep 17 00:00:00 2001 From: Hamid Bateni Date: Wed, 15 Jan 2025 21:05:10 +0330 Subject: [PATCH 2/4] temp commit --- docker-compose-via.yml | 2 +- via_verifier/lib/via_musig2/Cargo.toml | 5 +- .../lib/via_musig2/examples/withdrawal.rs | 136 +++++++++++------ .../lib/via_musig2/examples/withdrawal2.rs | 139 ++++++++++++++++++ 4 files changed, 238 insertions(+), 44 deletions(-) create mode 100644 via_verifier/lib/via_musig2/examples/withdrawal2.rs diff --git a/docker-compose-via.yml b/docker-compose-via.yml index eaf4b3e49..6029a6287 100644 --- a/docker-compose-via.yml +++ b/docker-compose-via.yml @@ -105,7 +105,7 @@ services: - VERIFIER_1_ADDRESS=bcrt1qw2mvkvm6alfhe86yf328kgvr7mupdx4vln7kpv - VERIFIER_2_ADDRESS=bcrt1qk8mkhrmgtq24nylzyzejznfzws6d98g4kmuuh4 - VERIFIER_3_ADDRESS=bcrt1q23lgaa90s85jvtl6dsrkvn0g949cwjkwuyzwdm - - BRIDGE_ADDRESS_TEST=bcrt1pdsrfe4xu50j9yl8k3t2x3zsl3u6nzg4eleg3udw406ev2kn2hmpqwvr44v + - BRIDGE_ADDRESS_TEST=bcrt1pjwy0jgaacfqdnlfrtk9rll0gk5awacen40wwj7yxmyh27g5dtr6s2z74aa - BRIDGE_ADDRESS=bcrt1pu2js99s96f97ecngk8nz7hkaf7fgnjnpv8rxpaft7zd3h9q5affqdz0dch - RPC_ARGS=-chain=regtest -rpcconnect=bitcoind -rpcwait -rpcuser=rpcuser -rpcpassword=rpcpassword - SLEEP_SECONDS=5 diff --git a/via_verifier/lib/via_musig2/Cargo.toml b/via_verifier/lib/via_musig2/Cargo.toml index 1b06ed844..cac7a0502 100644 --- a/via_verifier/lib/via_musig2/Cargo.toml +++ b/via_verifier/lib/via_musig2/Cargo.toml @@ -51,6 +51,7 @@ path = "examples/withdrawal.rs" name = "coordinator" path = "examples/coordinator.rs" + [[example]] -name = "generate_btc_pk" -path = "examples/generate_btc_pk.rs" +name = "withdrawal2" +path = "examples/withdrawal2.rs" diff --git a/via_verifier/lib/via_musig2/examples/withdrawal.rs b/via_verifier/lib/via_musig2/examples/withdrawal.rs index e9f15ce39..8b8267701 100644 --- a/via_verifier/lib/via_musig2/examples/withdrawal.rs +++ b/via_verifier/lib/via_musig2/examples/withdrawal.rs @@ -1,17 +1,20 @@ +use std::str::FromStr; + use bitcoin::{ absolute, hashes::Hash, secp256k1, + secp256k1::schnorr, sighash::{Prevouts, SighashCache}, - taproot::TaprootSpendInfo, - Address as BitcoinAddress, Amount, CompressedPublicKey, Network, PrivateKey, TapSighashType, - Transaction, TxIn, TxOut, Witness, XOnlyPublicKey, + taproot::{TaprootBuilder, TaprootSpendInfo}, + Address as BitcoinAddress, Amount, CompressedPublicKey, Network, PrivateKey, ScriptBuf, + TapSighashType, Transaction, TxIn, TxOut, Witness, XOnlyPublicKey, }; use musig2::{ verify_single, CompactSignature, FirstRound, KeyAggContext, PartialSignature, SecNonceSpices, }; use rand::Rng; -use secp256k1_musig2::{schnorr, PublicKey, Secp256k1, SecretKey}; +use secp256k1_musig2::{PublicKey, Scalar, Secp256k1, SecretKey}; use via_btc_client::{inscriber::Inscriber, types::NodeAuth}; const RPC_URL: &str = "http://0.0.0.0:18443"; @@ -20,43 +23,67 @@ const RPC_PASSWORD: &str = "rpcpassword"; const NETWORK: Network = Network::Regtest; const PK: &str = "cRaUbRSn8P8cXUcg6cMZ7oTZ1wbDjktYTsbdGw62tuqqD9ttQWMm"; +// See https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs +use std::sync::LazyLock; + +static UNSPENDABLE_XONLY_PUBKEY: LazyLock = + LazyLock::new(|| { + XOnlyPublicKey::from_str("93c7378d96518a75448821c4f7c8f4bae7ce60f804d03d1f0628dd5dd0f5de51") + .unwrap() + }); + +static SECP: LazyLock> = + LazyLock::new(|| secp256k1::Secp256k1::new()); + #[tokio::main] async fn main() -> Result<(), Box> { // ------------------------------------------- // Setup: Create secret and public keys for three participants // ------------------------------------------- - let private_key_1 = PrivateKey::from_wif(&String::from( - "cVZduZu265sWeAqFYygoDEE1FZ7wV9rpW5qdqjRkUehjaUMWLT1R", - ))?; + let private_key_1 = + PrivateKey::from_wif("cVZduZu265sWeAqFYygoDEE1FZ7wV9rpW5qdqjRkUehjaUMWLT1R")?; + let private_key_2 = + PrivateKey::from_wif("cUWA5dZXc6NwLovW3Kr9DykfY5ysFigKZM5Annzty7J8a43Fe2YF")?; + let private_key_3 = + PrivateKey::from_wif("cRaUbRSn8P8cXUcg6cMZ7oTZ1wbDjktYTsbdGw62tuqqD9ttQWMm")?; - let private_key_2 = PrivateKey::from_wif(&String::from( - "cUWA5dZXc6NwLovW3Kr9DykfY5ysFigKZM5Annzty7J8a43Fe2YF", - ))?; + let secp = Secp256k1::new(); - let private_key_3 = PrivateKey::from_wif(&String::from( - "cRaUbRSn8P8cXUcg6cMZ7oTZ1wbDjktYTsbdGw62tuqqD9ttQWMm", - ))?; + let secret_key_1 = SecretKey::from_slice(&private_key_1.inner.secret_bytes())?; + let public_key_1: PublicKey = secret_key_1.public_key(&secp); - let secp = Secp256k1::new(); - let secret_key_1 = SecretKey::from_byte_array(&private_key_1.inner.secret_bytes())?; - let public_key_1 = secp256k1_musig2::PublicKey::from_secret_key(&secp, &secret_key_1); + let secret_key_2 = SecretKey::from_slice(&private_key_2.inner.secret_bytes())?; + let public_key_2: PublicKey = secret_key_2.public_key(&secp); + let secret_key_3 = SecretKey::from_slice(&private_key_3.inner.secret_bytes())?; + let public_key_3: PublicKey = secret_key_3.public_key(&secp); + + // ------------------------------------------- + // Create receiver addresses + // ------------------------------------------- let com_public_key_1 = CompressedPublicKey::from_slice(&public_key_1.serialize().to_vec())?; let address_1 = BitcoinAddress::p2wpkh(&com_public_key_1, Network::Regtest); - let secret_key_2 = SecretKey::from_byte_array(&private_key_2.inner.secret_bytes())?; - let public_key_2 = secp256k1_musig2::PublicKey::from_secret_key(&secp, &secret_key_2); - - let secret_key_3 = SecretKey::from_byte_array(&private_key_3.inner.secret_bytes())?; - let public_key_3 = secp256k1_musig2::PublicKey::from_secret_key(&secp, &secret_key_3); - // ------------------------------------------- // Key aggregation (MuSig2) // ------------------------------------------- let pubkeys = vec![public_key_1, public_key_2, public_key_3]; - let key_agg_ctx = KeyAggContext::new(pubkeys)?; - let aggregated_pubkey: PublicKey = key_agg_ctx.aggregated_pubkey(); + let mut musig_key_agg_cache = KeyAggContext::new(pubkeys)?; + + let tree_info = create_taproot_spend_info(Some(agg_xonly_pubkey_raw), scripts)?; + + let plain_tweak: [u8; 32] = *b"this could be a BIP32 tweak....\0"; + let xonly_tweak: [u8; 32] = *b"this could be a Taproot tweak..\0"; + + let plain_tweak = Scalar::from_be_bytes(plain_tweak).unwrap(); + let xonly_tweak = Scalar::from_be_bytes(xonly_tweak).unwrap(); + + musig_key_agg_cache = musig_key_agg_cache.with_plain_tweak(plain_tweak)?; + musig_key_agg_cache = musig_key_agg_cache.with_xonly_tweak(xonly_tweak)?; + musig_key_agg_cache.with_tweak(tweak, is_xonly); + + let aggregated_pubkey: PublicKey = musig_key_agg_cache.aggregated_pubkey(); // Convert to x-only pubkey for Taproot address let (xonly_agg_key, _parity) = aggregated_pubkey.x_only_public_key(); @@ -140,7 +167,7 @@ async fn main() -> Result<(), Box> { // ------------------------------------------- // First round: generate public nonces let mut first_round_1 = FirstRound::new( - key_agg_ctx.clone(), + musig_key_agg_cache.clone(), rand::thread_rng().gen::<[u8; 32]>(), 0, SecNonceSpices::new() @@ -149,7 +176,7 @@ async fn main() -> Result<(), Box> { )?; let mut first_round_2 = FirstRound::new( - key_agg_ctx.clone(), + musig_key_agg_cache.clone(), rand::thread_rng().gen::<[u8; 32]>(), 1, SecNonceSpices::new() @@ -158,7 +185,7 @@ async fn main() -> Result<(), Box> { )?; let mut first_round_3 = FirstRound::new( - key_agg_ctx.clone(), + musig_key_agg_cache.clone(), rand::thread_rng().gen::<[u8; 32]>(), 2, SecNonceSpices::new() @@ -212,7 +239,7 @@ async fn main() -> Result<(), Box> { let signature = bitcoin::taproot::Signature { sighash_type, - signature: secp256k1::schnorr::Signature::from_slice(&final_signature.serialize())?, + signature: schnorr::Signature::from_slice(&final_signature.serialize())?, }; // For a key-path spend in taproot, the witness is just the signature @@ -224,19 +251,19 @@ async fn main() -> Result<(), Box> { // Verify the Schnorr signature // ------------------------------------------- let array: [u8; 64] = final_signature.serialize().try_into().unwrap(); - let sig = schnorr::Signature::from_byte_array(array); - - match secp.verify_schnorr( - &sig, - message.as_byte_array(), - &aggregated_pubkey.x_only_public_key().0, - ) { - Ok(_) => println!("Valid schnorr sig!"), - Err(e) => { - println!("Invalid schnorr sig: {:?}", e); - return Ok(()); - } - } + let sig = schnorr::Signature::from_slice(&array)?; + + // match secp.verify_schnorr( + // &sig, + // message.as_byte_array(), + // &aggregated_pubkey.x_only_public_key().0, + // ) { + // Ok(_) => println!("Valid schnorr sig!"), + // Err(e) => { + // println!("Invalid schnorr sig: {:?}", e); + // return Ok(()); + // } + // } // ------------------------------------------- // Print the signed raw transaction (in hex) @@ -251,3 +278,30 @@ async fn main() -> Result<(), Box> { Ok(()) } + +fn create_taproot_spend_info( + internal_key: Option, + scripts: Vec, +) -> anyhow::Result { + let n = scripts.len(); + if n == 0 { + return Err(anyhow::anyhow!("No scripts provided")); + } + + let taproot_builder = if n > 1 { + let m: u8 = ((n - 1).ilog2() + 1) as u8; // m = ceil(log(n)) + let k = 2_usize.pow(m.into()) - n; + (0..n).try_fold(TaprootBuilder::new(), |acc, i| { + acc.add_leaf(m - ((i >= n - k) as u8), scripts[i].clone()) + })? + } else { + TaprootBuilder::new().add_leaf(0, scripts[0].clone())? + }; + let tree_info = match internal_key { + Some(xonly_pk) => taproot_builder.finalize(&SECP, xonly_pk).map_err(|e| e)?, + None => taproot_builder + .finalize(&SECP, *UNSPENDABLE_XONLY_PUBKEY) + .map_err(|e| e)?, + }; + Ok(tree_info) +} diff --git a/via_verifier/lib/via_musig2/examples/withdrawal2.rs b/via_verifier/lib/via_musig2/examples/withdrawal2.rs new file mode 100644 index 000000000..e4bfef7f5 --- /dev/null +++ b/via_verifier/lib/via_musig2/examples/withdrawal2.rs @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Demonstrate creating a transaction that spends to and from p2tr outputs. + +use std::str::FromStr; + +use bitcoin::{ + hashes::Hash, + key::{Keypair, TapTweak, TweakedKeypair, UntweakedPublicKey}, + locktime::absolute, + secp256k1::{rand, Message, Secp256k1, SecretKey, Signing, Verification}, + sighash::{Prevouts, SighashCache, TapSighashType}, + transaction, Address, Amount, Network, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, + Txid, Witness, +}; + +const DUMMY_UTXO_AMOUNT: Amount = Amount::from_sat(20_000_000); +const SPEND_AMOUNT: Amount = Amount::from_sat(5_000_000); +const CHANGE_AMOUNT: Amount = Amount::from_sat(14_999_000); // 1000 sat fee. + +fn main() { + let secp = Secp256k1::new(); + + // Get a keypair we control. In a real application these would come from a stored secret. + let keypair = senders_keys(&secp); + let (internal_key, _parity) = keypair.x_only_public_key(); + + // Get an unspent output that is locked to the key above that we control. + // In a real application these would come from the chain. + let (dummy_out_point, dummy_utxo) = dummy_unspent_transaction_output(&secp, internal_key); + + // Get an address to send to. + let address = receivers_address(); + + // The input for the transaction we are constructing. + let input = TxIn { + previous_output: dummy_out_point, // The dummy output we are spending. + script_sig: ScriptBuf::default(), // For a p2tr script_sig is empty. + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Witness::default(), // Filled in after signing. + }; + + // The spend output is locked to a key controlled by the receiver. + let spend = TxOut { + value: SPEND_AMOUNT, + script_pubkey: address.script_pubkey(), + }; + + // The change output is locked to a key controlled by us. + let change = TxOut { + value: CHANGE_AMOUNT, + script_pubkey: ScriptBuf::new_p2tr(&secp, internal_key, None), // Change comes back to us. + }; + + // The transaction we want to sign and broadcast. + let mut unsigned_tx = Transaction { + version: transaction::Version::TWO, // Post BIP-68. + lock_time: absolute::LockTime::ZERO, // Ignore the locktime. + input: vec![input], // Input goes into index 0. + output: vec![spend, change], // Outputs, order does not matter. + }; + let input_index = 0; + + // Get the sighash to sign. + + let sighash_type = TapSighashType::Default; + let prevouts = vec![dummy_utxo]; + let prevouts = Prevouts::All(&prevouts); + + let mut sighasher = SighashCache::new(&mut unsigned_tx); + let sighash = sighasher + .taproot_key_spend_signature_hash(input_index, &prevouts, sighash_type) + .expect("failed to construct sighash"); + + // Sign the sighash using the secp256k1 library (exported by rust-bitcoin). + let tweaked: TweakedKeypair = keypair.tap_tweak(&secp, None); + let msg = Message::from_digest_slice(&sighash.to_byte_array()).expect("32 bytes"); + let signature = secp.sign_schnorr(&msg, &tweaked.to_inner()); + + // Update the witness stack. + let signature = bitcoin::taproot::Signature { + signature, + sighash_type, + }; + *sighasher.witness_mut(input_index).unwrap() = Witness::p2tr_key_spend(&signature); + + // Get the signed transaction. + let tx = sighasher.into_transaction(); + + // BOOM! Transaction signed and ready to broadcast. + println!("{:#?}", tx); +} + +/// An example of keys controlled by the transaction sender. +/// +/// In a real application these would be actual secrets. +fn senders_keys(secp: &Secp256k1) -> Keypair { + let sk = SecretKey::new(&mut rand::thread_rng()); + Keypair::from_secret_key(secp, &sk) +} + +/// A dummy address for the receiver. +/// +/// We lock the spend output to the key associated with this address. +/// +/// (FWIW this is an arbitrary mainnet address from block 805222.) +fn receivers_address() -> Address { + Address::from_str("bc1p0dq0tzg2r780hldthn5mrznmpxsxc0jux5f20fwj0z3wqxxk6fpqm7q0va") + .expect("a valid address") + .require_network(Network::Bitcoin) + .expect("valid address for mainnet") +} + +/// Creates a p2wpkh output locked to the key associated with `wpkh`. +/// +/// An utxo is described by the `OutPoint` (txid and index within the transaction that it was +/// created). Using the out point one can get the transaction by `txid` and using the `vout` get the +/// transaction value and script pubkey (`TxOut`) of the utxo. +/// +/// This output is locked to keys that we control, in a real application this would be a valid +/// output taken from a transaction that appears in the chain. +fn dummy_unspent_transaction_output( + secp: &Secp256k1, + internal_key: UntweakedPublicKey, +) -> (OutPoint, TxOut) { + let script_pubkey = ScriptBuf::new_p2tr(secp, internal_key, None); + + let out_point = OutPoint { + txid: Txid::all_zeros(), // Obviously invalid. + vout: 0, + }; + + let utxo = TxOut { + value: DUMMY_UTXO_AMOUNT, + script_pubkey, + }; + + (out_point, utxo) +} From a72eae7254effe25bbca852483e67cb7c7c3a597 Mon Sep 17 00:00:00 2001 From: Hamid Bateni Date: Thu, 16 Jan 2025 14:19:28 +0330 Subject: [PATCH 3/4] temp commit 2 --- docker-compose-via.yml | 28 +- via_verifier/lib/via_musig2/Cargo.toml | 5 + .../lib/via_musig2/examples/withdrawal2.rs | 95 +++---- .../lib/via_musig2/examples/withdrawal3.rs | 248 ++++++++++++++++++ 4 files changed, 318 insertions(+), 58 deletions(-) create mode 100644 via_verifier/lib/via_musig2/examples/withdrawal3.rs diff --git a/docker-compose-via.yml b/docker-compose-via.yml index 6029a6287..a18efd693 100644 --- a/docker-compose-via.yml +++ b/docker-compose-via.yml @@ -105,7 +105,7 @@ services: - VERIFIER_1_ADDRESS=bcrt1qw2mvkvm6alfhe86yf328kgvr7mupdx4vln7kpv - VERIFIER_2_ADDRESS=bcrt1qk8mkhrmgtq24nylzyzejznfzws6d98g4kmuuh4 - VERIFIER_3_ADDRESS=bcrt1q23lgaa90s85jvtl6dsrkvn0g949cwjkwuyzwdm - - BRIDGE_ADDRESS_TEST=bcrt1pjwy0jgaacfqdnlfrtk9rll0gk5awacen40wwj7yxmyh27g5dtr6s2z74aa + - BRIDGE_ADDRESS_TEST=bcrt1pcx974cg2w66cqhx67zadf85t8k4sd2wp68l8x8agd3aj4tuegsgsz97amg - BRIDGE_ADDRESS=bcrt1pu2js99s96f97ecngk8nz7hkaf7fgnjnpv8rxpaft7zd3h9q5affqdz0dch - RPC_ARGS=-chain=regtest -rpcconnect=bitcoind -rpcwait -rpcuser=rpcuser -rpcpassword=rpcpassword - SLEEP_SECONDS=5 @@ -122,19 +122,19 @@ services: environment: - POSTGRES_PASSWORD=notsecurepassword - celestia-node: - image: "ghcr.io/celestiaorg/celestia-node:v0.20.4-mocha" - container_name: celestia-node - volumes: - - type: bind - source: ./volumes/celestia - target: /home/celestia - command: celestia light start --headers.trusted-hash ${VIA_CELESTIA_CLIENT_TRUSTED_BLOCK_HASH} --core.ip consensus-full-mocha-4.celestia-mocha.com --p2p.network mocha - ports: - - '26658:26658' - environment: - - NODE_TYPE=light - - P2P_NETWORK=mocha + # celestia-node: + # image: "ghcr.io/celestiaorg/celestia-node:v0.20.4-mocha" + # container_name: celestia-node + # volumes: + # - type: bind + # source: ./volumes/celestia + # target: /home/celestia + # command: celestia light start --headers.trusted-hash ${VIA_CELESTIA_CLIENT_TRUSTED_BLOCK_HASH} --core.ip consensus-full-mocha-4.celestia-mocha.com --p2p.network mocha + # ports: + # - '26658:26658' + # environment: + # - NODE_TYPE=light + # - P2P_NETWORK=mocha volumes: bitcoin_data: diff --git a/via_verifier/lib/via_musig2/Cargo.toml b/via_verifier/lib/via_musig2/Cargo.toml index cac7a0502..024cd28bc 100644 --- a/via_verifier/lib/via_musig2/Cargo.toml +++ b/via_verifier/lib/via_musig2/Cargo.toml @@ -55,3 +55,8 @@ path = "examples/coordinator.rs" [[example]] name = "withdrawal2" path = "examples/withdrawal2.rs" + + +[[example]] +name = "withdrawal3" +path = "examples/withdrawal3.rs" diff --git a/via_verifier/lib/via_musig2/examples/withdrawal2.rs b/via_verifier/lib/via_musig2/examples/withdrawal2.rs index e4bfef7f5..7a1af55c6 100644 --- a/via_verifier/lib/via_musig2/examples/withdrawal2.rs +++ b/via_verifier/lib/via_musig2/examples/withdrawal2.rs @@ -6,28 +6,64 @@ use std::str::FromStr; use bitcoin::{ hashes::Hash, - key::{Keypair, TapTweak, TweakedKeypair, UntweakedPublicKey}, + key::{Keypair, TapTweak, TweakedKeypair}, locktime::absolute, - secp256k1::{rand, Message, Secp256k1, SecretKey, Signing, Verification}, + secp256k1::{Message, Secp256k1, SecretKey}, sighash::{Prevouts, SighashCache, TapSighashType}, - transaction, Address, Amount, Network, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, - Txid, Witness, + transaction, Address, Amount, Network, PrivateKey, ScriptBuf, Sequence, Transaction, TxIn, + TxOut, Witness, }; +use via_btc_client::{inscriber::Inscriber, types::NodeAuth}; + +const RPC_URL: &str = "http://0.0.0.0:18443"; +const RPC_USERNAME: &str = "rpcuser"; +const RPC_PASSWORD: &str = "rpcpassword"; +const NETWORK: Network = Network::Regtest; +const PK: &str = "cRaUbRSn8P8cXUcg6cMZ7oTZ1wbDjktYTsbdGw62tuqqD9ttQWMm"; -const DUMMY_UTXO_AMOUNT: Amount = Amount::from_sat(20_000_000); const SPEND_AMOUNT: Amount = Amount::from_sat(5_000_000); -const CHANGE_AMOUNT: Amount = Amount::from_sat(14_999_000); // 1000 sat fee. -fn main() { +#[tokio::main] +async fn main() -> Result<(), Box> { let secp = Secp256k1::new(); // Get a keypair we control. In a real application these would come from a stored secret. - let keypair = senders_keys(&secp); + let private_key = + PrivateKey::from_wif("cVZduZu265sWeAqFYygoDEE1FZ7wV9rpW5qdqjRkUehjaUMWLT1R").unwrap(); + + let secret_key = SecretKey::from_slice(&private_key.inner.secret_bytes()).unwrap(); + + let keypair = Keypair::from_secret_key(&secp, &secret_key); + let (internal_key, _parity) = keypair.x_only_public_key(); + let address = Address::p2tr(&secp, internal_key, None, NETWORK); + + println!("address: {}", address); + + // ------------------------------------------- + // Connect to Bitcoin node (adjust RPC credentials and URL) + // ------------------------------------------- + let inscriber = Inscriber::new( + RPC_URL, + NETWORK, + NodeAuth::UserPass(RPC_USERNAME.to_string(), RPC_PASSWORD.to_string()), + PK, + None, + ) + .await?; + let client = inscriber.get_client().await; + + // ------------------------------------------- + // Fetching UTXOs from node + // ------------------------------------------- + let utxos = client.fetch_utxos(&address).await?; + // Get an unspent output that is locked to the key above that we control. // In a real application these would come from the chain. - let (dummy_out_point, dummy_utxo) = dummy_unspent_transaction_output(&secp, internal_key); + let (dummy_out_point, dummy_utxo) = utxos[0].clone(); + + let change_amount = dummy_utxo.value - SPEND_AMOUNT; // Get an address to send to. let address = receivers_address(); @@ -48,7 +84,7 @@ fn main() { // The change output is locked to a key controlled by us. let change = TxOut { - value: CHANGE_AMOUNT, + value: change_amount, script_pubkey: ScriptBuf::new_p2tr(&secp, internal_key, None), // Change comes back to us. }; @@ -89,14 +125,12 @@ fn main() { // BOOM! Transaction signed and ready to broadcast. println!("{:#?}", tx); -} -/// An example of keys controlled by the transaction sender. -/// -/// In a real application these would be actual secrets. -fn senders_keys(secp: &Secp256k1) -> Keypair { - let sk = SecretKey::new(&mut rand::thread_rng()); - Keypair::from_secret_key(secp, &sk) + let tx_hex = bitcoin::consensus::encode::serialize_hex(&tx); + let res = client.broadcast_signed_transaction(&tx_hex).await?; + println!("res: {:?}", res); + + Ok(()) } /// A dummy address for the receiver. @@ -110,30 +144,3 @@ fn receivers_address() -> Address { .require_network(Network::Bitcoin) .expect("valid address for mainnet") } - -/// Creates a p2wpkh output locked to the key associated with `wpkh`. -/// -/// An utxo is described by the `OutPoint` (txid and index within the transaction that it was -/// created). Using the out point one can get the transaction by `txid` and using the `vout` get the -/// transaction value and script pubkey (`TxOut`) of the utxo. -/// -/// This output is locked to keys that we control, in a real application this would be a valid -/// output taken from a transaction that appears in the chain. -fn dummy_unspent_transaction_output( - secp: &Secp256k1, - internal_key: UntweakedPublicKey, -) -> (OutPoint, TxOut) { - let script_pubkey = ScriptBuf::new_p2tr(secp, internal_key, None); - - let out_point = OutPoint { - txid: Txid::all_zeros(), // Obviously invalid. - vout: 0, - }; - - let utxo = TxOut { - value: DUMMY_UTXO_AMOUNT, - script_pubkey, - }; - - (out_point, utxo) -} diff --git a/via_verifier/lib/via_musig2/examples/withdrawal3.rs b/via_verifier/lib/via_musig2/examples/withdrawal3.rs new file mode 100644 index 000000000..da294752e --- /dev/null +++ b/via_verifier/lib/via_musig2/examples/withdrawal3.rs @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Demonstrate creating a transaction that spends to and from p2tr outputs with musig2. + +use std::str::FromStr; + +use bitcoin::{ + hashes::Hash, + key::{Keypair, TapTweak, TweakedKeypair}, + locktime::absolute, + secp256k1::{Message, Secp256k1, SecretKey}, + sighash::{Prevouts, SighashCache, TapSighashType}, + transaction, Address, Amount, Network, PrivateKey, PublicKey, ScriptBuf, Sequence, Transaction, + TxIn, TxOut, Witness, XOnlyPublicKey, +}; +use musig2::{secp::Scalar, KeyAggContext}; +use rand::Rng; +use secp256k1_musig2::schnorr::Signature; +use via_btc_client::{inscriber::Inscriber, types::NodeAuth}; + +const RPC_URL: &str = "http://0.0.0.0:18443"; +const RPC_USERNAME: &str = "rpcuser"; +const RPC_PASSWORD: &str = "rpcpassword"; +const NETWORK: Network = Network::Regtest; +const PK: &str = "cRaUbRSn8P8cXUcg6cMZ7oTZ1wbDjktYTsbdGw62tuqqD9ttQWMm"; +const SPEND_AMOUNT: Amount = Amount::from_sat(5_000_000); + +#[tokio::main] +async fn main() -> Result<(), Box> { + let secp = Secp256k1::new(); + + // Get a keypair we control. In a real application these would come from a stored secret. + let private_key_1 = + PrivateKey::from_wif("cVZduZu265sWeAqFYygoDEE1FZ7wV9rpW5qdqjRkUehjaUMWLT1R").unwrap(); + + let private_key_2 = + PrivateKey::from_wif("cUWA5dZXc6NwLovW3Kr9DykfY5ysFigKZM5Annzty7J8a43Fe2YF").unwrap(); + + let private_key_3 = + PrivateKey::from_wif("cRaUbRSn8P8cXUcg6cMZ7oTZ1wbDjktYTsbdGw62tuqqD9ttQWMm").unwrap(); + + let secret_key_1 = SecretKey::from_slice(&private_key_1.inner.secret_bytes()).unwrap(); + let secret_key_2 = SecretKey::from_slice(&private_key_2.inner.secret_bytes()).unwrap(); + let secret_key_3 = SecretKey::from_slice(&private_key_3.inner.secret_bytes()).unwrap(); + + let keypair_1 = Keypair::from_secret_key(&secp, &secret_key_1); + let keypair_2 = Keypair::from_secret_key(&secp, &secret_key_2); + let keypair_3 = Keypair::from_secret_key(&secp, &secret_key_3); + + let (internal_key_1, parity_1) = keypair_1.x_only_public_key(); + let (internal_key_2, parity_2) = keypair_2.x_only_public_key(); + let (internal_key_3, parity_3) = keypair_3.x_only_public_key(); + + // ------------------------------------------- + // Key aggregation (MuSig2) + // ------------------------------------------- + let pubkeys = vec![ + musig2::secp256k1::PublicKey::from_slice(&internal_key_1.public_key(parity_1).serialize()) + .unwrap(), + musig2::secp256k1::PublicKey::from_slice(&internal_key_2.public_key(parity_2).serialize()) + .unwrap(), + musig2::secp256k1::PublicKey::from_slice(&internal_key_3.public_key(parity_3).serialize()) + .unwrap(), + ]; + + let mut musig_key_agg_cache = KeyAggContext::new(pubkeys)?; + + let agg_pubkey: secp256k1_musig2::PublicKey = musig_key_agg_cache.aggregated_pubkey(); + + let final_internal_key = agg_pubkey.x_only_public_key(); + + let final_internal_key = XOnlyPublicKey::from_slice(&final_internal_key.0.serialize())?; + let address = Address::p2tr(&secp, final_internal_key, None, NETWORK); + + println!("address: {}", address); + + // ------------------------------------------- + // Connect to Bitcoin node (adjust RPC credentials and URL) + // ------------------------------------------- + let inscriber = Inscriber::new( + RPC_URL, + NETWORK, + NodeAuth::UserPass(RPC_USERNAME.to_string(), RPC_PASSWORD.to_string()), + PK, + None, + ) + .await?; + let client = inscriber.get_client().await; + + // ------------------------------------------- + // Fetching UTXOs from node + // ------------------------------------------- + let utxos = client.fetch_utxos(&address).await?; + + // Get an unspent output that is locked to the key above that we control. + // In a real application these would come from the chain. + let (dummy_out_point, dummy_utxo) = utxos[0].clone(); + + let change_amount = dummy_utxo.value - SPEND_AMOUNT; + + // Get an address to send to. + let address = receivers_address(); + + // The input for the transaction we are constructing. + let input = TxIn { + previous_output: dummy_out_point, // The dummy output we are spending. + script_sig: ScriptBuf::default(), // For a p2tr script_sig is empty. + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Witness::default(), // Filled in after signing. + }; + + // The spend output is locked to a key controlled by the receiver. + let spend = TxOut { + value: SPEND_AMOUNT, + script_pubkey: address.script_pubkey(), + }; + + // The change output is locked to a key controlled by us. + let change = TxOut { + value: change_amount, + script_pubkey: ScriptBuf::new_p2tr(&secp, final_internal_key, None), // Change comes back to us. + }; + + // The transaction we want to sign and broadcast. + let mut unsigned_tx = Transaction { + version: transaction::Version::TWO, // Post BIP-68. + lock_time: absolute::LockTime::ZERO, // Ignore the locktime. + input: vec![input], // Input goes into index 0. + output: vec![spend, change], // Outputs, order does not matter. + }; + let input_index = 0; + + // Get the sighash to sign. + let sighash_type = TapSighashType::Default; + let prevouts = vec![dummy_utxo]; + let prevouts = Prevouts::All(&prevouts); + + let mut sighasher = SighashCache::new(&mut unsigned_tx); + let sighash = sighasher + .taproot_key_spend_signature_hash(input_index, &prevouts, sighash_type) + .expect("failed to construct sighash"); + + // ------------------------------------------- + // MuSig2 Signing Process + // ------------------------------------------- + use musig2::{FirstRound, SecNonceSpices}; + use rand::thread_rng; + + // Convert bitcoin::SecretKey to musig2::SecretKey for each participant + let secret_key_1 = musig2::secp256k1::SecretKey::from_slice(&secret_key_1[..]).unwrap(); + let secret_key_2 = musig2::secp256k1::SecretKey::from_slice(&secret_key_2[..]).unwrap(); + let secret_key_3 = musig2::secp256k1::SecretKey::from_slice(&secret_key_3[..]).unwrap(); + + // Apply taproot tweak to the aggregation context + let merkle_root = [0u8; 32]; // No script path in this example + let musig_key_agg_cache = musig_key_agg_cache + .with_taproot_tweak(&merkle_root) + .unwrap(); + + // First round: Generate nonces + let mut first_round_1 = FirstRound::new( + musig_key_agg_cache.clone(), + thread_rng().gen::<[u8; 32]>(), + 0, + SecNonceSpices::new() + .with_seckey(secret_key_1) + .with_message(&sighash.to_byte_array()), + )?; + + let mut first_round_2 = FirstRound::new( + musig_key_agg_cache.clone(), + thread_rng().gen::<[u8; 32]>(), + 1, + SecNonceSpices::new() + .with_seckey(secret_key_2) + .with_message(&sighash.to_byte_array()), + )?; + + let mut first_round_3 = FirstRound::new( + musig_key_agg_cache.clone(), + thread_rng().gen::<[u8; 32]>(), + 2, + SecNonceSpices::new() + .with_seckey(secret_key_3) + .with_message(&sighash.to_byte_array()), + )?; + + // Exchange nonces + let nonce_1 = first_round_1.our_public_nonce(); + let nonce_2 = first_round_2.our_public_nonce(); + let nonce_3 = first_round_3.our_public_nonce(); + + first_round_1.receive_nonce(1, nonce_2.clone())?; + first_round_1.receive_nonce(2, nonce_3.clone())?; + first_round_2.receive_nonce(0, nonce_1.clone())?; + first_round_2.receive_nonce(2, nonce_3.clone())?; + first_round_3.receive_nonce(0, nonce_1.clone())?; + first_round_3.receive_nonce(1, nonce_2.clone())?; + + // Second round: Create partial signatures + let binding = sighash.to_byte_array(); + let mut second_round_1 = first_round_1.finalize(secret_key_1, &binding)?; + let binding = sighash.to_byte_array(); + let second_round_2 = first_round_2.finalize(secret_key_2, &binding)?; + let binding = sighash.to_byte_array(); + let second_round_3 = first_round_3.finalize(secret_key_3, &binding)?; + // Combine partial signatures + let partial_sig_2: [u8; 32] = second_round_2.our_signature(); + let partial_sig_3: [u8; 32] = second_round_3.our_signature(); + + second_round_1.receive_signature(1, Scalar::from_slice(&partial_sig_2).unwrap())?; + second_round_1.receive_signature(2, Scalar::from_slice(&partial_sig_3).unwrap())?; + + let final_signature: Signature = second_round_1.finalize()?; + + // Update the witness stack with the aggregated signature + let signature = bitcoin::taproot::Signature { + signature: bitcoin::secp256k1::schnorr::Signature::from_slice( + &final_signature.serialize(), + )?, + sighash_type, + }; + *sighasher.witness_mut(input_index).unwrap() = Witness::p2tr_key_spend(&signature); + + // Get the signed transaction + let tx = sighasher.into_transaction(); + + // BOOM! Transaction signed and ready to broadcast. + println!("{:#?}", tx); + + let tx_hex = bitcoin::consensus::encode::serialize_hex(&tx); + let res = client.broadcast_signed_transaction(&tx_hex).await?; + println!("res: {:?}", res); + + Ok(()) +} + +/// A dummy address for the receiver. +/// +/// We lock the spend output to the key associated with this address. +/// +/// (FWIW this is an arbitrary mainnet address from block 805222.) +fn receivers_address() -> Address { + Address::from_str("bc1p0dq0tzg2r780hldthn5mrznmpxsxc0jux5f20fwj0z3wqxxk6fpqm7q0va") + .expect("a valid address") + .require_network(Network::Bitcoin) + .expect("valid address for mainnet") +} From 05db7c9b7ceab4c26e49745e35db90a6e0d26642 Mon Sep 17 00:00:00 2001 From: Hamid Bateni Date: Thu, 16 Jan 2025 14:38:49 +0330 Subject: [PATCH 4/4] temp commit 3 --- .../lib/via_musig2/examples/withdrawal3.rs | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/via_verifier/lib/via_musig2/examples/withdrawal3.rs b/via_verifier/lib/via_musig2/examples/withdrawal3.rs index da294752e..3098d6b51 100644 --- a/via_verifier/lib/via_musig2/examples/withdrawal3.rs +++ b/via_verifier/lib/via_musig2/examples/withdrawal3.rs @@ -10,8 +10,8 @@ use bitcoin::{ locktime::absolute, secp256k1::{Message, Secp256k1, SecretKey}, sighash::{Prevouts, SighashCache, TapSighashType}, - transaction, Address, Amount, Network, PrivateKey, PublicKey, ScriptBuf, Sequence, Transaction, - TxIn, TxOut, Witness, XOnlyPublicKey, + transaction, Address, Amount, Network, PrivateKey, PublicKey, ScriptBuf, Sequence, + TapTweakHash, Transaction, TxIn, TxOut, Witness, XOnlyPublicKey, }; use musig2::{secp::Scalar, KeyAggContext}; use rand::Rng; @@ -65,12 +65,23 @@ async fn main() -> Result<(), Box> { let mut musig_key_agg_cache = KeyAggContext::new(pubkeys)?; - let agg_pubkey: secp256k1_musig2::PublicKey = musig_key_agg_cache.aggregated_pubkey(); + let agg_pubkey = musig_key_agg_cache.aggregated_pubkey::(); + let (xonly_agg_key, _) = agg_pubkey.x_only_public_key(); - let final_internal_key = agg_pubkey.x_only_public_key(); + // Convert to bitcoin XOnlyPublicKey first + let internal_key = bitcoin::XOnlyPublicKey::from_slice(&xonly_agg_key.serialize())?; - let final_internal_key = XOnlyPublicKey::from_slice(&final_internal_key.0.serialize())?; - let address = Address::p2tr(&secp, final_internal_key, None, NETWORK); + // Calculate taproot tweak + let tap_tweak = TapTweakHash::from_key_and_tweak(internal_key, None); + let tweak = tap_tweak.to_scalar(); + let tweak_bytes = tweak.to_be_bytes(); + let tweak = secp256k1_musig2::Scalar::from_be_bytes(tweak_bytes).unwrap(); + + // Apply tweak to the key aggregation context before signing + musig_key_agg_cache = musig_key_agg_cache.with_xonly_tweak(tweak)?; + + // Use internal_key for address creation + let address = Address::p2tr(&secp, internal_key, None, NETWORK); println!("address: {}", address); @@ -118,7 +129,7 @@ async fn main() -> Result<(), Box> { // The change output is locked to a key controlled by us. let change = TxOut { value: change_amount, - script_pubkey: ScriptBuf::new_p2tr(&secp, final_internal_key, None), // Change comes back to us. + script_pubkey: ScriptBuf::new_p2tr(&secp, internal_key, None), // Change comes back to us. }; // The transaction we want to sign and broadcast. @@ -151,15 +162,9 @@ async fn main() -> Result<(), Box> { let secret_key_2 = musig2::secp256k1::SecretKey::from_slice(&secret_key_2[..]).unwrap(); let secret_key_3 = musig2::secp256k1::SecretKey::from_slice(&secret_key_3[..]).unwrap(); - // Apply taproot tweak to the aggregation context - let merkle_root = [0u8; 32]; // No script path in this example - let musig_key_agg_cache = musig_key_agg_cache - .with_taproot_tweak(&merkle_root) - .unwrap(); - // First round: Generate nonces let mut first_round_1 = FirstRound::new( - musig_key_agg_cache.clone(), + musig_key_agg_cache.clone(), // Use tweaked context thread_rng().gen::<[u8; 32]>(), 0, SecNonceSpices::new()