From fda9af518df80f2796d19f30d6d25a9d8bcf9212 Mon Sep 17 00:00:00 2001 From: fbock Date: Wed, 14 Aug 2024 17:29:42 +0200 Subject: [PATCH] work on musig signing --- .../coordinator/src/communication/api.rs | 1 + .../src/coordinator/coordinator_utils.rs | 9 +++++ .../coordinator/src/coordinator/mod.rs | 1 + .../coordinator/src/database/mod.rs | 8 +++- .../coordinator/src/wallet/escrow_psbt.rs | 15 ++++---- .../trader/src/communication/api.rs | 1 + .../trader/src/communication/mod.rs | 8 ++-- taptrade-cli-demo/trader/src/trading/mod.rs | 5 ++- taptrade-cli-demo/trader/src/wallet/mod.rs | 38 +++++++++++++++---- 9 files changed, 65 insertions(+), 21 deletions(-) diff --git a/taptrade-cli-demo/coordinator/src/communication/api.rs b/taptrade-cli-demo/coordinator/src/communication/api.rs index 2df76ad..715e046 100644 --- a/taptrade-cli-demo/coordinator/src/communication/api.rs +++ b/taptrade-cli-demo/coordinator/src/communication/api.rs @@ -95,6 +95,7 @@ pub struct PsbtSubmissionRequest { pub struct PayoutResponse { pub payout_psbt_hex: String, pub agg_musig_nonce_hex: String, + pub agg_musig_pubkey_ctx_hex: String, } #[derive(Debug, Serialize, Deserialize)] diff --git a/taptrade-cli-demo/coordinator/src/coordinator/coordinator_utils.rs b/taptrade-cli-demo/coordinator/src/coordinator/coordinator_utils.rs index b178e19..f34eb20 100644 --- a/taptrade-cli-demo/coordinator/src/coordinator/coordinator_utils.rs +++ b/taptrade-cli-demo/coordinator/src/coordinator/coordinator_utils.rs @@ -5,6 +5,7 @@ use bdk::{ bitcoin::{key::XOnlyPublicKey, Address}, miniscript::Descriptor, }; +use musig2::BinaryEncoding; use super::*; @@ -24,6 +25,7 @@ pub struct PayoutData { pub payout_amount_maker: u64, pub payout_amount_taker: u64, pub agg_musig_nonce: MusigAggNonce, + pub aggregated_musig_pubkey_ctx_hex: String, } impl PayoutData { @@ -35,6 +37,8 @@ impl PayoutData { payout_amount_taker: u64, musig_pub_nonce_hex_maker: &str, musig_pub_nonce_hex_taker: &str, + 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, @@ -55,6 +59,10 @@ impl PayoutData { } }; + 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]); @@ -69,6 +77,7 @@ impl PayoutData { payout_amount_maker, payout_amount_taker, agg_musig_nonce, + aggregated_musig_pubkey_ctx_hex, }) } } diff --git a/taptrade-cli-demo/coordinator/src/coordinator/mod.rs b/taptrade-cli-demo/coordinator/src/coordinator/mod.rs index 852034e..916c60a 100755 --- a/taptrade-cli-demo/coordinator/src/coordinator/mod.rs +++ b/taptrade-cli-demo/coordinator/src/coordinator/mod.rs @@ -333,6 +333,7 @@ pub async fn handle_final_payout( return Ok(PayoutProcessingResult::ReadyPSBT(PayoutResponse { payout_psbt_hex: payout_keyspend_psbt_hex, agg_musig_nonce_hex: escrow_payout_data.agg_musig_nonce.to_string(), + agg_musig_pubkey_ctx_hex: escrow_payout_data.aggregated_musig_pubkey_ctx_hex, })); } else if (trader_happiness.maker_happy.is_none() || trader_happiness.taker_happy.is_none()) && !trader_happiness.escrow_ongoing diff --git a/taptrade-cli-demo/coordinator/src/database/mod.rs b/taptrade-cli-demo/coordinator/src/database/mod.rs index 24157c8..908e556 100644 --- a/taptrade-cli-demo/coordinator/src/database/mod.rs +++ b/taptrade-cli-demo/coordinator/src/database/mod.rs @@ -3,6 +3,7 @@ mod db_tests; use anyhow::Context; use futures_util::StreamExt; +use musig2::BinaryEncoding; use serde::de::IntoDeserializer; use super::*; @@ -858,7 +859,8 @@ impl CoordinatorDB { let row = sqlx::query( "SELECT escrow_output_descriptor, payout_address_maker, payout_address_taker, musig_pub_nonce_hex_maker, musig_pub_nonce_hex_taker, - escrow_amount_maker_sat, escrow_amount_taker_sat + escrow_amount_maker_sat, escrow_amount_taker_sat, musig_pubkey_compressed_hex_maker, + musig_pubkey_compressed_hex_taker FROM taken_offers WHERE offer_id = ?", ) .bind(trade_id) @@ -873,6 +875,8 @@ impl CoordinatorDB { let musig_pub_nonce_hex_taker: &str = row.try_get("musig_pub_nonce_hex_taker")?; let payout_amount_maker: u64 = row.try_get::("escrow_amount_maker_sat")? as u64; let payout_amount_taker: u64 = row.try_get::("escrow_amount_taker_sat")? as u64; + let musig_pubkey_hex_maker: &str = row.try_get("musig_pubkey_compressed_hex_maker")?; + let musig_pubkey_hex_taker: &str = row.try_get("musig_pubkey_compressed_hex_taker")?; PayoutData::new_from_strings( escrow_output_descriptor, @@ -882,6 +886,8 @@ impl CoordinatorDB { payout_amount_taker, musig_pub_nonce_hex_maker, musig_pub_nonce_hex_taker, + musig_pubkey_hex_maker, + musig_pubkey_hex_taker, ) } } diff --git a/taptrade-cli-demo/coordinator/src/wallet/escrow_psbt.rs b/taptrade-cli-demo/coordinator/src/wallet/escrow_psbt.rs index eb8d281..68ad7aa 100644 --- a/taptrade-cli-demo/coordinator/src/wallet/escrow_psbt.rs +++ b/taptrade-cli-demo/coordinator/src/wallet/escrow_psbt.rs @@ -37,7 +37,7 @@ impl EscrowPsbtConstructionData { pub fn aggregate_musig_pubkeys( maker_musig_pubkey: &str, taker_musig_pubkey: &str, -) -> Result { +) -> Result { debug!( "Aggregating musig pubkeys: {} and {}", maker_musig_pubkey, taker_musig_pubkey @@ -48,11 +48,7 @@ pub fn aggregate_musig_pubkeys( ]; let key_agg_ctx = KeyAggContext::new(pubkeys).context("Error aggregating musig pubkeys")?; - let agg_pk: MuSig2PubKey = key_agg_ctx.aggregated_pubkey(); - let bitcoin_pk = bdk::bitcoin::PublicKey::from_slice(&agg_pk.serialize()) - .context("Error converting musig pk to bitcoin pk")? - .to_x_only_pubkey(); - Ok(bitcoin_pk) + Ok(key_agg_ctx) } /// this function builds the escrow output with all possible spending conditions @@ -107,10 +103,15 @@ pub fn build_escrow_transaction_output_descriptor( let tap_root = TapTree::Tree(Arc::new(tap_node_ab), Arc::new(tap_node_cd)); // An internal key, that defines the way to spend the transaction directly, using Key Path Spending - let internal_agg_musig_key: XOnlyPublicKey = aggregate_musig_pubkeys( + let key_agg_ctx: KeyAggContext = aggregate_musig_pubkeys( &maker_escrow_data.musig_pubkey_compressed_hex, &taker_escrow_data.musig_pubkey_compressed_hex, )?; + let internal_agg_musig_key: XOnlyPublicKey = bdk::bitcoin::PublicKey::from_slice( + &key_agg_ctx.aggregated_pubkey::().serialize(), + ) + .context("Error converting musig pk to bitcoin pk")? + .to_x_only_pubkey(); // Create the descriptor let descriptor = Descriptor::::new_tr(internal_agg_musig_key, Some(tap_root)) diff --git a/taptrade-cli-demo/trader/src/communication/api.rs b/taptrade-cli-demo/trader/src/communication/api.rs index ab02afc..aecc01f 100644 --- a/taptrade-cli-demo/trader/src/communication/api.rs +++ b/taptrade-cli-demo/trader/src/communication/api.rs @@ -114,6 +114,7 @@ pub struct TradeObligationsSatisfied { pub struct PayoutResponse { pub payout_psbt_hex: String, pub agg_musig_nonce_hex: String, + pub agg_musig_pubkey_ctx_hex: String, } #[derive(Debug, Serialize)] pub struct TradeObligationsUnsatisfied { diff --git a/taptrade-cli-demo/trader/src/communication/mod.rs b/taptrade-cli-demo/trader/src/communication/mod.rs index 9669283..b7d6795 100644 --- a/taptrade-cli-demo/trader/src/communication/mod.rs +++ b/taptrade-cli-demo/trader/src/communication/mod.rs @@ -14,7 +14,7 @@ use bdk::{ bitcoin::{consensus::Encodable, psbt::PartiallySignedTransaction}, wallet::AddressInfo, }; -use musig2::AggNonce; +use musig2::{AggNonce, KeyAggContext}; use serde::{Deserialize, Serialize}; use std::{f32::consts::E, str::FromStr, thread::sleep, time::Duration}; @@ -248,7 +248,7 @@ impl IsOfferReadyRequest { pub fn poll_payout( trader_config: &TraderSettings, offer: &ActiveOffer, - ) -> Result<(PartiallySignedTransaction, AggNonce)> { + ) -> Result<(PartiallySignedTransaction, AggNonce, KeyAggContext)> { let request = IsOfferReadyRequest { robohash_hex: trader_config.robosats_robohash_hex.clone(), offer_id_hex: offer.offer_id_hex.clone(), @@ -295,7 +295,9 @@ impl IsOfferReadyRequest { let final_psbt = PartiallySignedTransaction::from_str(&payout_response.payout_psbt_hex)?; let agg_nonce = AggNonce::from_str(&payout_response.agg_musig_nonce_hex) .map_err(|e| anyhow!("Error parsing agg nonce: {}", e))?; - Ok((final_psbt, agg_nonce)) + let agg_pubk_ctx = KeyAggContext::from_hex(&payout_response.agg_musig_nonce_hex) + .map_err(|e| anyhow!("Error parsing agg pubkey ctx: {}", e))?; + Ok((final_psbt, agg_nonce, agg_pubk_ctx)) } } diff --git a/taptrade-cli-demo/trader/src/trading/mod.rs b/taptrade-cli-demo/trader/src/trading/mod.rs index 44634aa..f81f093 100644 --- a/taptrade-cli-demo/trader/src/trading/mod.rs +++ b/taptrade-cli-demo/trader/src/trading/mod.rs @@ -50,11 +50,12 @@ pub fn run_maker(maker_config: &TraderSettings) -> Result<()> { // this represents the "confirm payment" / "confirm fiat recieved" button TradeObligationsSatisfied::submit(&offer.offer_id_hex, maker_config)?; info!("Waiting for other party to confirm the trade."); - let (payout_keyspend_psbt, agg_pub_nonce) = + let (payout_keyspend_psbt, agg_pub_nonce, agg_pubk_ctx) = IsOfferReadyRequest::poll_payout(maker_config, &offer)?; + let signed_payout_psbt = wallet .validate_payout_psbt(&payout_keyspend_psbt)? - .sign_payout_psbt(payout_keyspend_psbt, agg_pub_nonce)?; + .sign_payout_psbt(payout_keyspend_psbt, agg_pub_nonce, agg_pubk_ctx)?; // submit signed payout psbt back to coordinator panic!("Payout to be implemented!"); } else { diff --git a/taptrade-cli-demo/trader/src/wallet/mod.rs b/taptrade-cli-demo/trader/src/wallet/mod.rs index 450c6ab..1e9c99e 100644 --- a/taptrade-cli-demo/trader/src/wallet/mod.rs +++ b/taptrade-cli-demo/trader/src/wallet/mod.rs @@ -7,21 +7,23 @@ use crate::{ cli::TraderSettings, communication::api::{BondRequirementResponse, OfferTakenResponse}, }; -use ::musig2::AggNonce; -use anyhow::{anyhow, Result}; +use ::musig2::{AggNonce, KeyAggContext}; +use anyhow::{anyhow, Context, Result}; use bdk::{ bitcoin::{ self, bip32::ExtendedPrivKey, consensus::encode::serialize_hex, + hashes::Hash, key::{KeyPair, Secp256k1, XOnlyPublicKey}, - psbt::{serialize, Input, PartiallySignedTransaction}, - Address, Network, + psbt::{serialize, Input, PartiallySignedTransaction, Prevouts}, + sighash::{SighashCache, TapSighash, TapSighashType}, + Address, Network, TxOut, }, blockchain::ElectrumBlockchain, database::{Database, MemoryDatabase}, electrum_client::Client, - keys::DescriptorPublicKey, + keys::{DescriptorPublicKey, ValidNetworks}, miniscript::{descriptor::Tr, Descriptor}, template::{Bip86, DescriptorTemplate}, wallet::{AddressIndex, AddressInfo}, @@ -207,11 +209,31 @@ impl TradingWallet { Ok(self) } - pub fn sign_payout_psbt( + /// creates a partitial signature to spend the keyspend path of the escrow output + /// which will be returned to the coordinator for aggregation + pub fn sign_keyspend_payout_psbt( &self, - psbt: PartiallySignedTransaction, + validated_payout_psbt: PartiallySignedTransaction, + key_agg_context: KeyAggContext, agg_pub_nonce: AggNonce, - ) -> Result { + ) -> Result { + let payout_tx = validated_payout_psbt.extract_tx(); + let sig_hash_cache = SighashCache::new(payout_tx); + + let utxo = validated_payout_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 keyspend_sig_hash_msg = sig_hash_cache + .taproot_key_spend_signature_hash(0, &Prevouts::All(&[utxo]), TapSighashType::All) + .context("Failed to create keyspend sighash")? + .as_byte_array(); + + panic!("Implement keyspend signing"); + Ok(signed_psbt) } }