From 2bd8d4fed5149edcae70d49453dcc86cd7b0ea2f Mon Sep 17 00:00:00 2001 From: conduition Date: Mon, 18 Mar 2024 20:00:58 +0000 Subject: [PATCH] run signing session --- Cargo.lock | 1 - demo/common/Cargo.toml | 1 - demo/common/src/lib.rs | 15 +- demo/mm_server/src/global_state.rs | 1 + demo/mm_server/src/server/mod.rs | 16 ++- demo/mm_server/src/server/signing_session.rs | 142 +++++++++++++++++++ src/lib.rs | 1 + 7 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 demo/mm_server/src/server/signing_session.rs diff --git a/Cargo.lock b/Cargo.lock index 51a157d..1a1a2bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,7 +106,6 @@ version = "0.1.0" dependencies = [ "bitcoin", "dlctix", - "secp", "serde", ] diff --git a/demo/common/Cargo.toml b/demo/common/Cargo.toml index 06d20b6..c2ac484 100644 --- a/demo/common/Cargo.toml +++ b/demo/common/Cargo.toml @@ -8,5 +8,4 @@ edition = "2021" [dependencies] bitcoin = "0.31.1" dlctix = { version = "0.0.4", path = "../.." } -secp = { version = "0.2.3", features = ["serde"] } serde = "1.0.197" diff --git a/demo/common/src/lib.rs b/demo/common/src/lib.rs index a15ed65..61ed740 100644 --- a/demo/common/src/lib.rs +++ b/demo/common/src/lib.rs @@ -1,6 +1,7 @@ use bitcoin::Amount; -use dlctix::{ContractParameters, EventAnnouncement, Outcome}; -use secp::Point; +use dlctix::musig2::AggNonce; +use dlctix::secp::Point; +use dlctix::{ContractParameters, ContractSignatures, EventAnnouncement, Outcome, SigMap}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -49,3 +50,13 @@ pub enum ServerOfferAck { Ok, Retry, } + +#[derive(Serialize, Deserialize)] +pub enum ServerNonceAck { + Ok(SigMap), +} + +#[derive(Serialize, Deserialize)] +pub enum ServerSignatureAck { + Ok(ContractSignatures), +} diff --git a/demo/mm_server/src/global_state.rs b/demo/mm_server/src/global_state.rs index a8c7610..94e89e5 100644 --- a/demo/mm_server/src/global_state.rs +++ b/demo/mm_server/src/global_state.rs @@ -29,6 +29,7 @@ pub(crate) struct PlayerRegistration { pub(crate) enum Stage { IntentRegistry, OfferAndAck, + SigningSession, } pub(crate) struct GlobalState { diff --git a/demo/mm_server/src/server/mod.rs b/demo/mm_server/src/server/mod.rs index c4390c8..8c5ce44 100644 --- a/demo/mm_server/src/server/mod.rs +++ b/demo/mm_server/src/server/mod.rs @@ -1,5 +1,6 @@ mod handshake; mod offer_and_ack; +mod signing_session; use crate::errors::WrongStageError; use crate::global_state::{GlobalState, Stage}; @@ -42,7 +43,20 @@ fn handle_tcp_conn( } if let Some(accepted_offers) = offer_and_ack::offer_and_ack_cycle(&state)? { - // TODO prompt all players for signatures + { + let mut state_wlock = state.write().unwrap(); + state_wlock.stage = Stage::SigningSession; + } + + let (funding_tx, signed_contract) = + signing_session::run_signing_sessions(&state, accepted_offers)?; + + // TODO: + // - store signed contract offline + // - accept prepayments atomically + // - broadcast funding TX and await confs + // - sell ticket preimages atomically + // - watch blockchain and resolve contract } } diff --git a/demo/mm_server/src/server/signing_session.rs b/demo/mm_server/src/server/signing_session.rs new file mode 100644 index 0000000..85ea51a --- /dev/null +++ b/demo/mm_server/src/server/signing_session.rs @@ -0,0 +1,142 @@ +use bitcoin::{absolute::LockTime, OutPoint, Transaction}; +use bitcoincore_rpc::RpcApi; + +use crate::global_state::GlobalState; +use common::{PlayerID, ServerNonceAck, ServerOffer, ServerOfferAck, ServerSignatureAck}; +use dlctix::musig2::{PartialSignature, PubNonce}; +use dlctix::secp::Point; +use dlctix::{SigMap, SignedContract, SigningSession, TicketedDLC}; + +use std::{ + collections::BTreeMap, + error::Error, + sync::{Arc, RwLock}, + thread, +}; + +pub(crate) fn run_signing_sessions( + state: &Arc>, + accepted_offers: BTreeMap, +) -> Result<(Transaction, SignedContract), Box> { + let contract_parameters = accepted_offers + .values() + .next() + .unwrap() + .contract_parameters + .clone(); + + // Create, fund, and sign the funding transaction using + // bitcoind's current loaded wallet. + let funding_tx = { + let skeleton_tx = Transaction { + version: bitcoin::transaction::Version::TWO, + lock_time: LockTime::ZERO, + input: vec![], + output: vec![contract_parameters.funding_output()?], + }; + + let state_rlock = state.read().unwrap(); + let funded_tx = state_rlock + .bitcoind + .fund_raw_transaction(&skeleton_tx, None, Some(true))? + .hex; + + state_rlock + .bitcoind + .sign_raw_transaction_with_wallet(&funded_tx, None, None)? + .transaction()? + }; + + let funding_outpoint = OutPoint { + txid: funding_tx.txid(), + vout: 0, + }; + + let ticketed_dlc = TicketedDLC::new(contract_parameters, funding_outpoint)?; + let signing_session = SigningSession::new( + ticketed_dlc, + &mut rand::thread_rng(), + state.read().unwrap().market_maker_seckey, + )?; + + // Round 1: receive nonces. + let thread_handles: Vec> = accepted_offers + .keys() + .map(|&player_id| { + let state = Arc::clone(state); + thread::spawn(move || -> serde_cbor::Result<(Point, SigMap)> { + let state_rlock = state.read().unwrap(); + let conn = &state_rlock.registrations[&player_id].connection; + let pubkey = state_rlock.registrations[&player_id].player.pubkey; + serde_cbor::to_writer(conn, &ServerOfferAck::Ok)?; + let nonces: SigMap = serde_cbor::from_reader(conn)?; + Ok((pubkey, nonces)) + }) + }) + .collect(); + + // TODO tell clients if someone failed to share nonces and we have to retry. + let received_nonces: BTreeMap> = thread_handles + .into_iter() + .map(|handle| handle.join().unwrap()) + .collect::>()?; + + // TODO handle invalid (incomplete) sets of nonces + let signing_session = signing_session.compute_partial_signatures(received_nonces)?; + + // Round 2: distribute agg nonces and receive partial signatures + let thread_handles: Vec> = accepted_offers + .keys() + .map(|&player_id| { + let state = Arc::clone(state); + let agg_nonces = signing_session.aggregated_nonces().clone(); + + thread::spawn( + move || -> serde_cbor::Result<(Point, SigMap)> { + let state_rlock = state.read().unwrap(); + let conn = &state_rlock.registrations[&player_id].connection; + let pubkey = state_rlock.registrations[&player_id].player.pubkey; + + serde_cbor::to_writer(conn, &ServerNonceAck::Ok(agg_nonces))?; + let partial_sigs: SigMap = serde_cbor::from_reader(conn)?; + Ok((pubkey, partial_sigs)) + }, + ) + }) + .collect(); + + let received_signatures: BTreeMap> = thread_handles + .into_iter() + .map(|handle| handle.join().unwrap()) + .collect::>()?; + + // TODO assign blame + for (&signer, partial_sigs) in &received_signatures { + signing_session.verify_partial_signatures(signer, partial_sigs)?; + } + + let signed_contract = signing_session.aggregate_all_signatures(received_signatures)?; + + // Final round: distribute aggregated signatures. + let thread_handles: Vec> = accepted_offers + .keys() + .map(|&player_id| { + let state = Arc::clone(state); + // TODO send only pruned signatures to peers + let signatures = signed_contract.all_signatures().clone(); + + thread::spawn(move || -> serde_cbor::Result<()> { + let state_rlock = state.read().unwrap(); + let conn = &state_rlock.registrations[&player_id].connection; + serde_cbor::to_writer(conn, &ServerSignatureAck::Ok(signatures)) + }) + }) + .collect(); + + // ensure all players receive their signatures + for handle in thread_handles { + handle.join().unwrap()?; + } + + Ok((funding_tx, signed_contract)) +} diff --git a/src/lib.rs b/src/lib.rs index 40c8e84..80a2383 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ pub(crate) mod spend_info; pub mod hashlock; +pub use musig2; pub use secp; use contract::{