mirror of
https://github.com/conduition/dlctix.git
synced 2026-02-20 07:24:24 +01:00
refactor SigningSession API to encourage use of a coordinator
This commit is contained in:
109
src/lib.rs
109
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<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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user