mirror of
https://github.com/RoboSats/taptrade-core.git
synced 2025-07-20 09:43:30 +00:00
work on musig signing
This commit is contained in:
@ -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)]
|
||||
|
@ -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<Self> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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::<i64, _>("escrow_amount_maker_sat")? as u64;
|
||||
let payout_amount_taker: u64 = row.try_get::<i64, _>("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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ impl EscrowPsbtConstructionData {
|
||||
pub fn aggregate_musig_pubkeys(
|
||||
maker_musig_pubkey: &str,
|
||||
taker_musig_pubkey: &str,
|
||||
) -> Result<XOnlyPublicKey> {
|
||||
) -> Result<KeyAggContext> {
|
||||
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::<MuSig2PubKey>().serialize(),
|
||||
)
|
||||
.context("Error converting musig pk to bitcoin pk")?
|
||||
.to_x_only_pubkey();
|
||||
|
||||
// Create the descriptor
|
||||
let descriptor = Descriptor::<XOnlyPublicKey>::new_tr(internal_agg_musig_key, Some(tap_root))
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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<PartiallySignedTransaction> {
|
||||
) -> Result<String> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user