mirror of
https://github.com/RoboSats/taptrade-core.git
synced 2025-07-18 16:53:22 +00:00
implementation of partial signature aggregation
This commit is contained in:
@ -265,7 +265,12 @@ async fn submit_payout_signature(
|
||||
Json(payload): Json<PayoutSignatureRequest>,
|
||||
) -> Result<Response, AppError> {
|
||||
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())
|
||||
|
@ -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<MusigAggNonce> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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)?,
|
||||
|
@ -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<Coordinator>,
|
||||
@ -326,39 +327,38 @@ pub async fn handle_final_payout(
|
||||
pub async fn handle_payout_signature(
|
||||
payload: &PayoutSignatureRequest,
|
||||
coordinator: Arc<Coordinator>,
|
||||
) -> Result<(), RequestError> {
|
||||
) -> Result<bool, RequestError> {
|
||||
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)
|
||||
}
|
||||
|
@ -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<Option<(String, String, String)>> {
|
||||
) -> 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<Option<KeyspendContext>> {
|
||||
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<String> = row.try_get("musig_partial_sig_maker")?;
|
||||
let taker_sig: Option<String> = 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)
|
||||
}
|
||||
|
@ -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)?;
|
||||
|
Reference in New Issue
Block a user