diff --git a/Cargo.toml b/Cargo.toml index 33fb15a..d3adbdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,12 @@ categories = ["cryptography"] default = ["with_p256k1_bindgen"] with_p256k1_bindgen = ["p256k1/with_bindgen"] with_v1 = [] -testing = [] +testing = ["dep:bitcoin", "dep:bitcoinconsensus"] [dependencies] +bitcoin = { version = "0.32", default-features = false, features = ["serde", "rand-std"], optional = true } +bitcoinconsensus = { version = "0.106.0", default-features = false, optional = true } + aes-gcm = "0.10" bs58 = "0.5" elliptic-curve = { version = "0.13.8", features = ["hash2curve"] } diff --git a/src/btc.rs b/src/btc.rs index 20ae1a9..1dd9bd6 100644 --- a/src/btc.rs +++ b/src/btc.rs @@ -1,7 +1,6 @@ use bitcoin::{ absolute::LockTime, consensus::Encodable, - key::TapTweak, secp256k1::{self, Secp256k1, Verification, XOnlyPublicKey}, sighash::{Prevouts, SighashCache}, taproot::{LeafVersion, Signature}, @@ -233,6 +232,7 @@ mod test { traits::{Aggregator, Signer}, v2, }; + use bitcoin::key::TapTweak; use bitcoin::{ blockdata::{ diff --git a/src/lib.rs b/src/lib.rs index c6b91ce..1599427 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ #![doc = include_str!("../README.md")] /// test bitcoin/taproot integration using libbitcoin_consensus -#[cfg(test)] +#[cfg(any(test, feature = "testing"))] pub mod btc; /// Types which are common to both v1 and v2 #[allow(clippy::op_ref)] diff --git a/src/state_machine/coordinator/fire.rs b/src/state_machine/coordinator/fire.rs index 28a163e..b9ff95a 100644 --- a/src/state_machine/coordinator/fire.rs +++ b/src/state_machine/coordinator/fire.rs @@ -1592,11 +1592,11 @@ pub mod test { coordinator::{ fire::Coordinator as FireCoordinator, test::{ - bad_signature_share_request, check_signature_shares, coordinator_state_machine, - empty_private_shares, empty_public_shares, equal_after_save_load, - feedback_messages, feedback_mutated_messages, gen_nonces, invalid_nonce, - new_coordinator, run_dkg_sign, setup, setup_with_timeouts, start_dkg_round, - start_signing_round, verify_packet_sigs, + bad_signature_share_request, btc_sign_verify, check_signature_shares, + coordinator_state_machine, empty_private_shares, empty_public_shares, + equal_after_save_load, feedback_messages, feedback_mutated_messages, + gen_nonces, invalid_nonce, new_coordinator, run_dkg_sign, setup, + setup_with_timeouts, start_dkg_round, start_signing_round, verify_packet_sigs, }, Config, Coordinator as CoordinatorTrait, State, }, @@ -3732,4 +3732,15 @@ pub mod test { fn verify_packet_sigs_v2() { verify_packet_sigs::, v2::Signer>(); } + + #[test] + #[cfg(feature = "with_v1")] + fn btc_sign_verify_v1() { + btc_sign_verify::, v1::Signer>(5, 2); + } + + #[test] + fn btc_sign_verify_v2() { + btc_sign_verify::, v2::Signer>(5, 2); + } } diff --git a/src/state_machine/coordinator/frost.rs b/src/state_machine/coordinator/frost.rs index 05cbdf5..ff5e20a 100644 --- a/src/state_machine/coordinator/frost.rs +++ b/src/state_machine/coordinator/frost.rs @@ -1011,10 +1011,10 @@ pub mod test { state_machine::coordinator::{ frost::Coordinator as FrostCoordinator, test::{ - bad_signature_share_request, check_signature_shares, coordinator_state_machine, - empty_private_shares, empty_public_shares, equal_after_save_load, invalid_nonce, - new_coordinator, run_dkg_sign, setup, start_dkg_round, start_signing_round, - verify_packet_sigs, + bad_signature_share_request, btc_sign_verify, check_signature_shares, + coordinator_state_machine, empty_private_shares, empty_public_shares, + equal_after_save_load, invalid_nonce, new_coordinator, run_dkg_sign, setup, + start_dkg_round, start_signing_round, verify_packet_sigs, }, Config, Coordinator as CoordinatorTrait, State, }, @@ -1614,4 +1614,15 @@ pub mod test { fn verify_packet_sigs_v2() { verify_packet_sigs::, v2::Signer>(); } + + #[test] + #[cfg(feature = "with_v1")] + fn btc_sign_verify_v1() { + btc_sign_verify::, v1::Signer>(5, 2); + } + + #[test] + fn btc_sign_verify_v2() { + btc_sign_verify::, v2::Signer>(5, 2); + } } diff --git a/src/state_machine/coordinator/mod.rs b/src/state_machine/coordinator/mod.rs index cf37777..9db0cad 100644 --- a/src/state_machine/coordinator/mod.rs +++ b/src/state_machine/coordinator/mod.rs @@ -1885,4 +1885,111 @@ pub mod test { ); } } + + use crate::btc::UnsignedTx; + + use bitcoin::{ + blockdata::script::Builder, + opcodes::all::*, + secp256k1::{Secp256k1, XOnlyPublicKey}, + taproot::{self, LeafVersion, TaprootBuilder}, + TapSighashType, Witness, + }; + + /// Create a taproot transaction with key and script spends, then sign/verify each spend path + pub fn btc_sign_verify( + num_signers: u32, + keys_per_signer: u32, + ) { + let (mut coordinators, mut signers) = + run_dkg::(num_signers, keys_per_signer); + + let aggregate_public_key = coordinators[0] + .get_aggregate_public_key() + .expect("public key"); + let aggregate_xonly_key = XOnlyPublicKey::from_slice(&aggregate_public_key.x().to_bytes()) + .expect("failed to create XOnlyPublicKey"); + + let secp = Secp256k1::new(); + let script = Builder::new() + .push_x_only_key(&aggregate_xonly_key) + .push_opcode(OP_CHECKSIG) + .into_script(); + let spend_info = TaprootBuilder::new() + .add_leaf(0, script.clone()) + .unwrap() + .finalize(&secp, aggregate_xonly_key) + .expect("failed to finalize taproot_spend_info"); + let merkle_root = spend_info.merkle_root(); + let internal_key = spend_info.internal_key(); + let unsigned = UnsignedTx::new(internal_key); + + // test the key spend + let sighash = unsigned + .compute_sighash(&secp, merkle_root) + .expect("failed to compute taproot sighash"); + let msg: &[u8] = sighash.as_ref(); + + let raw_merkle_root = merkle_root.map(|root| { + let bytes: [u8; 32] = *root.to_raw_hash().as_ref(); + bytes + }); + + let OperationResult::SignTaproot(proof) = run_sign::( + &mut coordinators, + &mut signers, + msg, + SignatureType::Taproot(raw_merkle_root), + ) else { + panic!("taproot signature failed"); + }; + + let schnorr_sig = bitcoin::secp256k1::schnorr::Signature::from_slice(&proof.to_bytes()) + .expect("Failed to parse Signature from slice"); + let taproot_sig = taproot::Signature { + signature: schnorr_sig, + sighash_type: TapSighashType::All, + }; + + unsigned + .verify_signature(&secp, &taproot_sig, merkle_root) + .expect("signature verification failed"); + + // test the script spend + let sighash = unsigned + .compute_script_sighash(&secp, merkle_root, &script) + .expect("failed to compute taproot sighash"); + let msg: &[u8] = sighash.as_ref(); + + let OperationResult::SignSchnorr(proof) = run_sign::( + &mut coordinators, + &mut signers, + msg, + SignatureType::Schnorr, + ) else { + panic!("schnorr signature failed"); + }; + + let schnorr_sig = bitcoin::secp256k1::schnorr::Signature::from_slice(&proof.to_bytes()) + .expect("Failed to parse Signature from slice"); + let taproot_sig = taproot::Signature { + signature: schnorr_sig, + sighash_type: TapSighashType::All, + }; + + let control_block = spend_info + .control_block(&(script.clone(), LeafVersion::TapScript)) + .expect("insert the accept script into the control block"); + + println!("ControlBlock {control_block:?}"); + + let mut witness = Witness::new(); + witness.push(taproot_sig.to_vec()); + witness.push(script.as_bytes()); + witness.push(control_block.serialize()); + + unsigned + .verify_witness(&secp, witness, merkle_root) + .expect("signature verification failed"); + } }