mirror of
https://github.com/conduition/dlctix.git
synced 2026-01-30 13:15:11 +01:00
run signing session
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -106,7 +106,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitcoin",
|
||||
"dlctix",
|
||||
"secp",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<AggNonce>),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum ServerSignatureAck {
|
||||
Ok(ContractSignatures),
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ pub(crate) struct PlayerRegistration {
|
||||
pub(crate) enum Stage {
|
||||
IntentRegistry,
|
||||
OfferAndAck,
|
||||
SigningSession,
|
||||
}
|
||||
|
||||
pub(crate) struct GlobalState {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
142
demo/mm_server/src/server/signing_session.rs
Normal file
142
demo/mm_server/src/server/signing_session.rs
Normal file
@@ -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<RwLock<GlobalState>>,
|
||||
accepted_offers: BTreeMap<PlayerID, ServerOffer>,
|
||||
) -> Result<(Transaction, SignedContract), Box<dyn Error>> {
|
||||
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<thread::JoinHandle<_>> = accepted_offers
|
||||
.keys()
|
||||
.map(|&player_id| {
|
||||
let state = Arc::clone(state);
|
||||
thread::spawn(move || -> serde_cbor::Result<(Point, SigMap<PubNonce>)> {
|
||||
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<PubNonce> = 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<Point, SigMap<PubNonce>> = thread_handles
|
||||
.into_iter()
|
||||
.map(|handle| handle.join().unwrap())
|
||||
.collect::<Result<_, serde_cbor::Error>>()?;
|
||||
|
||||
// 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<thread::JoinHandle<_>> = 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<PartialSignature>)> {
|
||||
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<PartialSignature> = serde_cbor::from_reader(conn)?;
|
||||
Ok((pubkey, partial_sigs))
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let received_signatures: BTreeMap<Point, SigMap<PartialSignature>> = thread_handles
|
||||
.into_iter()
|
||||
.map(|handle| handle.join().unwrap())
|
||||
.collect::<Result<_, serde_cbor::Error>>()?;
|
||||
|
||||
// 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<thread::JoinHandle<_>> = 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))
|
||||
}
|
||||
@@ -12,6 +12,7 @@ pub(crate) mod spend_info;
|
||||
|
||||
pub mod hashlock;
|
||||
|
||||
pub use musig2;
|
||||
pub use secp;
|
||||
|
||||
use contract::{
|
||||
|
||||
Reference in New Issue
Block a user