From 76f13655641062d886d493097b4d6a7aa2a0ebdb Mon Sep 17 00:00:00 2001 From: conduition Date: Wed, 20 Mar 2024 05:38:30 +0000 Subject: [PATCH] refactor SigningSession API to encourage use of a coordinator --- src/lib.rs | 109 ++++++++++++++++++++++++++++++++++++++++++++----- src/regtest.rs | 53 ++++++++++++------------ 2 files changed, 125 insertions(+), 37 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e914ccf..1aacdf1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -132,15 +132,25 @@ pub struct NonceSharingRound { } /// A [`SigningSessionState`] state for the second signature-sharing -/// round of communication. -pub struct PartialSignatureSharingRound { +/// round of communication used by the signing coordinator (usually +/// this would be the market maker). +pub struct CoordinatorPartialSignatureSharingRound { received_nonces: BTreeMap>, aggregated_nonces: SigMap, our_partial_signatures: SigMap, } +/// A [`SigningSessionState`] state for the second signature-sharing +/// round of communication used by individual signing contributors. +/// Usually this would be used by a player. +pub struct ContributorPartialSignatureSharingRound { + aggregated_nonces: SigMap, + our_partial_signatures: SigMap, +} + impl SigningSessionState for NonceSharingRound {} -impl SigningSessionState for PartialSignatureSharingRound {} +impl SigningSessionState for CoordinatorPartialSignatureSharingRound {} +impl SigningSessionState for ContributorPartialSignatureSharingRound {} /// This is a state machine to manage signing the various transactions in a [`TicketedDLC`]. /// The generic parameter `S` determines which of the two stages of the signing session @@ -199,19 +209,21 @@ impl SigningSession { &self.state.our_public_nonces } - /// Receive the nonces from all other signers and construct our set of partial - /// signatures. This begins the `PartialSignatureSharingRound` of the `SigningSession`. - pub fn compute_partial_signatures( + /// Receive the nonces from all other signers and aggregate them into a full set + /// of aggregated nonces for every signing path. The aggnonces are then used to + /// compute our own partial signatures. Both are stored in the resulting + /// `SigningSession`. + pub fn aggregate_nonces_and_compute_partial_signatures( self, mut received_nonces: BTreeMap>, - ) -> Result, Error> { + ) -> Result, Error> { // Insert our own public nonces so that callers don't need // to inject them manually. received_nonces.insert(self.our_public_key, self.state.our_public_nonces); validate_sigmaps_completeness(&self.dlc.params, &received_nonces)?; - let aggregated_nonces: SigMap = self.dlc.params.full_sigmap().map( + let aggregated_nonces = self.dlc.params.full_sigmap().map( |outcome, _| { received_nonces .values() @@ -244,11 +256,46 @@ impl SigningSession { )?, }; + let coordinator_session = SigningSession { + dlc: self.dlc, + our_public_key: self.our_public_key, + state: CoordinatorPartialSignatureSharingRound { + received_nonces, + aggregated_nonces, + our_partial_signatures, + }, + }; + Ok(coordinator_session) + } + + /// Receive the nonces from all other signers and construct our set of partial + /// signatures. This begins the `ContributorPartialSignatureSharingRound` of the `SigningSession`. + pub fn compute_partial_signatures( + self, + aggregated_nonces: SigMap, + ) -> Result, Error> { + let our_partial_signatures = SigMap { + by_outcome: contract::outcome::partial_sign_outcome_txs( + &self.dlc.params, + &self.dlc.outcome_tx_build, + self.state.signing_key, + self.state.our_secret_nonces.by_outcome, + &aggregated_nonces.by_outcome, + )?, + by_win_condition: contract::split::partial_sign_split_txs( + &self.dlc.params, + &self.dlc.outcome_tx_build, + &self.dlc.split_tx_build, + self.state.signing_key, + self.state.our_secret_nonces.by_win_condition, + &aggregated_nonces.by_win_condition, + )?, + }; + let session = SigningSession { dlc: self.dlc, our_public_key: self.our_public_key, - state: PartialSignatureSharingRound { - received_nonces, + state: ContributorPartialSignatureSharingRound { aggregated_nonces, our_partial_signatures, }, @@ -257,7 +304,7 @@ impl SigningSession { } } -impl SigningSession { +impl SigningSession { /// Returns the set of partial signatures which should be shared /// with our signing peers. pub fn our_partial_signatures(&self) -> &SigMap { @@ -306,6 +353,46 @@ impl SigningSession { Ok(()) } + /// Unwrap the coordinator's session into a regular peer's signing session. + pub fn into_inner(self) -> SigningSession { + SigningSession { + dlc: self.dlc, + our_public_key: self.our_public_key, + state: ContributorPartialSignatureSharingRound { + aggregated_nonces: self.state.aggregated_nonces, + our_partial_signatures: self.state.our_partial_signatures, + }, + } + } + + /// Combine all the partial signatures received from peers. Assumes all signature sets have + /// been verified individually using [`verify_partial_signatures`][Self::verify_partial_signatures]. + /// The aggregated signatures will still be verified before this method returns, but not in + /// a way that blame can be properly placed on erroneous peer signatures. + /// + /// This completes the signing session. + pub fn aggregate_all_signatures( + self, + received_signatures: BTreeMap>, + ) -> Result { + self.into_inner() + .aggregate_all_signatures(received_signatures) + } +} + +impl SigningSession { + /// Returns the set of partial signatures which should be shared + /// with our signing peers. + pub fn our_partial_signatures(&self) -> &SigMap { + &self.state.our_partial_signatures + } + + /// Returns the set of aggregated nonces which should be sent to + /// our signing peers if they don't already have them. + pub fn aggregated_nonces(&self) -> &SigMap { + &self.state.aggregated_nonces + } + /// Combine all the partial signatures received from peers. Assumes all signature sets have /// been verified individually using [`verify_partial_signatures`][Self::verify_partial_signatures]. /// The aggregated signatures will still be verified before this method returns, but not in diff --git a/src/regtest.rs b/src/regtest.rs index 1f0f720..7520307 100644 --- a/src/regtest.rs +++ b/src/regtest.rs @@ -206,8 +206,9 @@ fn musig_sign_ticketed_dlc( ticketed_dlc: &TicketedDLC, all_seckeys: impl IntoIterator, rng: &mut R, + verify_all_partial_signatures: bool, ) -> SignedContract { - let signing_sessions: BTreeMap> = all_seckeys + let mut signing_sessions: BTreeMap> = all_seckeys .into_iter() .map(|seckey| { let session = SigningSession::new(ticketed_dlc.clone(), rng, seckey) @@ -228,12 +229,18 @@ fn musig_sign_ticketed_dlc( }) .collect(); - let signing_sessions: BTreeMap> = + let coordinator_session = signing_sessions + .remove(&ticketed_dlc.params().market_maker.pubkey) + .unwrap() + .aggregate_nonces_and_compute_partial_signatures(pubnonces_by_sender) + .expect("error aggregating pubnonces"); + + let signing_sessions: BTreeMap> = signing_sessions .into_iter() .map(|(pubkey, session)| { let new_session = session - .compute_partial_signatures(pubnonces_by_sender.clone()) + .compute_partial_signatures(coordinator_session.aggregated_nonces().clone()) .expect("failed to compute partial signatures"); (pubkey, new_session) }) @@ -250,45 +257,39 @@ fn musig_sign_ticketed_dlc( }) .collect(); - // Everyone's signatures can be verified by everyone else. - for session in signing_sessions.values() { + // Every player's signatures can be verified individually by the coordinator. + if verify_all_partial_signatures { for (&sender_pubkey, partial_sigs) in &partial_sigs_by_sender { - session + coordinator_session .verify_partial_signatures(sender_pubkey, partial_sigs) .expect("valid partial signatures should be verified as OK"); } } - let mut signed_contracts: BTreeMap = signing_sessions - .into_iter() - .map(|(pubkey, session)| { - let signed_contract = session - .aggregate_all_signatures(partial_sigs_by_sender.clone()) - .expect("error during signature aggregation"); - (pubkey, signed_contract) - }) - .collect(); + let signed_contract = coordinator_session + .aggregate_all_signatures(partial_sigs_by_sender) + .expect("error aggregating partial signatures"); - // Everyone should have computed the same set of signatures. - for contract1 in signed_contracts.values() { - for contract2 in signed_contracts.values() { - assert_eq!(contract1.all_signatures(), contract2.all_signatures()); - } + for session in signing_sessions.into_values() { + session + .verify_aggregated_signatures(signed_contract.all_signatures()) + .expect("player failed to verify signatures aggregated by the market maker"); + + // let player_signed_contract = + // session.into_signed_contract(signed_contract.all_signatures().clone()); } - let (_, contract) = signed_contracts.pop_first().unwrap(); - // SignedContract should be able to be stored and retrieved via serde serialization. let decoded_contract = serde_json::from_str( - &serde_json::to_string(&contract).expect("error serializing SignedContract"), + &serde_json::to_string(&signed_contract).expect("error serializing SignedContract"), ) .expect("error deserializing SignedContract"); assert_eq!( - contract, decoded_contract, + signed_contract, decoded_contract, "deserialized SignedContract does not match original" ); - contract + signed_contract } struct SimulationManager { @@ -414,7 +415,7 @@ impl SimulationManager { dave.seckey, ]; - let signed_contract = musig_sign_ticketed_dlc(&ticketed_dlc, seckeys, &mut rng); + let signed_contract = musig_sign_ticketed_dlc(&ticketed_dlc, seckeys, &mut rng, true); // At this point, the market maker is confident they'll be able to reclaim their // capital if needed, and the players know they'll be able to enforce the DLC outcome