From 4541246f9d069e3fd6210fd10d96f3391dc22ca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Efe=20Ak=C3=A7a?= <13402668+mmtftr@users.noreply.github.com> Date: Mon, 10 Feb 2025 17:25:47 +0300 Subject: [PATCH] [Actor, TxHandler] add signing of txhandler through tagged signatures (#511) * prototype * prototype2 * add winternitz and preimagereveal * refactor with script kind for easy matching * feat(actor): remove txhandler clone by refactoring sighash calc Added a new scriptkind parser for code quality with tests Implemented sighash calculation closure to avoid cloning and ensure immutability of the TxHandler inputs. * test: actor correctly signs * chore(actor): rename winternitz partial sign * fix: tests * Remove TypeId runtime for SpendableScript to get ScriptKind (#512) * change option * fix: errors from merge, add calculate_sighash * fix: add spend info to assert end txhandler * fix: winternitz commit script, add tests * chore: cargo fmt * chore: clippy * chore: rename functions and clippy * chore: clippy --------- Co-authored-by: atacann Co-authored-by: Roman --- core/src/actor.rs | 585 ++++++++++++---- core/src/builder/script.rs | 622 ++++++++++++++++-- core/src/builder/sighash.rs | 65 +- core/src/builder/transaction/challenge.rs | 5 +- core/src/builder/transaction/input.rs | 4 + .../builder/transaction/operator_assert.rs | 24 +- core/src/builder/transaction/txhandler.rs | 146 ++-- core/src/constants.rs | 2 + core/src/errors.rs | 13 + core/src/musig2.rs | 4 +- core/src/operator.rs | 2 +- core/src/rpc/verifier.rs | 2 +- core/src/test_utils.rs | 17 +- core/tests/musig2.rs | 8 +- core/tests/taproot.rs | 35 +- 15 files changed, 1201 insertions(+), 333 deletions(-) diff --git a/core/src/actor.rs b/core/src/actor.rs index dc4ab35f..e0546e16 100644 --- a/core/src/actor.rs +++ b/core/src/actor.rs @@ -1,16 +1,21 @@ +use crate::builder::script::SpendPath; +use crate::builder::transaction::input::SpentTxIn; use crate::builder::transaction::TxHandler; use crate::errors::BridgeError; +use crate::errors::BridgeError::NotOwnKeyPath; use crate::operator::PublicHash; +use crate::rpc::clementine::tagged_signature::SignatureId; +use crate::rpc::clementine::TaggedSignature; use crate::utils::{self, SECP}; use bitcoin::hashes::hash160; use bitcoin::secp256k1::PublicKey; -use bitcoin::sighash::SighashCache; +use bitcoin::taproot::{LeafVersion, TaprootSpendInfo}; use bitcoin::{ hashes::Hash, secp256k1::{schnorr, Keypair, Message, SecretKey, XOnlyPublicKey}, - Address, TapSighash, TapTweakHash, + Address, ScriptBuf, TapSighash, TapTweakHash, }; -use bitcoin::{TapNodeHash, TapSighashType, TxOut, Witness}; +use bitcoin::{TapNodeHash, Witness}; use bitvm::signatures::winternitz::{ self, BinarysearchVerifier, StraightforwardConverter, Winternitz, }; @@ -151,92 +156,6 @@ impl Actor { ) } - #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] - pub fn sign_taproot_script_spend_tx( - &self, - tx: &mut TxHandler, - txin_index: usize, - script_index: usize, - ) -> Result { - let sighash = tx.calculate_script_spend_sighash_indexed( - txin_index, - script_index, - TapSighashType::Default, - )?; - - Ok(self.sign(sighash)) - } - - #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] - pub fn sign_taproot_pubkey_spend( - &self, - tx_handler: &mut TxHandler, - input_index: usize, - sighash_type: Option, - ) -> Result { - let sig_hash = tx_handler.calculate_pubkey_spend_sighash(input_index, sighash_type)?; - - self.sign_with_tweak(sig_hash, None) - } - - #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] - pub fn sign_taproot_pubkey_spend_tx( - &self, - tx: &mut bitcoin::Transaction, - prevouts: &[TxOut], - input_index: usize, - ) -> Result { - let mut sighash_cache = SighashCache::new(tx); - - let sig_hash = sighash_cache.taproot_key_spend_signature_hash( - input_index, - &bitcoin::sighash::Prevouts::All(prevouts), - bitcoin::sighash::TapSighashType::Default, - )?; - - self.sign_with_tweak(sig_hash, None) - } - - #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] - pub fn sign_taproot_pubkey_spend_tx_with_sighash( - &self, - tx: &mut bitcoin::Transaction, - prevouts: &[TxOut], - input_index: usize, - sighash_type: Option, - ) -> Result { - let mut sighash_cache = SighashCache::new(tx); - - let sig_hash = sighash_cache.taproot_key_spend_signature_hash( - input_index, - &match sighash_type { - Some(TapSighashType::SinglePlusAnyoneCanPay) => { - bitcoin::sighash::Prevouts::One(input_index, prevouts[input_index].clone()) - } - _ => bitcoin::sighash::Prevouts::All(prevouts), - }, - sighash_type.unwrap_or(TapSighashType::Default), - )?; - - self.sign_with_tweak(sig_hash, None) - } - - #[tracing::instrument(skip(self), err(level = tracing::Level::ERROR), ret(level = tracing::Level::TRACE))] - pub fn sign_taproot_script_spend_tx_new_tweaked( - &self, - tx_handler: &mut TxHandler, - txin_index: usize, - script_index: usize, - ) -> Result { - let sighash = tx_handler.calculate_script_spend_sighash_indexed( - txin_index, - script_index, - TapSighashType::Default, - )?; - - self.sign_with_tweak(sighash, None) - } - /// Returns derivied Winternitz secret key from given path. fn get_derived_winternitz_sk( &self, @@ -288,6 +207,258 @@ impl Actor { let hash = hash160::Hash::hash(&preimage); Ok(hash.to_byte_array()) } + + fn get_saved_signature( + signature_id: SignatureId, + signatures: &[TaggedSignature], + ) -> Option { + signatures + .iter() + .find(|sig| { + sig.signature_id + .map(|id| id == signature_id) + .unwrap_or(false) + }) + .and_then(|sig| schnorr::Signature::from_slice(sig.signature.as_ref()).ok()) + } + + fn add_script_path_to_witness( + witness: &mut Witness, + script: &ScriptBuf, + spend_info: &TaprootSpendInfo, + ) -> Result<(), BridgeError> { + let spend_control_block = spend_info + .control_block(&(script.clone(), LeafVersion::TapScript)) + .ok_or(BridgeError::ControlBlockError)?; + witness.push(script.clone()); + witness.push(spend_control_block.serialize()); + Ok(()) + } + + pub fn tx_sign_preimage( + &self, + txhandler: &mut TxHandler, + data: impl AsRef<[u8]>, + ) -> Result<(), BridgeError> { + let mut signed_preimage = false; + + let data = data.as_ref(); + let signer = + move |_: usize, + spt: &SpentTxIn, + calc_sighash: Box Result + '_>| + -> Result, BridgeError> { + let spendinfo = spt + .get_spendable() + .get_spend_info() + .as_ref() + .ok_or(BridgeError::MissingSpendInfo)?; + match spt.get_spend_path() { + SpendPath::ScriptSpend(script_idx) => { + let script = spt + .get_spendable() + .get_scripts() + .get(script_idx) + .ok_or(BridgeError::NoScriptAtIndex(script_idx))?; + + use crate::builder::script::ScriptKind as Kind; + + let mut witness = match script.kind() { + Kind::PreimageRevealScript(script) => { + if script.0 != self.xonly_public_key { + return Err(BridgeError::NotOwnedScriptPath); + } + script.generate_script_inputs(data, &self.sign(calc_sighash()?)) + } + Kind::WinternitzCommit(_) + | Kind::CheckSig(_) + | Kind::Other(_) + | Kind::DepositScript(_) + | Kind::TimelockScript(_) + | Kind::WithdrawalScript(_) => return Ok(None), + }; + + if signed_preimage { + return Err(BridgeError::MultiplePreimageRevealScripts); + } + + signed_preimage = true; + + Self::add_script_path_to_witness( + &mut witness, + &script.to_script_buf(), + spendinfo, + )?; + + Ok(Some(witness)) + } + SpendPath::KeySpend => Ok(None), + SpendPath::Unknown => Err(BridgeError::SpendPathNotSpecified), + } + }; + + txhandler.sign_txins(signer)?; + Ok(()) + } + pub fn tx_sign_winternitz( + &self, + txhandler: &mut TxHandler, + data: &Vec, + path: WinternitzDerivationPath, + ) -> Result<(), BridgeError> { + let mut signed_winternitz = false; + + let signer = + move |_: usize, + spt: &SpentTxIn, + calc_sighash: Box Result + '_>| + -> Result, BridgeError> { + let spendinfo = spt + .get_spendable() + .get_spend_info() + .as_ref() + .ok_or(BridgeError::MissingSpendInfo)?; + match spt.get_spend_path() { + SpendPath::ScriptSpend(script_idx) => { + let script = spt + .get_spendable() + .get_scripts() + .get(script_idx) + .ok_or(BridgeError::NoScriptAtIndex(script_idx))?; + + use crate::builder::script::ScriptKind as Kind; + + let mut witness = match script.kind() { + Kind::WinternitzCommit(script) => { + if script.1 != self.xonly_public_key { + return Err(BridgeError::NotOwnedScriptPath); + } + script.generate_script_inputs( + data, + &self.get_derived_winternitz_sk(path)?, + &self.sign(calc_sighash()?), + ) + } + Kind::PreimageRevealScript(_) + | Kind::CheckSig(_) + | Kind::Other(_) + | Kind::DepositScript(_) + | Kind::TimelockScript(_) + | Kind::WithdrawalScript(_) => return Ok(None), + }; + + if signed_winternitz { + return Err(BridgeError::MultipleWinternitzScripts); + } + + signed_winternitz = true; + + Self::add_script_path_to_witness( + &mut witness, + &script.to_script_buf(), + spendinfo, + )?; + + Ok(Some(witness)) + } + SpendPath::KeySpend => Ok(None), + SpendPath::Unknown => Err(BridgeError::SpendPathNotSpecified), + } + }; + + txhandler.sign_txins(signer)?; + Ok(()) + } + + pub fn tx_sign_and_fill_sigs( + &self, + txhandler: &mut TxHandler, + signatures: &[TaggedSignature], + ) -> Result<(), BridgeError> { + let signer = move |_, + spt: &SpentTxIn, + calc_sighash: Box< + dyn for<'a> FnOnce() -> Result + '_, + >| + -> Result, BridgeError> { + let spendinfo = spt + .get_spendable() + .get_spend_info() + .as_ref() + .ok_or_else(|| BridgeError::MissingSpendInfo)?; + + match spt.get_spend_path() { + SpendPath::ScriptSpend(script_idx) => { + let script = spt + .get_spendable() + .get_scripts() + .get(script_idx) + .ok_or_else(|| BridgeError::NoScriptAtIndex(script_idx))?; + let sig = Self::get_saved_signature(spt.get_signature_id(), signatures); + use crate::builder::script::ScriptKind as Kind; + + // Set the script inputs of the witness + let mut witness: Witness = match script.kind() { + Kind::DepositScript(script) => { + match (sig, script.0 == self.xonly_public_key) { + (Some(sig), _) => script.generate_script_inputs(&sig), + (None, true) => { + script.generate_script_inputs(&self.sign(calc_sighash()?)) + } + (None, false) => return Err(BridgeError::SignatureNotFound), + } + } + Kind::TimelockScript(script) => match (sig, script.0) { + (Some(sig), Some(_)) => script.generate_script_inputs(Some(&sig)), + (None, Some(xonly_key)) if xonly_key == self.xonly_public_key => { + script.generate_script_inputs(Some(&self.sign(calc_sighash()?))) + } + (None, Some(_)) => return Err(BridgeError::SignatureNotFound), + (_, None) => Witness::new(), + }, + Kind::CheckSig(script) => match (sig, script.0 == self.xonly_public_key) { + (Some(sig), _) => script.generate_script_inputs(&sig), + (None, true) => { + script.generate_script_inputs(&self.sign(calc_sighash()?)) + } + (None, false) => return Err(BridgeError::SignatureNotFound), + }, + Kind::WinternitzCommit(_) + | Kind::PreimageRevealScript(_) + | Kind::Other(_) + | Kind::WithdrawalScript(_) => return Ok(None), + }; + + // Add P2TR elements (control block and script) to the witness + Self::add_script_path_to_witness( + &mut witness, + &script.to_script_buf(), + spendinfo, + )?; + Ok(Some(witness)) + } + SpendPath::KeySpend => { + let xonly_public_key = spendinfo.internal_key(); + + if xonly_public_key == self.xonly_public_key { + let sighash = calc_sighash()?; + // TODO: get Schnorr sigs, not Vec, pref in HashMap + let sig = Self::get_saved_signature(spt.get_signature_id(), signatures); + let sig = match sig { + Some(sig) => sig, + None => self.sign_with_tweak(sighash, spendinfo.merkle_root())?, + }; + return Ok(Some(Witness::from_slice(&[&sig.serialize()]))); + } + Err(NotOwnKeyPath) + } + SpendPath::Unknown => Err(BridgeError::SpendPathNotSpecified), + } + }; + + txhandler.sign_txins(signer)?; + Ok(()) + } } #[cfg(test)] @@ -295,19 +466,24 @@ mod tests { use super::Actor; use crate::builder::address::create_taproot_address; + use super::*; + use crate::builder::script::{CheckSig, SpendPath, SpendableScript}; use crate::builder::transaction::input::SpendableTxIn; use crate::builder::transaction::output::UnspentTxOut; - use crate::builder::transaction::TxHandlerBuilder; + use crate::builder::transaction::{TxHandler, TxHandlerBuilder}; use crate::config::BridgeConfig; use crate::rpc::clementine::NormalSignatureKind; use crate::utils::{initialize_logger, SECP}; use crate::{ - actor::WinternitzDerivationPath, builder::transaction::TxHandler, - create_test_config_with_thread_name, database::Database, initialize_database, + actor::WinternitzDerivationPath, create_test_config_with_thread_name, database::Database, + initialize_database, }; - use bitcoin::secp256k1::SecretKey; - use bitcoin::Sequence; - use bitcoin::{Amount, Network, OutPoint, TxOut}; + use bitcoin::secp256k1::{schnorr, Message, SecretKey}; + + use bitcoin::sighash::TapSighashType; + use bitcoin::transaction::Transaction; + + use bitcoin::{Amount, Network, OutPoint}; use bitvm::{ execute_script, signatures::winternitz::{ @@ -315,39 +491,194 @@ mod tests { }, treepp::script, }; + use rand::thread_rng; use secp256k1::rand; use std::str::FromStr; + use std::sync::Arc; - /// Returns a valid [`TxHandler`]. - fn create_valid_mock_tx_handler(actor: &Actor) -> TxHandler { - let (op_addr, op_spend) = + // Helper: create a TxHandler with a single key spend input. + fn create_key_spend_tx_handler(actor: &Actor) -> (bitcoin::TxOut, TxHandler) { + let (tap_addr, spend_info) = create_taproot_address(&[], Some(actor.xonly_public_key), Network::Regtest); + // Build a transaction with one input that expects a key spend signature. + let prevtxo = bitcoin::TxOut { + value: Amount::from_sat(1000), + script_pubkey: tap_addr.script_pubkey(), + }; let builder = TxHandlerBuilder::new().add_input( NormalSignatureKind::AlreadyDisproved1, SpendableTxIn::new( OutPoint::default(), - TxOut { - value: Amount::from_sat(1000), - script_pubkey: op_addr.script_pubkey(), - }, + prevtxo.clone(), vec![], - Some(op_spend), + Some(spend_info), ), - crate::builder::script::SpendPath::Unknown, - Sequence::ENABLE_RBF_NO_LOCKTIME, + SpendPath::KeySpend, + bitcoin::Sequence::ENABLE_RBF_NO_LOCKTIME, ); - builder - .add_output(UnspentTxOut::new( - TxOut { - value: Amount::from_sat(999), - script_pubkey: actor.address.script_pubkey(), - }, - vec![], - None, - )) - .finalize() + + ( + prevtxo, + builder + .add_output(UnspentTxOut::new( + bitcoin::TxOut { + value: Amount::from_sat(999), + script_pubkey: actor.address.script_pubkey(), + }, + vec![], + None, + )) + .finalize(), + ) + } + + // Helper: create a dummy CheckSig script for script spend. + fn create_dummy_checksig_script(actor: &Actor) -> CheckSig { + // Use a trivial script that is expected to be spent via a signature. + // In production this would be a proper P2TR script. + CheckSig(actor.xonly_public_key) + } + + // Helper: create a TxHandler with a single script spend input using CheckSig. + fn create_script_spend_tx_handler(actor: &Actor) -> (bitcoin::TxOut, TxHandler) { + // Create a dummy spendable input that carries a script. + // Here we simulate that the spendable has one script: a CheckSig script. + let script = create_dummy_checksig_script(actor); + + let (tap_addr, spend_info) = create_taproot_address( + &[script.to_script_buf()], + Some(actor.xonly_public_key), + Network::Regtest, + ); + + let prevutxo = bitcoin::TxOut { + value: Amount::from_sat(1000), + script_pubkey: tap_addr.script_pubkey(), + }; + let spendable_input = SpendableTxIn::new( + OutPoint::default(), + prevutxo.clone(), + vec![Arc::new(script)], + Some(spend_info), + ); + + let builder = TxHandlerBuilder::new().add_input( + NormalSignatureKind::AlreadyDisproved1, + spendable_input, + SpendPath::ScriptSpend(0), + bitcoin::Sequence::ENABLE_RBF_NO_LOCKTIME, + ); + + ( + prevutxo, + builder + .add_output(UnspentTxOut::new( + bitcoin::TxOut { + value: Amount::from_sat(999), + script_pubkey: actor.address.script_pubkey(), + }, + vec![], + None, + )) + .finalize(), + ) } + #[test] + fn test_actor_key_spend_verification() { + let sk = SecretKey::new(&mut thread_rng()); + let actor = Actor::new(sk, None, Network::Regtest); + let (utxo, mut txhandler) = create_key_spend_tx_handler(&actor); + + // Actor signs the key spend input. + actor + .tx_sign_and_fill_sigs(&mut txhandler, &[]) + .expect("Key spend signature should succeed"); + + // Retrieve the cached transaction from the txhandler. + let tx: &Transaction = txhandler.get_cached_tx(); + + tx.verify(|_| Some(utxo.clone())) + .expect("Expected valid signature for key spend"); + } + + #[test] + fn test_actor_script_spend_tx_valid() { + let sk = SecretKey::new(&mut thread_rng()); + let actor = Actor::new(sk, None, Network::Regtest); + let (prevutxo, mut txhandler) = create_script_spend_tx_handler(&actor); + + // Actor performs a partial sign for script spend. + // Using an empty signature slice since our dummy CheckSig uses actor signature. + let signatures: Vec<_> = vec![]; + actor + .tx_sign_and_fill_sigs(&mut txhandler, &signatures) + .expect("Script spend partial sign should succeed"); + + // Retrieve the cached transaction. + let tx: &Transaction = txhandler.get_cached_tx(); + + tx.verify(|_| Some(prevutxo.clone())) + .expect("Invalid transaction"); + } + + #[test] + fn test_actor_script_spend_sig_valid() { + let sk = SecretKey::new(&mut thread_rng()); + let actor = Actor::new(sk, None, Network::Regtest); + let (_, mut txhandler) = create_script_spend_tx_handler(&actor); + + // Actor performs a partial sign for script spend. + // Using an empty signature slice since our dummy CheckSig uses actor signature. + let signatures: Vec<_> = vec![]; + actor + .tx_sign_and_fill_sigs(&mut txhandler, &signatures) + .expect("Script spend partial sign should succeed"); + + // Retrieve the cached transaction. + let tx: &Transaction = txhandler.get_cached_tx(); + + // For script spend, we extract the witness from the corresponding input. + // Our dummy witness is expected to contain the signature. + let witness = &tx.input[0].witness; + assert!(!witness.is_empty(), "Witness should not be empty"); + let sig = schnorr::Signature::from_slice(&witness[0]) + .expect("Failed to parse Schnorr signature from witness"); + + // Compute the sighash expected for a pubkey spend (similar to key spend). + let sighash = txhandler + .calculate_script_spend_sighash_indexed(0, 0, TapSighashType::Default) + .expect("Sighash computed"); + + let message = Message::from_digest(*sighash.as_byte_array()); + SECP.verify_schnorr(&sig, &message, &actor.xonly_public_key) + .expect("Script spend signature verification failed"); + } + + // #[test] + // fn verify_cached_tx() { + // let sk = SecretKey::new(&mut rand::thread_rng()); + // let network = Network::Regtest; + // let actor = Actor::new(sk, None, network); + + // let mut txhandler = create_valid_mock_tx_handler(&actor); + + // // Sign the transaction + // actor + // .sign_taproot_pubkey_spend(&mut txhandler, 0, None) + // .unwrap(); + + // // Add witness to the transaction + // let sig = actor + // .sign_taproot_pubkey_spend(&mut txhandler, 0, None) + // .unwrap(); + // txhandler.get_cached_tx().input[0].witness = Witness::p2tr_key_spend(&sig); + + // // Verify the cached transaction + // let cached_tx = txhandler.get_cached_tx(); + // cached_tx.verify().expect("Transaction verification failed"); + // } + #[test] fn actor_new() { let sk = SecretKey::new(&mut rand::thread_rng()); @@ -368,14 +699,16 @@ mod tests { // This transaction is matching with prevouts. Therefore signing will // be successful. - let mut tx_handler = create_valid_mock_tx_handler(&actor); - actor - .sign_taproot_pubkey_spend( - &mut tx_handler, - 0, - Some(bitcoin::TapSighashType::SinglePlusAnyoneCanPay), - ) - .unwrap(); + let tx_handler = create_key_spend_tx_handler(&actor).1; + let sighash = tx_handler + .calculate_pubkey_spend_sighash(0, bitcoin::TapSighashType::Default) + .expect("calculating pubkey spend sighash"); + + let signature = actor.sign(sighash); + + let message = Message::from_digest(*sighash.as_byte_array()); + SECP.verify_schnorr(&signature, &message, &actor.xonly_public_key) + .expect("invalid signature"); } #[test] @@ -386,8 +719,10 @@ mod tests { // This transaction is matching with prevouts. Therefore signing will // be successful. - let tx_handler = create_valid_mock_tx_handler(&actor); - let x = tx_handler.calculate_pubkey_spend_sighash(0, None).unwrap(); + let tx_handler = create_key_spend_tx_handler(&actor).1; + let x = tx_handler + .calculate_pubkey_spend_sighash(0, TapSighashType::Default) + .unwrap(); actor.sign_with_tweak(x, None).unwrap(); } diff --git a/core/src/builder/script.rs b/core/src/builder/script.rs index 38404224..1b47a102 100644 --- a/core/src/builder/script.rs +++ b/core/src/builder/script.rs @@ -5,6 +5,7 @@ // Currently generate_witness functions are not yet used. #![allow(dead_code)] +use crate::constants::WINTERNITZ_LOG_D; use crate::{utils, EVMAddress}; use bitcoin::opcodes::OP_TRUE; use bitcoin::script::PushBytesBuf; @@ -15,21 +16,31 @@ use bitcoin::{ ScriptBuf, XOnlyPublicKey, }; use bitcoin::{Amount, Witness}; -use bitvm::signatures::winternitz; +use bitvm::signatures::winternitz::SecretKey; use bitvm::signatures::winternitz::{Parameters, PublicKey}; use std::any::Any; use std::fmt::Debug; -#[derive(Debug, Clone)] +#[derive(Debug, Copy, Clone)] pub enum SpendPath { ScriptSpend(usize), KeySpend, Unknown, } +/// A trait that marks all script types. Each script has a `generate_script_inputs` (eg. [`WinternitzCommit::generate_script_inputs`]) function that +/// generates the witness for the script using various arguments. A `dyn SpendableScript` is cast into a concrete [`ScriptKind`] to +/// generate a witness, the trait object can be used to generate the script_buf. +/// +/// We store [`Arc`]s inside a [`super::transaction::TxHandler`] input, and we cast them into a [`ScriptKind`] when signing. +/// +/// When creating a new Script, make sure you add it to the [`ScriptKind`] enum and add a test for it below. +/// Otherwise, it will not be spendable. pub trait SpendableScript: Send + Sync + 'static + std::any::Any { fn as_any(&self) -> &dyn Any; + fn kind(&self) -> ScriptKind; + fn to_script_buf(&self) -> ScriptBuf; } @@ -54,6 +65,10 @@ impl SpendableScript for OtherSpendable { self } + fn kind(&self) -> ScriptKind { + ScriptKind::Other(self) + } + fn to_script_buf(&self) -> ScriptBuf { self.0.clone() } @@ -64,7 +79,7 @@ impl OtherSpendable { &self.0 } - fn generate_witness(&self, witness: Witness) -> Witness { + fn generate_script_inputs(&self, witness: Witness) -> Witness { witness } @@ -75,12 +90,16 @@ impl OtherSpendable { /// Struct for scripts that only includes a CHECKSIG #[derive(Debug, Clone)] -pub struct CheckSig(XOnlyPublicKey); +pub struct CheckSig(pub(crate) XOnlyPublicKey); impl SpendableScript for CheckSig { fn as_any(&self) -> &dyn Any { self } + fn kind(&self) -> ScriptKind { + ScriptKind::CheckSig(self) + } + fn to_script_buf(&self) -> ScriptBuf { Builder::new() .push_x_only_key(&self.0) @@ -90,7 +109,7 @@ impl SpendableScript for CheckSig { } impl CheckSig { - fn generate_witness(&self, signature: schnorr::Signature) -> Witness { + pub fn generate_script_inputs(&self, signature: &schnorr::Signature) -> Witness { Witness::from_slice(&[signature.serialize()]) } @@ -100,36 +119,57 @@ impl CheckSig { } /// Struct for scripts that commit to a message using Winternitz keys +/// Contains the Winternitz PK, CheckSig PK, message length respectively #[derive(Clone)] -pub struct WinternitzCommit(PublicKey, Parameters, XOnlyPublicKey); +pub struct WinternitzCommit(PublicKey, pub(crate) XOnlyPublicKey, u32); impl SpendableScript for WinternitzCommit { fn as_any(&self) -> &dyn Any { self } + fn kind(&self) -> ScriptKind { + ScriptKind::WinternitzCommit(self) + } + fn to_script_buf(&self) -> ScriptBuf { - let pubkey = self.0.clone(); - let params = self.1.clone(); - let xonly_pubkey = self.2; - let verifier = winternitz::Winternitz::< - winternitz::ListpickVerifier, - winternitz::TabledConverter, - >::new(); - verifier - .checksig_verify(¶ms, &pubkey) - .push_x_only_key(&xonly_pubkey) + let winternitz_pubkey = self.0.clone(); + let params = self.get_params(); + let xonly_pubkey = self.1; + + let mut a = bitvm::signatures::winternitz_hash::WINTERNITZ_MESSAGE_VERIFIER + .checksig_verify(¶ms, &winternitz_pubkey); + + for _ in 0..self.2 { + a = a.push_opcode(OP_DROP) + } + + a.push_x_only_key(&xonly_pubkey) .push_opcode(OP_CHECKSIG) .compile() } } impl WinternitzCommit { - fn generate_witness(&self, commit_data: &[u8], signature: schnorr::Signature) -> Witness { - Witness::from_slice(&[commit_data, &signature.serialize()]) + pub fn get_params(&self) -> Parameters { + Parameters::new(self.2, WINTERNITZ_LOG_D) + } + pub fn generate_script_inputs( + &self, + commit_data: &Vec, + secret_key: &SecretKey, + signature: &schnorr::Signature, + ) -> Witness { + let mut witness = Witness::new(); + witness.push(signature.serialize()); + bitvm::signatures::winternitz_hash::WINTERNITZ_MESSAGE_VERIFIER + .sign(&self.get_params(), secret_key, commit_data) + .into_iter() + .for_each(|x| witness.push(x)); + witness } - pub fn new(pubkey: PublicKey, params: Parameters, xonly_pubkey: XOnlyPublicKey) -> Self { - Self(pubkey, params, xonly_pubkey) + pub fn new(pubkey: PublicKey, xonly_pubkey: XOnlyPublicKey, msg_len: u32) -> Self { + Self(pubkey, xonly_pubkey, msg_len) } } @@ -145,13 +185,17 @@ impl WinternitzCommit { /// - [BIP-0068](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki) /// - [BIP-0112](https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki) #[derive(Debug, Clone)] -pub struct TimelockScript(Option, u16); +pub struct TimelockScript(pub(crate) Option, u16); impl SpendableScript for TimelockScript { fn as_any(&self) -> &dyn Any { self } + fn kind(&self) -> ScriptKind { + ScriptKind::TimelockScript(self) + } + fn to_script_buf(&self) -> ScriptBuf { let script_builder = Builder::new() .push_int(self.1 as i64) @@ -170,8 +214,11 @@ impl SpendableScript for TimelockScript { } impl TimelockScript { - fn generate_witness(&self, signature: schnorr::Signature) -> Witness { - Witness::from_slice(&[signature.serialize()]) + pub fn generate_script_inputs(&self, signature: Option<&schnorr::Signature>) -> Witness { + match signature { + Some(sig) => Witness::from_slice(&[sig.serialize()]), + None => Witness::default(), + } } pub fn new(xonly_pk: Option, block_count: u16) -> Self { @@ -180,13 +227,17 @@ impl TimelockScript { } /// Struct for scripts that reveal a preimage and verify it against a hash. -pub struct PreimageRevealScript(XOnlyPublicKey, [u8; 20]); +pub struct PreimageRevealScript(pub(crate) XOnlyPublicKey, [u8; 20]); impl SpendableScript for PreimageRevealScript { fn as_any(&self) -> &dyn Any { self } + fn kind(&self) -> ScriptKind { + ScriptKind::PreimageRevealScript(self) + } + fn to_script_buf(&self) -> ScriptBuf { Builder::new() .push_opcode(OP_HASH160) @@ -199,8 +250,15 @@ impl SpendableScript for PreimageRevealScript { } impl PreimageRevealScript { - fn generate_witness(&self, preimage: &[u8], signature: schnorr::Signature) -> Witness { - Witness::from_slice(&[preimage, &signature.serialize()]) + pub fn generate_script_inputs( + &self, + preimage: impl AsRef<[u8]>, + signature: &schnorr::Signature, + ) -> Witness { + let mut witness = Witness::new(); + witness.push(signature.serialize()); + witness.push(preimage.as_ref()); + witness } pub fn new(xonly_pk: XOnlyPublicKey, hash: [u8; 20]) -> Self { @@ -209,13 +267,18 @@ impl PreimageRevealScript { } /// Struct for deposit script that commits Citrea address to be deposited into onchain. -pub struct DepositScript(XOnlyPublicKey, EVMAddress, Amount); +#[derive(Debug, Clone)] +pub struct DepositScript(pub(crate) XOnlyPublicKey, EVMAddress, Amount); impl SpendableScript for DepositScript { fn as_any(&self) -> &dyn Any { self } + fn kind(&self) -> ScriptKind { + ScriptKind::DepositScript(self) + } + fn to_script_buf(&self) -> ScriptBuf { let citrea: [u8; 6] = "citrea".as_bytes().try_into().expect("length == 6"); @@ -233,12 +296,12 @@ impl SpendableScript for DepositScript { } impl DepositScript { - fn generate_witness(&self, signature: schnorr::Signature) -> Witness { + pub fn generate_script_inputs(&self, signature: &schnorr::Signature) -> Witness { Witness::from_slice(&[signature.serialize()]) } - pub fn new(xonly_pk: XOnlyPublicKey, evm_address: EVMAddress, amount: Amount) -> Self { - Self(xonly_pk, evm_address, amount) + pub fn new(nofn_xonly_pk: XOnlyPublicKey, evm_address: EVMAddress, amount: Amount) -> Self { + Self(nofn_xonly_pk, evm_address, amount) } } @@ -250,6 +313,10 @@ impl SpendableScript for WithdrawalScript { self } + fn kind(&self) -> ScriptKind { + ScriptKind::WithdrawalScript(self) + } + fn to_script_buf(&self) -> ScriptBuf { let mut push_bytes = PushBytesBuf::new(); push_bytes @@ -269,6 +336,17 @@ impl WithdrawalScript { } } +#[derive(Clone)] +pub enum ScriptKind<'a> { + CheckSig(&'a CheckSig), + WinternitzCommit(&'a WinternitzCommit), + TimelockScript(&'a TimelockScript), + PreimageRevealScript(&'a PreimageRevealScript), + DepositScript(&'a DepositScript), + WithdrawalScript(&'a WithdrawalScript), + Other(&'a OtherSpendable), +} + #[cfg(test)] fn get_script_from_arr( arr: &Vec>, @@ -277,23 +355,471 @@ fn get_script_from_arr( .enumerate() .find_map(|(i, x)| x.as_any().downcast_ref::().map(|x| (i, x))) } +#[cfg(test)] +mod tests { + use crate::actor::{Actor, TxType, WinternitzDerivationPath}; + use crate::extended_rpc::ExtendedRpc; + use crate::{create_test_config_with_thread_name, utils}; + use std::sync::Arc; + + use super::*; + + use bitcoin::hashes::Hash; + use bitcoin::secp256k1::PublicKey; + use bitcoincore_rpc::RpcApi; + use secp256k1::rand; + // Create some dummy values for testing. + // Note: These values are not cryptographically secure and are only used for tests. + fn dummy_xonly() -> XOnlyPublicKey { + // 32 bytes array filled with 0x03. + *utils::UNSPENDABLE_XONLY_PUBKEY + } + + fn dummy_scriptbuf() -> ScriptBuf { + ScriptBuf::from_hex("51").expect("valid hex") + } + + fn dummy_pubkey() -> PublicKey { + *utils::UNSPENDABLE_PUBKEY + } + + fn dummy_params() -> Parameters { + Parameters::new(32, 4) + } + + fn dummy_evm_address() -> EVMAddress { + // For testing purposes, we use a dummy 20-byte array. + EVMAddress([0u8; 20]) + } + + #[test] + fn test_dynamic_casting_extended() { + // Build a collection of SpendableScript implementations. + let scripts: Vec> = vec![ + Box::new(OtherSpendable::new(dummy_scriptbuf())), + Box::new(CheckSig::new(dummy_xonly())), + Box::new(WinternitzCommit::new( + vec![[0u8; 20]; 32], + dummy_xonly(), + 32, + )), + Box::new(TimelockScript::new(Some(dummy_xonly()), 10)), + Box::new(PreimageRevealScript::new(dummy_xonly(), [0; 20])), + Box::new(DepositScript::new( + dummy_xonly(), + dummy_evm_address(), + Amount::from_sat(100), + )), + ]; + + // helper closures that return Option<(usize, &T)> using get_script_from_arr. + let checksig = get_script_from_arr::(&scripts); + let winternitz = get_script_from_arr::(&scripts); + let timelock = get_script_from_arr::(&scripts); + let preimage = get_script_from_arr::(&scripts); + let deposit = get_script_from_arr::(&scripts); + let others = get_script_from_arr::(&scripts); + + assert!(checksig.is_some(), "CheckSig not found"); + assert!(winternitz.is_some(), "WinternitzCommit not found"); + assert!(timelock.is_some(), "TimelockScript not found"); + assert!(preimage.is_some(), "PreimageRevealScript not found"); + assert!(deposit.is_some(), "DepositScript not found"); + assert!(others.is_some(), "OtherSpendable not found"); + + // Print found items. + println!("CheckSig: {:?}", checksig.unwrap().1); + // println!("WinternitzCommit: {:?}", winternitz.unwrap().1); + println!("TimelockScript: {:?}", timelock.unwrap().1); + // println!("PreimageRevealScript: {:?}", preimage.unwrap().1); + // println!("DepositScript: {:?}", deposit.unwrap().1); + println!("OtherSpendable: {:?}", others.unwrap().1); + } + + #[test] + fn test_dynamic_casting() { + use crate::utils; + let scripts: Vec> = vec![ + Box::new(OtherSpendable(ScriptBuf::from_hex("51").expect(""))), + Box::new(CheckSig(*utils::UNSPENDABLE_XONLY_PUBKEY)), + ]; + + let otherspendable = scripts + .first() + .expect("") + .as_any() + .downcast_ref::() + .expect(""); + + let checksig = get_script_from_arr::(&scripts).expect(""); + println!("{:?}", otherspendable); + println!("{:?}", checksig); + } -#[test] -fn test_dynamic_casting() { - use crate::utils; - let scripts: Vec> = vec![ - Box::new(OtherSpendable(ScriptBuf::from_hex("51").expect(""))), - Box::new(CheckSig(*utils::UNSPENDABLE_XONLY_PUBKEY)), - ]; - - let otherspendable = scripts - .first() - .expect("") - .as_any() - .downcast_ref::() - .expect(""); - - let checksig = get_script_from_arr::(&scripts).expect(""); - println!("{:?}", otherspendable); - println!("{:?}", checksig); + #[test] + fn test_scriptkind_completeness() { + let script_variants: Vec<(&str, Arc)> = vec![ + ("CheckSig", Arc::new(CheckSig::new(dummy_xonly()))), + ( + "WinternitzCommit", + Arc::new(WinternitzCommit::new( + vec![[0u8; 20]; 32], + dummy_xonly(), + 32, + )), + ), + ( + "TimelockScript", + Arc::new(TimelockScript::new(Some(dummy_xonly()), 15)), + ), + ( + "PreimageRevealScript", + Arc::new(PreimageRevealScript::new(dummy_xonly(), [1; 20])), + ), + ( + "DepositScript", + Arc::new(DepositScript::new( + dummy_xonly(), + dummy_evm_address(), + Amount::from_sat(50), + )), + ), + ("Other", Arc::new(OtherSpendable::new(dummy_scriptbuf()))), + ]; + + for (expected, script) in script_variants { + let kind = script.kind(); + match (expected, kind) { + ("CheckSig", ScriptKind::CheckSig(_)) => (), + ("WinternitzCommit", ScriptKind::WinternitzCommit(_)) => (), + ("TimelockScript", ScriptKind::TimelockScript(_)) => (), + ("PreimageRevealScript", ScriptKind::PreimageRevealScript(_)) => (), + ("DepositScript", ScriptKind::DepositScript(_)) => (), + ("Other", ScriptKind::Other(_)) => (), + (s, _) => panic!("ScriptKind conversion not comprehensive for variant: {}", s), + } + } + } + // Tests for the spendability of all scripts + use crate::builder; + use crate::builder::transaction::input::SpendableTxIn; + use crate::builder::transaction::output::UnspentTxOut; + use crate::builder::transaction::{TxHandlerBuilder, DEFAULT_SEQUENCE}; + use crate::utils::SECP; + use bitcoin::{Amount, Sequence, TxOut}; + + async fn create_taproot_test_tx( + rpc: &ExtendedRpc, + scripts: Vec>, + spend_path: SpendPath, + amount: Amount, + ) -> (TxHandlerBuilder, bitcoin::Address) { + let (address, taproot_spend_info) = builder::address::create_taproot_address( + &scripts + .iter() + .map(|s| s.to_script_buf()) + .collect::>(), + None, + bitcoin::Network::Regtest, + ); + + let outpoint = rpc.send_to_address(&address, amount).await.unwrap(); + let sequence = if let SpendPath::ScriptSpend(idx) = spend_path { + if let Some(script) = scripts.get(idx) { + match script.kind() { + ScriptKind::TimelockScript(&TimelockScript(_, seq)) => { + Sequence::from_height(seq) + } + _ => DEFAULT_SEQUENCE, + } + } else { + DEFAULT_SEQUENCE + } + } else { + DEFAULT_SEQUENCE + }; + let mut builder = TxHandlerBuilder::new(); + builder = builder.add_input( + crate::rpc::clementine::NormalSignatureKind::NotStored, + SpendableTxIn::new( + outpoint, + TxOut { + value: amount, + script_pubkey: address.script_pubkey(), + }, + scripts.clone(), + Some(taproot_spend_info.clone()), + ), + spend_path, + sequence, + ); + + builder = builder.add_output(UnspentTxOut::new( + TxOut { + value: amount - Amount::from_sat(5000), // Subtract fee + script_pubkey: address.script_pubkey(), + }, + scripts, + Some(taproot_spend_info), + )); + + (builder, address) + } + + use crate::{ + config::BridgeConfig, database::Database, initialize_database, utils::initialize_logger, + }; + + #[tokio::test] + #[serial_test::serial] + async fn test_checksig_spendable() { + let config = create_test_config_with_thread_name!(None); + let rpc = ExtendedRpc::connect( + config.bitcoin_rpc_url.clone(), + config.bitcoin_rpc_user.clone(), + config.bitcoin_rpc_password.clone(), + ) + .await + .unwrap(); + + let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng()); + let xonly_pk = kp.public_key().x_only_public_key().0; + + let scripts: Vec> = vec![Arc::new(CheckSig::new(xonly_pk))]; + let (builder, _) = create_taproot_test_tx( + &rpc, + scripts, + SpendPath::ScriptSpend(0), + Amount::from_sat(10_000), + ) + .await; + let mut tx = builder.finalize(); + + // Should be able to sign with the key + let signer = Actor::new( + kp.secret_key(), + Some(bitcoin::secp256k1::SecretKey::new(&mut rand::thread_rng())), + bitcoin::Network::Regtest, + ); + + signer + .tx_sign_and_fill_sigs(&mut tx, &[]) + .expect("should be able to sign checksig"); + let tx = tx + .promote() + .expect("the transaction should be fully signed"); + + rpc.client + .send_raw_transaction(tx.get_cached_tx()) + .await + .expect("bitcoin RPC did not accept transaction"); + } + + #[tokio::test] + #[serial_test::serial] + async fn test_winternitz_commit_spendable() { + let config = create_test_config_with_thread_name!(None); + let rpc = ExtendedRpc::connect( + config.bitcoin_rpc_url.clone(), + config.bitcoin_rpc_user.clone(), + config.bitcoin_rpc_password.clone(), + ) + .await + .unwrap(); + + let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng()); + let xonly_pk = kp.public_key().x_only_public_key().0; + + let derivation = WinternitzDerivationPath { + message_length: 64, + log_d: 4, + tx_type: TxType::BitVM, + index: Default::default(), + operator_idx: Default::default(), + watchtower_idx: Default::default(), + sequential_collateral_tx_idx: Some(0), + kickoff_idx: Some(0), + intermediate_step_name: None, + }; + let signer = Actor::new( + kp.secret_key(), + Some(kp.secret_key()), + bitcoin::Network::Regtest, + ); + + let script: Arc = Arc::new(WinternitzCommit::new( + signer + .derive_winternitz_pk(derivation) + .expect("failed to derive Winternitz public key"), + xonly_pk, + 64, + )); + + let scripts = vec![script]; + let (builder, _) = create_taproot_test_tx( + &rpc, + scripts, + SpendPath::ScriptSpend(0), + Amount::from_sat(10_000), + ) + .await; + let mut tx = builder.finalize(); + + signer + .tx_sign_winternitz(&mut tx, &vec![0; 32], derivation) + .expect("failed to partially sign commitments"); + + let tx = tx + .promote() + .expect("the transaction should be fully signed"); + + rpc.client + .send_raw_transaction(tx.get_cached_tx()) + .await + .expect("bitcoin RPC did not accept transaction"); + } + + #[tokio::test] + #[serial_test::serial] + async fn test_timelock_script_spendable() { + let config = create_test_config_with_thread_name!(None); + let rpc = ExtendedRpc::connect( + config.bitcoin_rpc_url.clone(), + config.bitcoin_rpc_user.clone(), + config.bitcoin_rpc_password.clone(), + ) + .await + .unwrap(); + + let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng()); + let xonly_pk = kp.public_key().x_only_public_key().0; + + let scripts: Vec> = + vec![Arc::new(TimelockScript::new(Some(xonly_pk), 15))]; + let (builder, _) = create_taproot_test_tx( + &rpc, + scripts, + SpendPath::ScriptSpend(0), + Amount::from_sat(10_000), + ) + .await; + + let mut tx = builder.finalize(); + + let signer = Actor::new( + kp.secret_key(), + Some(bitcoin::secp256k1::SecretKey::new(&mut rand::thread_rng())), + bitcoin::Network::Regtest, + ); + + signer + .tx_sign_and_fill_sigs(&mut tx, &[]) + .expect("should be able to sign timelock"); + + rpc.client + .send_raw_transaction(tx.get_cached_tx()) + .await + .expect_err("should not pass without 15 blocks"); + + rpc.mine_blocks(15).await.expect("failed to mine blocks"); + + rpc.client + .send_raw_transaction(tx.get_cached_tx()) + .await + .expect("should pass after 15 blocks"); + } + + #[tokio::test] + #[serial_test::serial] + async fn test_preimage_reveal_script_spendable() { + let config = create_test_config_with_thread_name!(None); + let rpc = ExtendedRpc::connect( + config.bitcoin_rpc_url.clone(), + config.bitcoin_rpc_user.clone(), + config.bitcoin_rpc_password.clone(), + ) + .await + .unwrap(); + let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng()); + let xonly_pk = kp.public_key().x_only_public_key().0; + + let preimage = [1; 20]; + let hash = bitcoin::hashes::hash160::Hash::hash(&preimage); + let script: Arc = + Arc::new(PreimageRevealScript::new(xonly_pk, hash.to_byte_array())); + let scripts = vec![script]; + let (builder, _) = create_taproot_test_tx( + &rpc, + scripts, + SpendPath::ScriptSpend(0), + Amount::from_sat(10_000), + ) + .await; + let mut tx = builder.finalize(); + + let signer = Actor::new( + kp.secret_key(), + Some(bitcoin::secp256k1::SecretKey::new(&mut rand::thread_rng())), + bitcoin::Network::Regtest, + ); + + signer + .tx_sign_preimage(&mut tx, preimage) + .expect("failed to sign preimage reveal"); + + let final_tx = tx + .promote() + .expect("the transaction should be fully signed"); + + rpc.client + .send_raw_transaction(final_tx.get_cached_tx()) + .await + .expect("bitcoin RPC did not accept transaction"); + } + + #[tokio::test] + #[serial_test::serial] + async fn test_deposit_script_spendable() { + let config = create_test_config_with_thread_name!(None); + let rpc = ExtendedRpc::connect( + config.bitcoin_rpc_url.clone(), + config.bitcoin_rpc_user.clone(), + config.bitcoin_rpc_password.clone(), + ) + .await + .unwrap(); + + let kp = bitcoin::secp256k1::Keypair::new(&SECP, &mut rand::thread_rng()); + let xonly_pk = kp.public_key().x_only_public_key().0; + + let script: Arc = Arc::new(DepositScript::new( + xonly_pk, + EVMAddress([2; 20]), + Amount::from_sat(50), + )); + let scripts = vec![script]; + let (builder, _) = create_taproot_test_tx( + &rpc, + scripts, + SpendPath::ScriptSpend(0), + Amount::from_sat(10_000), + ) + .await; + let mut tx = builder.finalize(); + + let signer = Actor::new( + kp.secret_key(), + Some(bitcoin::secp256k1::SecretKey::new(&mut rand::thread_rng())), + bitcoin::Network::Regtest, + ); + + signer + .tx_sign_and_fill_sigs(&mut tx, &[]) + .expect("should be able to sign deposit"); + + rpc.client + .send_raw_transaction(tx.get_cached_tx()) + .await + .expect("bitcoin RPC did not accept transaction"); + } } diff --git a/core/src/builder/sighash.rs b/core/src/builder/sighash.rs index 7cd1cb79..1819402a 100644 --- a/core/src/builder/sighash.rs +++ b/core/src/builder/sighash.rs @@ -11,7 +11,7 @@ use crate::errors::BridgeError; use crate::rpc::clementine::tagged_signature::SignatureId; use crate::{builder, database::Database, EVMAddress}; use async_stream::try_stream; -use bitcoin::{address::NetworkUnchecked, Address, Amount, OutPoint}; +use bitcoin::{address::NetworkUnchecked, Address, Amount, OutPoint, TapSighashType}; use bitcoin::{TapSighash, Txid, XOnlyPublicKey}; use futures_core::stream::Stream; @@ -119,7 +119,6 @@ pub fn create_nofn_sighash_stream( bridge_amount_sats: Amount, network: bitcoin::Network, ) -> impl Stream> { - use bitcoin::TapSighashType::All as SighashAll; try_stream! { // Create move_tx handler. This is unique for each deposit tx. let move_txhandler = builder::transaction::create_move_to_vault_txhandler( @@ -199,9 +198,9 @@ pub fn create_nofn_sighash_stream( )?; // Yields the sighash for the challenge_tx.input[0], which spends kickoff_tx.input[1] using SinglePlusAnyoneCanPay. - yield (challenge_tx.calculate_pubkey_spend_sighash( + yield (challenge_tx.calculate_sighash( 0, - Some(bitcoin::sighash::TapSighashType::SinglePlusAnyoneCanPay) + bitcoin::sighash::TapSighashType::SinglePlusAnyoneCanPay )?, partial.complete(challenge_tx.get_signature_id(0)?)); // Creates the start_happy_reimburse_tx handler. @@ -211,9 +210,9 @@ pub fn create_nofn_sighash_stream( network )?; // Yields the sighash for the start_happy_reimburse_tx.input[1], which spends kickoff_tx.output[3]. - yield (start_happy_reimburse_txhandler.calculate_pubkey_spend_sighash( + yield (start_happy_reimburse_txhandler.calculate_sighash( 1, - None + TapSighashType::Default )?, partial.complete(start_happy_reimburse_txhandler.get_signature_id(1)?)); // Creates the happy_reimburse_tx handler. @@ -226,9 +225,9 @@ pub fn create_nofn_sighash_stream( )?; // Yields the sighash for the happy_reimburse_tx.input[0], which spends move_to_vault_tx.output[0]. - yield (happy_reimburse_txhandler.calculate_pubkey_spend_sighash( + yield (happy_reimburse_txhandler.calculate_sighash( 0, - None + TapSighashType::Default )?, partial.complete(happy_reimburse_txhandler.get_signature_id(0)?)); // Collect the challenge Winternitz pubkeys for this specific kickoff_utxo. @@ -244,9 +243,9 @@ pub fn create_nofn_sighash_stream( )?; // Yields the sighash for the watchtower_challenge_kickoff_tx.input[0], which spends kickoff_tx.input[0]. - yield (watchtower_challenge_kickoff_txhandler.calculate_pubkey_spend_sighash( + yield (watchtower_challenge_kickoff_txhandler.calculate_sighash( 0, - None, + TapSighashType::Default, )?, partial.complete(watchtower_challenge_kickoff_txhandler.get_signature_id(0)?)); // Creates the kickoff_timeout_tx handler. @@ -256,10 +255,9 @@ pub fn create_nofn_sighash_stream( )?; // Yields the sighash for the kickoff_timeout_tx.input[0], which spends kickoff_tx.output[3]. - yield (kickoff_timeout_txhandler.calculate_script_spend_sighash_indexed( - 0, + yield (kickoff_timeout_txhandler.calculate_sighash( 0, - SighashAll + TapSighashType::Default )?, partial.complete(kickoff_timeout_txhandler.get_signature_id(0)?)); let public_hashes = db.get_operators_challenge_ack_hashes(None, operator_idx as i32, sequential_collateral_tx_idx as i32, kickoff_idx as i32).await?.ok_or(BridgeError::WatchtowerPublicHashesNotFound(operator_idx as i32, sequential_collateral_tx_idx as i32, kickoff_idx as i32))?; @@ -288,16 +286,15 @@ pub fn create_nofn_sighash_stream( )?; // Yields the sighash for the operator_challenge_NACK_tx.input[0], which spends watchtower_challenge_tx.output[0]. - yield (operator_challenge_nack_txhandler.calculate_script_spend_sighash_indexed( + yield (operator_challenge_nack_txhandler.calculate_sighash( 0, - 1, - SighashAll, + TapSighashType::Default, )?, partial.complete(operator_challenge_nack_txhandler.get_signature_id(0)?)); // Yields the sighash for the operator_challenge_NACK_tx.input[1], which spends kickoff_tx.output[2]. - yield (operator_challenge_nack_txhandler.calculate_pubkey_spend_sighash( + yield (operator_challenge_nack_txhandler.calculate_sighash( 1, - None, + TapSighashType::Default )?, partial.complete(operator_challenge_nack_txhandler.get_signature_id(1)?)); } @@ -321,9 +318,9 @@ pub fn create_nofn_sighash_stream( )?; // Yields the sighash for the assert_end_tx, which spends kickoff_tx.output[3]. - yield (assert_end_txhandler.calculate_pubkey_spend_sighash( + yield (assert_end_txhandler.calculate_sighash( PARALLEL_ASSERT_TX_CHAIN_SIZE, - None, + TapSighashType::Default, )?, partial.complete(assert_end_txhandler.get_signature_id(PARALLEL_ASSERT_TX_CHAIN_SIZE)?)); // Creates the disprove_timeout_tx handler. @@ -334,16 +331,15 @@ pub fn create_nofn_sighash_stream( )?; // Yields the sighash for the disprove_timeout_tx.input[0], which spends assert_end_tx.output[0]. - yield (disprove_timeout_txhandler.calculate_pubkey_spend_sighash( + yield (disprove_timeout_txhandler.calculate_sighash( 0, - None, + TapSighashType::Default )?, partial.complete(disprove_timeout_txhandler.get_signature_id(0)?)); // Yields the disprove_timeout_tx.input[1], which spends assert_end_tx.output[1]. - yield (disprove_timeout_txhandler.calculate_script_spend_sighash_indexed( + yield (disprove_timeout_txhandler.calculate_sighash( 1, - 0, - SighashAll, + TapSighashType::Default, )?, partial.complete(disprove_timeout_txhandler.get_signature_id(1)?)); // Creates the already_disproved_tx handler. @@ -353,10 +349,9 @@ pub fn create_nofn_sighash_stream( )?; // Yields the sighash for the already_disproved_tx.input[0], which spends assert_end_tx.output[1]. - yield (already_disproved_txhandler.calculate_script_spend_sighash_indexed( + yield (already_disproved_txhandler.calculate_sighash( 0, - 1, - SighashAll, + TapSighashType::Default, )?, partial.complete(already_disproved_txhandler.get_signature_id(0)?)); // Creates the reimburse_tx handler. @@ -369,7 +364,7 @@ pub fn create_nofn_sighash_stream( )?; // Yields the sighash for the reimburse_tx.input[0], which spends move_to_vault_tx.output[0]. - yield (reimburse_txhandler.calculate_pubkey_spend_sighash(0, None)?, partial.complete(reimburse_txhandler.get_signature_id(0)?)); + yield (reimburse_txhandler.calculate_sighash(0, TapSighashType::Default)?, partial.complete(reimburse_txhandler.get_signature_id(0)?)); } input_txid = *reimburse_generator_txhandler.get_txid(); @@ -465,9 +460,9 @@ pub fn create_operator_sighash_stream( )?; // Yields the sighash for the kickoff_timeout_tx.input[0], which spends kickoff_tx.output[3]. - yield (kickoff_timeout_txhandler.calculate_pubkey_spend_sighash( + yield (kickoff_timeout_txhandler.calculate_sighash( 1, - None, + TapSighashType::Default, )?, partial.complete(kickoff_timeout_txhandler.get_signature_id(1)?)); let (assert_tx_addrs, root_hash, _public_input_wots) = db.get_bitvm_setup(None, operator_idx as i32, sequential_collateral_idx as i32, kickoff_utxo_idx as i32).await?.ok_or(BridgeError::BitvmSetupNotFound(operator_idx as i32, sequential_collateral_idx as i32, kickoff_utxo_idx as i32))?; @@ -496,9 +491,9 @@ pub fn create_operator_sighash_stream( )?; // Yields the sighash for the already_disproved_tx.input[0], which spends assert_end_tx.output[1]. - yield (already_disproved_txhandler.calculate_pubkey_spend_sighash( + yield (already_disproved_txhandler.calculate_sighash( 1, - None, + TapSighashType::Default, )?, partial.complete(already_disproved_txhandler.get_signature_id(1)?)); let disprove_txhandler = builder::transaction::create_disprove_txhandler( @@ -507,9 +502,9 @@ pub fn create_operator_sighash_stream( )?; // Yields the sighash for the disprove_tx.input[1], which spends sequential_collateral_tx.output[0]. - yield (disprove_txhandler.calculate_pubkey_spend_sighash( + yield (disprove_txhandler.calculate_sighash( 1, - None, + TapSighashType::Default, )?, partial.complete(disprove_txhandler.get_signature_id(1)?)); } diff --git a/core/src/builder/transaction/challenge.rs b/core/src/builder/transaction/challenge.rs index b43a3f2a..2ca17fb8 100644 --- a/core/src/builder/transaction/challenge.rs +++ b/core/src/builder/transaction/challenge.rs @@ -7,7 +7,6 @@ use crate::constants::{BLOCKS_PER_WEEK, OPERATOR_CHALLENGE_AMOUNT}; use crate::errors::BridgeError; use crate::rpc::clementine::{NormalSignatureKind, WatchtowerSignatureKind}; use bitcoin::{Amount, ScriptBuf, Sequence, TxOut, XOnlyPublicKey}; -use bitvm::signatures::winternitz; use std::sync::Arc; /// Creates a [`TxHandler`] for the `watchtower_challenge_kickoff_tx`. This transaction can be sent by anyone. @@ -27,13 +26,11 @@ pub fn create_watchtower_challenge_kickoff_txhandler( DEFAULT_SEQUENCE, ); - let wots_params = winternitz::Parameters::new(240, 4); - for i in 0..num_watchtowers { let winternitz_commit = Arc::new(WinternitzCommit::new( watchtower_challenge_winternitz_pks[i as usize].clone(), - wots_params.clone(), watchtower_xonly_pks[i as usize], + 240, )); builder = builder.add_output(UnspentTxOut::from_scripts( Amount::from_sat(2000), // TODO: Hand calculate this diff --git a/core/src/builder/transaction/input.rs b/core/src/builder/transaction/input.rs index 795e614d..49d93155 100644 --- a/core/src/builder/transaction/input.rs +++ b/core/src/builder/transaction/input.rs @@ -193,6 +193,10 @@ impl SpentTxIn { &self.spendable } + pub fn get_spend_path(&self) -> SpendPath { + self.spend_path + } + pub fn get_witness(&self) -> &Option { &self.witness } diff --git a/core/src/builder/transaction/operator_assert.rs b/core/src/builder/transaction/operator_assert.rs index d9489b89..a1150ecd 100644 --- a/core/src/builder/transaction/operator_assert.rs +++ b/core/src/builder/transaction/operator_assert.rs @@ -1,5 +1,5 @@ use crate::builder; -use crate::builder::script::TimelockScript; +use crate::builder::script::{SpendableScript, TimelockScript}; pub use crate::builder::transaction::txhandler::TxHandler; pub use crate::builder::transaction::*; use crate::constants::{BLOCKS_PER_WEEK, MIN_TAPROOT_AMOUNT, PARALLEL_ASSERT_TX_CHAIN_SIZE}; @@ -176,8 +176,10 @@ pub fn create_assert_end_txhandler( ); let disprove_taproot_spend_info = TaprootBuilder::new() - .add_hidden_node(0, TapNodeHash::from_byte_array(*root_hash)) - .expect("empty taptree will accept a node at depth 0") + .add_hidden_node(1, TapNodeHash::from_byte_array(*root_hash)) + .expect("empty taptree will accept a node at depth 1") + .add_leaf(1, CheckSig(nofn_xonly_pk).to_script_buf()) + .expect("taptree with one node at depth 1 will accept a script node") .finalize(&SECP, nofn_xonly_pk) // TODO: we should convert this to script spend but we only have partial access to the taptree .expect("finalize always succeeds for taptree with single node at depth 0"); @@ -196,20 +198,22 @@ pub fn create_assert_end_txhandler( // Add outputs Ok(builder - .add_output(UnspentTxOut::from_partial(TxOut { - value: MIN_TAPROOT_AMOUNT, - script_pubkey: disprove_address.script_pubkey().clone(), - })) + .add_output(UnspentTxOut::new( + TxOut { + value: MIN_TAPROOT_AMOUNT, + script_pubkey: disprove_address.script_pubkey().clone(), + }, + vec![Arc::new(CheckSig::new(nofn_xonly_pk))], + Some(disprove_taproot_spend_info), // not disprove_taproot_spend_info as it will cause check to fail because we do not store all scripts + )) .add_output(UnspentTxOut::from_scripts( MIN_TAPROOT_AMOUNT, vec![nofn_1week, nofn_2week], None, network, )) - .add_output(UnspentTxOut::new( + .add_output(UnspentTxOut::from_partial( builder::transaction::anchor_output(), - vec![], - None, )) .finalize()) } diff --git a/core/src/builder/transaction/txhandler.rs b/core/src/builder/transaction/txhandler.rs index 3cee7923..1ffdcb88 100644 --- a/core/src/builder/transaction/txhandler.rs +++ b/core/src/builder/transaction/txhandler.rs @@ -66,10 +66,62 @@ impl TxHandler { &self.cached_txid } + fn get_sighash_calculator( + &self, + idx: usize, + ) -> impl FnOnce() -> Result + '_ { + move || -> Result { + match self.txins[idx].get_spend_path() { + SpendPath::KeySpend => { + self.calculate_pubkey_spend_sighash(idx, TapSighashType::Default) + } + SpendPath::ScriptSpend(script_idx) => self.calculate_script_spend_sighash_indexed( + idx, + script_idx, + TapSighashType::Default, + ), + SpendPath::Unknown => Err(BridgeError::SpendPathNotSpecified), + } + } + } + + /// Signs all **unsigned** transaction inputs using the provided signer function. + /// + /// This function will skip all transaction inputs that already have a witness. + /// + /// # Parameters + /// * `signer` - A function that returns an optional witness for transaction inputs or returns an error + /// if the signing fails. The function takes the input idx, input object, and a sighash calculator closure. + /// + /// # Returns + /// * `Ok(())` if signing is successful + /// * `Err(BridgeError)` if signing fails + pub fn sign_txins( + &mut self, + mut signer: impl for<'a> FnMut( + usize, + &'a SpentTxIn, + Box Result + 'a>, + ) -> Result, BridgeError>, + ) -> Result<(), BridgeError> { + for idx in 0..self.txins.len() { + let calc_sighash = Box::new(self.get_sighash_calculator(idx)); + if self.txins[idx].get_witness().is_some() { + continue; + } + + if let Some(witness) = signer(idx, &self.txins[idx], calc_sighash)? { + self.cached_tx.input[idx].witness = witness.clone(); + self.txins[idx].set_witness(witness); + } + } + Ok(()) + } + pub fn calculate_pubkey_spend_sighash( &self, txin_index: usize, - sighash_type: Option, + sighash_type: TapSighashType, ) -> Result { let prevouts_vec: Vec<&TxOut> = self .txins @@ -78,20 +130,17 @@ impl TxHandler { .collect(); // TODO: Maybe there is a better way to do this let mut sighash_cache: SighashCache<&bitcoin::Transaction> = SighashCache::new(&self.cached_tx); - let prevouts = &match sighash_type { - Some(TapSighashType::SinglePlusAnyoneCanPay) - | Some(TapSighashType::AllPlusAnyoneCanPay) - | Some(TapSighashType::NonePlusAnyoneCanPay) => { + let prevouts = match sighash_type { + TapSighashType::SinglePlusAnyoneCanPay + | TapSighashType::AllPlusAnyoneCanPay + | TapSighashType::NonePlusAnyoneCanPay => { bitcoin::sighash::Prevouts::One(txin_index, prevouts_vec[txin_index]) } _ => bitcoin::sighash::Prevouts::All(&prevouts_vec), }; - let sig_hash = sighash_cache.taproot_key_spend_signature_hash( - txin_index, - prevouts, - sighash_type.unwrap_or(TapSighashType::Default), - )?; + let sig_hash = + sighash_cache.taproot_key_spend_signature_hash(txin_index, &prevouts, sighash_type)?; Ok(sig_hash) } @@ -113,7 +162,7 @@ impl TxHandler { .to_script_buf(); // TODO: remove copy here - self.calculate_script_spend_sighash(txin_index, &script.clone(), sighash_type) + self.calculate_script_spend_sighash(txin_index, &script, sighash_type) } pub fn calculate_script_spend_sighash( @@ -148,6 +197,25 @@ impl TxHandler { Ok(sig_hash) } + + pub fn calculate_sighash( + &self, + txin_index: usize, + sighash_type: TapSighashType, + ) -> Result { + match self.txins[txin_index].get_spend_path() { + SpendPath::ScriptSpend(idx) => { + self.calculate_script_spend_sighash_indexed(txin_index, idx, sighash_type) + } + SpendPath::KeySpend => self.calculate_pubkey_spend_sighash(txin_index, sighash_type), + SpendPath::Unknown => Err(BridgeError::MissingSpendInfo), + } + } + + #[cfg(test)] + pub fn get_input_txout(&self, input_idx: usize) -> &TxOut { + self.txins[input_idx].get_spendable().get_prevout() + } } impl TxHandler { @@ -216,43 +284,6 @@ impl TxHandler { Ok(()) } - // Candidate refactoring - // pub fn set_p2tr_script_spend_witness_find>( - // &mut self, - // txin_index: usize, - // script_finder: impl Fn(&&Scripts) -> bool, - // script_spender: impl FnOnce(&Scripts) -> Witness, - // ) -> Result<(), BridgeError> { - // let txin = self - // .txins - // .get_mut(txin_index) - // ?; - - // if txin.get_witness().is_some() { - // return Err(BridgeError::WitnessAlreadySet); - // } - - // let script = txin - // .get_witness() - // .get_scripts() - // .iter() - // .find(script_finder) - // .ok_or(BridgeError::TaprootScriptError)?; - - // let spend_control_block = txin - // .get_spendable() - // .get_spend_info() - // .control_block(&((*script).to_script_buf(), LeafVersion::TapScript)) - // .ok_or(BridgeError::ControlBlockError)?; - - // let witness = script_spender(script); - - // txin.set_witness(witness); - - // self.cached_tx.input[txin_index].witness = txin.get_witness().as_ref().unwrap().clone(); - // Ok(()) - // } - pub fn set_p2tr_key_spend_witness( &mut self, signature: &taproot::Signature, @@ -368,23 +399,4 @@ impl TxHandlerBuilder { pub fn finalize_signed(self) -> Result, BridgeError> { self.finalize().promote() } - - // pub fn spend Witness>( - // &mut self, - // txin_index: usize, - // script_index: usize, - // witness_fn: T, - // ) -> Result<(), BridgeError> { - // let spendable = self - // .prev_scripts - // .get(txin_index) - // .ok_or(BridgeError::NoScriptsForTxIn(txin_index))? - // .get(script_index) - // .ok_or(BridgeError::NoScriptAtIndex(script_index))? - // .downcast::() - // .map_err(|_| BridgeError::ScriptTypeMismatch)?; - // let witness = witness_fn(spendable); - // self.tx.input_mut(txin_index).witness = witness; - // Ok(()) - // } } diff --git a/core/src/constants.rs b/core/src/constants.rs index 63371aca..99677666 100644 --- a/core/src/constants.rs +++ b/core/src/constants.rs @@ -20,3 +20,5 @@ pub const ANCHOR_AMOUNT: Amount = Amount::from_sat(240); // TODO: This will chan pub const OPERATOR_CHALLENGE_AMOUNT: Amount = Amount::from_sat(200_000_000); pub const BLOCKS_PER_WEEK: u16 = 6 * 24 * 7; + +pub const WINTERNITZ_LOG_D: u32 = 4; diff --git a/core/src/errors.rs b/core/src/errors.rs index 6bd01eda..17a9c335 100644 --- a/core/src/errors.rs +++ b/core/src/errors.rs @@ -228,6 +228,14 @@ pub enum BridgeError { NoScriptsForTxIn(usize), #[error("No script in TxHandler for the index {0}")] NoScriptAtIndex(usize), + #[error("Spend Path in SpentTxIn in TxHandler not specified")] + SpendPathNotSpecified, + #[error("Actor does not own the key needed in P2TR keypath")] + NotOwnKeyPath, + #[error("public key of Checksig in script is not owned by Actor")] + NotOwnedScriptPath, + #[error("Couldn't find needed signature from database")] + SignatureNotFound, #[error("BitvmSetupNotFound for operator {0}, sequential_collateral_tx {1}, kickoff {2}")] BitvmSetupNotFound(i32, i32, i32), @@ -250,6 +258,11 @@ pub enum BridgeError { #[error("Not enough operators")] NotEnoughOperators, + + #[error("Encountered multiple winternitz scripts when attempting to commit to only one.")] + MultipleWinternitzScripts, + #[error("Encountered multiple preimage reveal scripts when attempting to commit to only one.")] + MultiplePreimageRevealScripts, } impl From for ErrorObject<'static> { diff --git a/core/src/musig2.rs b/core/src/musig2.rs index a09fa892..a221a9be 100644 --- a/core/src/musig2.rs +++ b/core/src/musig2.rs @@ -244,7 +244,7 @@ mod tests { key::Keypair, script, secp256k1::{schnorr, Message, PublicKey}, - Amount, OutPoint, TapNodeHash, TxOut, Txid, XOnlyPublicKey, + Amount, OutPoint, TapNodeHash, TapSighashType, TxOut, Txid, XOnlyPublicKey, }; use secp256k1::{musig::MusigPartialSignature, rand::Rng}; use std::sync::Arc; @@ -547,7 +547,7 @@ mod tests { let message = Message::from_digest( tx_details - .calculate_pubkey_spend_sighash(0, None) + .calculate_pubkey_spend_sighash(0, TapSighashType::Default) .unwrap() .to_byte_array(), ); diff --git a/core/src/operator.rs b/core/src/operator.rs index 57796d85..c6077e88 100644 --- a/core/src/operator.rs +++ b/core/src/operator.rs @@ -440,7 +440,7 @@ impl Operator { let sighash = payout_txhandler.calculate_pubkey_spend_sighash( 0, - Some(bitcoin::sighash::TapSighashType::SinglePlusAnyoneCanPay), + bitcoin::sighash::TapSighashType::SinglePlusAnyoneCanPay, )?; SECP.verify_schnorr( diff --git a/core/src/rpc/verifier.rs b/core/src/rpc/verifier.rs index 1fed0a1a..bc8de114 100644 --- a/core/src/rpc/verifier.rs +++ b/core/src/rpc/verifier.rs @@ -470,7 +470,7 @@ impl ClementineVerifier for Verifier { })?; nonce_idx += 1; - tracing::info!( + tracing::debug!( "Verifier {} signed sighash {} of {}", verifier.idx, nonce_idx, diff --git a/core/src/test_utils.rs b/core/src/test_utils.rs index ef9052b7..ae1a7e8e 100644 --- a/core/src/test_utils.rs +++ b/core/src/test_utils.rs @@ -457,7 +457,7 @@ macro_rules! generate_withdrawal_transaction_and_signature { value: $withdrawal_amount, script_pubkey: $withdrawal_address.script_pubkey(), }; - let txout = builder::transaction::output::UnspentTxOut::new(txout.clone(), vec![], None); + let txout = builder::transaction::output::UnspentTxOut::from_partial(txout.clone()); let tx = builder::transaction::TxHandlerBuilder::new() .add_input( @@ -468,18 +468,13 @@ macro_rules! generate_withdrawal_transaction_and_signature { ) .add_output(txout.clone()) .finalize(); - let mut tx = tx.get_cached_tx().clone(); - let prevouts = vec![dust_utxo.txout.clone()]; - - let sig = signer - .sign_taproot_pubkey_spend_tx_with_sighash( - &mut tx, - &prevouts, - 0, - Some(bitcoin::TapSighashType::SinglePlusAnyoneCanPay), - ) + + let sighash = tx + .calculate_sighash(0, bitcoin::sighash::TapSighashType::SinglePlusAnyoneCanPay) .unwrap(); + let sig = signer.sign_with_tweak(sighash, None).unwrap(); + (dust_utxo, txout, sig) }}; } diff --git a/core/tests/musig2.rs b/core/tests/musig2.rs index 46ea70b4..4a48af3c 100644 --- a/core/tests/musig2.rs +++ b/core/tests/musig2.rs @@ -1,6 +1,6 @@ use bitcoin::key::Keypair; use bitcoin::secp256k1::{Message, PublicKey}; -use bitcoin::{hashes::Hash, script, Amount}; +use bitcoin::{hashes::Hash, script, Amount, TapSighashType}; use bitcoin::{taproot, Sequence, TxOut, XOnlyPublicKey}; use bitcoincore_rpc::RpcApi; use clementine_core::builder::script::{CheckSig, OtherSpendable, SpendPath, SpendableScript}; @@ -116,7 +116,7 @@ async fn key_spend() { let message = Message::from_digest( tx_details - .calculate_pubkey_spend_sighash(0, None) + .calculate_pubkey_spend_sighash(0, TapSighashType::Default) .unwrap() .to_byte_array(), ); @@ -226,7 +226,7 @@ async fn key_spend_with_script() { let mut tx_details = builder.finalize(); let message = Message::from_digest( tx_details - .calculate_pubkey_spend_sighash(0, None) + .calculate_pubkey_spend_sighash(0, TapSighashType::Default) .unwrap() .to_byte_array(), ); @@ -511,7 +511,7 @@ async fn key_and_script_spend() { ); let sighash_2 = Message::from_digest( test_txhandler_2 - .calculate_pubkey_spend_sighash(0, None) + .calculate_pubkey_spend_sighash(0, TapSighashType::Default) .unwrap() .to_byte_array(), ); diff --git a/core/tests/taproot.rs b/core/tests/taproot.rs index 871f6847..39a673dd 100644 --- a/core/tests/taproot.rs +++ b/core/tests/taproot.rs @@ -1,6 +1,4 @@ -use bitcoin::hashes::{Hash, HashEngine}; -use bitcoin::secp256k1::Scalar; -use bitcoin::{Address, Amount, TapTweakHash, TxOut}; +use bitcoin::{Amount, TxOut}; use bitcoincore_rpc::RpcApi; use clementine_core::actor::Actor; use clementine_core::builder::script::{CheckSig, SpendPath, SpendableScript}; @@ -29,23 +27,11 @@ async fn create_address_and_transaction_then_sign_transaction() { .unwrap(); 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(); - - // Calculate tweaked public key. - let mut hasher = TapTweakHash::engine(); - hasher.input(&xonly_pk.serialize()); - xonly_pk - .add_tweak( - &SECP, - &Scalar::from_be_bytes(TapTweakHash::from_engine(hasher).to_byte_array()).unwrap(), - ) - .unwrap(); // Prepare script and address. let script = Arc::new(CheckSig::new( - bitcoin::XOnlyPublicKey::from_slice(&tweaked_pk_script).unwrap(), + // bitcoin::XOnlyPublicKey::from_slice(&tweaked_pk_script).unwrap(), + xonly_pk, )); let scripts: Vec> = vec![script.clone()]; let (taproot_address, taproot_spend_info) = builder::address::create_taproot_address( @@ -65,7 +51,7 @@ async fn create_address_and_transaction_then_sign_transaction() { let mut builder = TxHandlerBuilder::new(); builder = builder.add_input( - NormalSignatureKind::NormalSignatureUnknown, + NormalSignatureKind::NotStored, SpendableTxIn::new( utxo, TxOut { @@ -75,7 +61,7 @@ async fn create_address_and_transaction_then_sign_transaction() { scripts.clone(), Some(taproot_spend_info.clone()), ), - SpendPath::Unknown, + SpendPath::ScriptSpend(0), DEFAULT_SEQUENCE, ); @@ -96,12 +82,11 @@ async fn create_address_and_transaction_then_sign_transaction() { config.winternitz_secret_key, config.network, ); - let sig = signer - .sign_taproot_script_spend_tx_new_tweaked(&mut tx_handler, 0, 0) - .unwrap(); - tx_handler - .set_p2tr_script_spend_witness(&[sig.as_ref()], 0, 0) - .unwrap(); + + signer + .tx_sign_and_fill_sigs(&mut tx_handler, &[]) + .expect("failed to sign transaction"); + rpc.mine_blocks(1).await.unwrap(); // New transaction should be OK to send.