diff --git a/taptrade-cli-demo/coordinator/src/communication/mod.rs b/taptrade-cli-demo/coordinator/src/communication/mod.rs index cd268a2..0053cc2 100755 --- a/taptrade-cli-demo/coordinator/src/communication/mod.rs +++ b/taptrade-cli-demo/coordinator/src/communication/mod.rs @@ -265,7 +265,12 @@ async fn submit_payout_signature( Json(payload): Json, ) -> Result { match handle_payout_signature(&payload, coordinator).await { - Ok(_) => Ok(StatusCode::OK.into_response()), + // received both sigs, published final tx + Ok(true) => Ok(StatusCode::OK.into_response()), + + // this was the first signature + Ok(false) => Ok(StatusCode::ACCEPTED.into_response()), + // Err(RequestError::NotConfirmed) => { // info!("Offer tx for final payout not confirmed"); // Ok(StatusCode::NOT_ACCEPTABLE.into_response()) diff --git a/taptrade-cli-demo/coordinator/src/coordinator/coordinator_utils.rs b/taptrade-cli-demo/coordinator/src/coordinator/coordinator_utils.rs index f34eb20..d2faa6f 100644 --- a/taptrade-cli-demo/coordinator/src/coordinator/coordinator_utils.rs +++ b/taptrade-cli-demo/coordinator/src/coordinator/coordinator_utils.rs @@ -1,13 +1,17 @@ -use std::str::FromStr; - +use super::*; use anyhow::Context; use bdk::{ - bitcoin::{key::XOnlyPublicKey, Address}, + bitcoin::{ + hashes::Hash, + key::XOnlyPublicKey, + psbt::{PartiallySignedTransaction, Prevouts}, + sighash::{SighashCache, TapSighashType}, + Address, + }, miniscript::Descriptor, }; -use musig2::BinaryEncoding; - -use super::*; +use musig2::{BinaryEncoding, LiftedSignature}; +use std::str::FromStr; #[derive(Debug)] pub enum PayoutProcessingResult { @@ -28,6 +32,92 @@ pub struct PayoutData { pub aggregated_musig_pubkey_ctx_hex: String, } +pub struct KeyspendContext { + pub agg_sig: LiftedSignature, + pub agg_nonce: MusigAggNonce, + pub agg_keyspend_pk: KeyAggContext, + pub keyspend_psbt: PartiallySignedTransaction, +} + +pub fn agg_hex_musig_nonces(maker_nonce: &str, taker_nonce: &str) -> Result { + let musig_pub_nonce_maker = match MusigPubNonce::from_hex(maker_nonce) { + Ok(musig_pub_nonce_maker) => musig_pub_nonce_maker, + Err(e) => { + return Err(anyhow!( + "Error decoding maker musig pub nonce: {}", + e.to_string() + )) + } + }; + let musig_pub_nonce_taker = match MusigPubNonce::from_hex(taker_nonce) { + Ok(musig_pub_nonce_taker) => musig_pub_nonce_taker, + Err(e) => { + return Err(anyhow!( + "Error decoding taker musig pub nonce: {}", + e.to_string() + )) + } + }; + + let agg_nonce = musig2::AggNonce::sum([musig_pub_nonce_maker, musig_pub_nonce_taker]); + + Ok(agg_nonce) +} + +impl KeyspendContext { + pub fn from_hex_str( + maker_sig: &str, + taker_sig: &str, + maker_nonce: &str, + taker_nonce: &str, + maker_pk: &str, + taker_pk: &str, + keyspend_psbt: &str, + ) -> anyhow::Result { + let agg_keyspend_pk: musig2::KeyAggContext = + wallet::aggregate_musig_pubkeys(maker_pk, taker_pk)?; + let agg_nonce: MusigAggNonce = + coordinator_utils::agg_hex_musig_nonces(&maker_nonce, &taker_nonce)?; + let keyspend_psbt = PartiallySignedTransaction::from_str(keyspend_psbt)?; + + let partial_maker_sig = PartialSignature::from_hex(maker_sig)?; + let partial_taker_sig = PartialSignature::from_hex(taker_sig)?; + let partial_signatures = vec![partial_maker_sig, partial_taker_sig]; + + // let msg = keyspend_psbt. + let msg = { + let mut sig_hash_cache = SighashCache::new(keyspend_psbt.unsigned_tx.clone()); + + let utxo = keyspend_psbt + .iter_funding_utxos() + .next() + .ok_or(anyhow!("No UTXO found in payout psbt"))?? + .clone(); + + // get the msg (sighash) to sign with the musig key + let binding = sig_hash_cache + .taproot_key_spend_signature_hash(0, &Prevouts::All(&[utxo]), TapSighashType::All) + .context("Failed to create keyspend sighash")?; + binding.as_byte_array().to_vec() + }; + + let agg_sig: LiftedSignature = musig2::aggregate_partial_signatures( + &agg_keyspend_pk, + &agg_nonce, + partial_signatures, + msg.as_slice(), + ) + .context("Aggregating partial signatures failed")?; + + Ok(Self { + agg_sig, + agg_nonce, + agg_keyspend_pk, + keyspend_psbt, + }) + } +} + impl PayoutData { pub fn new_from_strings( escrow_output_descriptor: &str, @@ -40,31 +130,12 @@ impl PayoutData { musig_pk_hex_maker: &str, musig_pk_hex_taker: &str, ) -> Result { - let musig_pub_nonce_maker = match MusigPubNonce::from_hex(musig_pub_nonce_hex_maker) { - Ok(musig_pub_nonce_maker) => musig_pub_nonce_maker, - Err(e) => { - return Err(anyhow!( - "Error decoding maker musig pub nonce: {}", - e.to_string() - )) - } - }; - let musig_pub_nonce_taker = match MusigPubNonce::from_hex(musig_pub_nonce_hex_taker) { - Ok(musig_pub_nonce_taker) => musig_pub_nonce_taker, - Err(e) => { - return Err(anyhow!( - "Error decoding taker musig pub nonce: {}", - e.to_string() - )) - } - }; - let aggregated_musig_pubkey_ctx_hex = hex::encode( aggregate_musig_pubkeys(musig_pk_hex_maker, musig_pk_hex_taker)?.to_bytes(), ); let agg_musig_nonce: MusigAggNonce = - musig2::AggNonce::sum([musig_pub_nonce_maker, musig_pub_nonce_taker]); + agg_hex_musig_nonces(musig_pub_nonce_hex_maker, musig_pub_nonce_hex_taker)?; Ok(Self { escrow_output_descriptor: Descriptor::from_str(escrow_output_descriptor)?, diff --git a/taptrade-cli-demo/coordinator/src/coordinator/mod.rs b/taptrade-cli-demo/coordinator/src/coordinator/mod.rs index 6b7e654..00a343f 100755 --- a/taptrade-cli-demo/coordinator/src/coordinator/mod.rs +++ b/taptrade-cli-demo/coordinator/src/coordinator/mod.rs @@ -6,6 +6,7 @@ pub mod tx_confirmation_monitoring; use self::coordinator_utils::*; use super::*; +use musig2::{AggNonce, KeyAggContext, PartialSignature}; pub async fn process_order( coordinator: Arc, @@ -326,39 +327,38 @@ pub async fn handle_final_payout( pub async fn handle_payout_signature( payload: &PayoutSignatureRequest, coordinator: Arc, -) -> Result<(), RequestError> { +) -> Result { let database = &coordinator.coordinator_db; let wallet = &coordinator.coordinator_wallet; check_offer_and_confirmation(&payload.offer_id_hex, &payload.robohash_hex, database).await?; - let (maker_partial_sig_hex, taker_partial_sig_hex, payout_psbt_hex) = match database - .insert_partial_sig_and_fetch_if_both( + database + .insert_partial_sig( &payload.partial_sig_hex, &payload.offer_id_hex, &payload.robohash_hex, ) .await + .map_err(|e| RequestError::Database(e.to_string()))?; + + let keyspend_information = if let Some(keyspend_context) = database + .fetch_keyspend_payout_information(&payload.offer_id_hex) + .await + .map_err(|e| RequestError::Database(e.to_string()))? { - Ok(Some((maker_partial_sig, taker_partial_sig, payout_transaction_psbt_hex))) => ( - maker_partial_sig, - taker_partial_sig, - bdk::bitcoin::psbt::PartiallySignedTransaction::deserialize( - &hex::decode(payout_transaction_psbt_hex) - .map_err(|e| RequestError::CoordinatorError(e.to_string()))?, - ), - ), - Ok(None) => return Ok(()), - Err(e) => return Err(RequestError::Database(e.to_string())), + keyspend_context + } else { + return Ok(false); }; + // let aggregated_signature = wallet::payout_tx::aggregate_partial_signatures( + // &maker_partial_sig_hex, + // &taker_partial_sig_hex, + // ) + // .map_err(|e| RequestError::CoordinatorError(e.to_string()))?; + warn!("Use musig2 validate partial sig to validate sigs before using to blame users providing wrong sigs"); - let aggregated_signature = wallet::payout_tx::aggregate_partial_signatures( - &maker_partial_sig_hex, - &taker_partial_sig_hex, - ) - .map_err(|e| RequestError::CoordinatorError(e.to_string()))?; - - Ok(()) + Ok(true) } diff --git a/taptrade-cli-demo/coordinator/src/database/mod.rs b/taptrade-cli-demo/coordinator/src/database/mod.rs index e81dae2..1d625d6 100644 --- a/taptrade-cli-demo/coordinator/src/database/mod.rs +++ b/taptrade-cli-demo/coordinator/src/database/mod.rs @@ -2,9 +2,10 @@ mod db_tests; use anyhow::Context; +use coordinator::coordinator_utils::*; use futures_util::StreamExt; -use musig2::BinaryEncoding; -use serde::de::IntoDeserializer; +use musig2::PartialSignature; +use sha2::digest::typenum::marker_traits; use super::*; use bdk::bitcoin::address::Address; @@ -907,12 +908,12 @@ impl CoordinatorDB { Ok(()) } - pub async fn insert_partial_sig_and_fetch_if_both( + pub async fn insert_partial_sig( &self, partial_sig_hex: &str, offer_id_hex: &str, robohash_hex: &str, - ) -> Result> { + ) -> Result<()> { // first check if the escrow psbt has already been submitted let is_maker = self .is_maker_in_taken_offers(offer_id_hex, robohash_hex) @@ -957,16 +958,40 @@ impl CoordinatorDB { .execute(&*self.db_pool) .await?; } + Ok(()) + } + pub async fn fetch_keyspend_payout_information( + &self, + offer_id_hex: &str, + ) -> Result> { let row = sqlx::query( - "SELECT musig_partial_sig_maker, musig_partial_sig_taker, payout_transaction_psbt_hex FROM taken_offers WHERE offer_id = ?", + "SELECT musig_partial_sig_maker, musig_partial_sig_taker, + musig_pubkey_compressed_hex_maker, musig_pubkey_compressed_hex_taker, musig_pub_nonce_hex_maker, musig_pub_nonce_hex_taker, + payout_transaction_psbt_hex FROM taken_offers WHERE offer_id = ?", ).bind(offer_id_hex).fetch_one(&*self.db_pool).await?; let maker_sig: Option = row.try_get("musig_partial_sig_maker")?; let taker_sig: Option = row.try_get("musig_partial_sig_taker")?; - let payout_tx_hex: String = row.try_get("payout_transaction_psbt_hex")?; + + let maker_pubkey: String = row.try_get("musig_pubkey_compressed_hex_maker")?; + let taker_pubkey: String = row.try_get("musig_pubkey_compressed_hex_taker")?; + + let maker_nonce: String = row.try_get("musig_pub_nonce_hex_maker")?; + let taker_nonce: String = row.try_get("musig_pub_nonce_hex_taker")?; + + let keyspend_psbt: String = row.try_get("payout_transaction_psbt_hex")?; + if let (Some(maker), Some(taker)) = (maker_sig, taker_sig) { - Ok(Some((maker, taker, payout_tx_hex))) + Ok(Some(KeyspendContext::from_hex_str( + &maker, + &taker, + &maker_nonce, + &taker_nonce, + &maker_pubkey, + &taker_pubkey, + &keyspend_psbt, + )?)) } else { Ok(None) } diff --git a/taptrade-cli-demo/coordinator/src/wallet/payout_tx.rs b/taptrade-cli-demo/coordinator/src/wallet/payout_tx.rs index 7a1804a..563bdc1 100644 --- a/taptrade-cli-demo/coordinator/src/wallet/payout_tx.rs +++ b/taptrade-cli-demo/coordinator/src/wallet/payout_tx.rs @@ -1,18 +1,9 @@ /// construction of the transaction spending the escrow output after a successfull trade as keyspend transaction use super::*; use bdk::bitcoin::psbt::Input; -use bdk::bitcoin::psbt::PartiallySignedTransaction; + use bdk::bitcoin::OutPoint; use bdk::miniscript::Descriptor; -use musig2::{AggNonce, KeyAggContext, PartialSignature}; - -pub struct KeyspendContext { - pub partial_maker_sig: PartialSignature, - pub partial_taker_sig: PartialSignature, - pub agg_nonce: AggNonce, - pub key_agg_context: KeyAggContext, - pub keyspend_psbt: PartiallySignedTransaction, -} fn get_tx_fees_abs_sat(blockchain_backend: &RpcBlockchain) -> Result<(u64, u64)> { let feerate = blockchain_backend.estimate_fee(6)?;