refactor SigningSession API to encourage use of a coordinator

This commit is contained in:
conduition
2024-03-20 05:38:30 +00:00
parent 96e82bd5f1
commit 76f1365564
2 changed files with 125 additions and 37 deletions

View File

@@ -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<Point, SigMap<PubNonce>>,
aggregated_nonces: SigMap<AggNonce>,
our_partial_signatures: SigMap<PartialSignature>,
}
/// 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<AggNonce>,
our_partial_signatures: SigMap<PartialSignature>,
}
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<NonceSharingRound> {
&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<Point, SigMap<PubNonce>>,
) -> Result<SigningSession<PartialSignatureSharingRound>, Error> {
) -> Result<SigningSession<CoordinatorPartialSignatureSharingRound>, 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<AggNonce> = 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<NonceSharingRound> {
)?,
};
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<AggNonce>,
) -> Result<SigningSession<ContributorPartialSignatureSharingRound>, 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<NonceSharingRound> {
}
}
impl SigningSession<PartialSignatureSharingRound> {
impl SigningSession<CoordinatorPartialSignatureSharingRound> {
/// Returns the set of partial signatures which should be shared
/// with our signing peers.
pub fn our_partial_signatures(&self) -> &SigMap<PartialSignature> {
@@ -306,6 +353,46 @@ impl SigningSession<PartialSignatureSharingRound> {
Ok(())
}
/// Unwrap the coordinator's session into a regular peer's signing session.
pub fn into_inner(self) -> SigningSession<ContributorPartialSignatureSharingRound> {
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<Point, SigMap<PartialSignature>>,
) -> Result<SignedContract, Error> {
self.into_inner()
.aggregate_all_signatures(received_signatures)
}
}
impl SigningSession<ContributorPartialSignatureSharingRound> {
/// Returns the set of partial signatures which should be shared
/// with our signing peers.
pub fn our_partial_signatures(&self) -> &SigMap<PartialSignature> {
&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<AggNonce> {
&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

View File

@@ -206,8 +206,9 @@ fn musig_sign_ticketed_dlc<R: RngCore + CryptoRng>(
ticketed_dlc: &TicketedDLC,
all_seckeys: impl IntoIterator<Item = Scalar>,
rng: &mut R,
verify_all_partial_signatures: bool,
) -> SignedContract {
let signing_sessions: BTreeMap<Point, SigningSession<NonceSharingRound>> = all_seckeys
let mut signing_sessions: BTreeMap<Point, SigningSession<NonceSharingRound>> = all_seckeys
.into_iter()
.map(|seckey| {
let session = SigningSession::new(ticketed_dlc.clone(), rng, seckey)
@@ -228,12 +229,18 @@ fn musig_sign_ticketed_dlc<R: RngCore + CryptoRng>(
})
.collect();
let signing_sessions: BTreeMap<Point, SigningSession<PartialSignatureSharingRound>> =
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<Point, SigningSession<ContributorPartialSignatureSharingRound>> =
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<R: RngCore + CryptoRng>(
})
.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<Point, SignedContract> = 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