mirror of
https://github.com/conduition/dlctix.git
synced 2026-01-29 20:55:10 +01:00
perf: verify only signatures relevant to specific players
Each player only cares about the signatures which are needed to enforce outcomes in which they receive money. Other outcomes are entirely optional. The market maker must verify all signatures.
This commit is contained in:
@@ -5,7 +5,7 @@ use musig2::{
|
||||
};
|
||||
use secp::{Point, Scalar};
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use crate::{
|
||||
contract::{ContractParameters, Outcome},
|
||||
@@ -223,7 +223,7 @@ pub(crate) fn verify_outcome_tx_partial_signatures(
|
||||
pub(crate) struct OutcomeSignatures {
|
||||
/// A set of adaptor signatures which can be unlocked by the oracle's attestation
|
||||
/// for each outcome.
|
||||
pub(crate) outcome_tx_signatures: Vec<AdaptorSignature>,
|
||||
pub(crate) outcome_tx_signatures: BTreeMap<usize, AdaptorSignature>,
|
||||
|
||||
/// The complete signature on the expiry transaction. This is `None` if the
|
||||
/// [`ContractParameters::outcome_payouts`] field does not contain an
|
||||
@@ -253,7 +253,7 @@ where
|
||||
let funding_spend_info = &outcome_build_out.funding_spend_info;
|
||||
|
||||
let mut signatures = OutcomeSignatures {
|
||||
outcome_tx_signatures: Vec::with_capacity(params.event.outcome_messages.len()),
|
||||
outcome_tx_signatures: BTreeMap::new(),
|
||||
expiry_tx_signature: None,
|
||||
};
|
||||
|
||||
@@ -282,7 +282,9 @@ where
|
||||
sighash,
|
||||
)?;
|
||||
|
||||
signatures.outcome_tx_signatures.push(adaptor_sig);
|
||||
signatures
|
||||
.outcome_tx_signatures
|
||||
.insert(outcome_index, adaptor_sig);
|
||||
}
|
||||
|
||||
Outcome::Expiry => {
|
||||
@@ -305,37 +307,43 @@ where
|
||||
/// outcome and expiry transactions.
|
||||
pub(crate) fn verify_outcome_tx_aggregated_signatures(
|
||||
params: &ContractParameters,
|
||||
our_pubkey: Point,
|
||||
outcome_build_out: &OutcomeTransactionBuildOutput,
|
||||
outcome_tx_signatures: &[AdaptorSignature],
|
||||
outcome_tx_signatures: &BTreeMap<usize, AdaptorSignature>,
|
||||
expiry_tx_signature: Option<CompactSignature>,
|
||||
) -> Result<(), Error> {
|
||||
let funding_spend_info = &outcome_build_out.funding_spend_info;
|
||||
|
||||
// One signature per outcome TX.
|
||||
if outcome_tx_signatures.len() != params.event.outcome_messages.len() {
|
||||
return Err(Error);
|
||||
}
|
||||
|
||||
let joint_pubkey: Point = funding_spend_info.key_agg_ctx().aggregated_pubkey();
|
||||
|
||||
// We only need to verify signatures on outcomes where our pubkey might
|
||||
// win something.
|
||||
let relevant_outcomes: BTreeSet<Outcome> = params
|
||||
.win_conditions_controlled_by_pubkey(our_pubkey)
|
||||
.ok_or(Error)?
|
||||
.into_iter()
|
||||
.map(|win_cond| win_cond.outcome)
|
||||
.collect();
|
||||
|
||||
// Construct a batch for efficient mass signature verification.
|
||||
let batch: Vec<BatchVerificationRow> = outcome_build_out
|
||||
.outcome_txs
|
||||
.iter()
|
||||
.map(|(outcome, outcome_tx)| {
|
||||
let batch: Vec<BatchVerificationRow> = relevant_outcomes
|
||||
.into_iter()
|
||||
.map(|outcome| {
|
||||
let outcome_tx = outcome_build_out.outcome_txs.get(&outcome).ok_or(Error)?;
|
||||
|
||||
let sighash = outcome_build_out
|
||||
.funding_spend_info
|
||||
.sighash_tx_outcome(outcome_tx)?;
|
||||
|
||||
let batch_row = match outcome {
|
||||
// One adaptor signature for each possible attestation outcome.
|
||||
&Outcome::Attestation(outcome_index) => {
|
||||
Outcome::Attestation(outcome_index) => {
|
||||
let adaptor_point = params
|
||||
.event
|
||||
.attestation_lock_point(outcome_index)
|
||||
.ok_or(Error)?;
|
||||
|
||||
let &signature = outcome_tx_signatures.get(outcome_index).ok_or(Error)?;
|
||||
let &signature = outcome_tx_signatures.get(&outcome_index).ok_or(Error)?;
|
||||
BatchVerificationRow::from_adaptor_signature(
|
||||
joint_pubkey,
|
||||
sighash,
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::{
|
||||
spend_info::SplitSpendInfo,
|
||||
};
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
/// Represents the output of building the set of split transactions.
|
||||
/// This contains cached data used for constructing further transactions,
|
||||
@@ -274,18 +274,18 @@ where
|
||||
/// Verify the set of complete aggregated signatures on the split transactions.
|
||||
pub(crate) fn verify_split_tx_aggregated_signatures(
|
||||
params: &ContractParameters,
|
||||
our_pubkey: Point,
|
||||
outcome_build_out: &OutcomeTransactionBuildOutput,
|
||||
split_build_out: &SplitTransactionBuildOutput,
|
||||
split_tx_signatures: &BTreeMap<WinCondition, CompactSignature>,
|
||||
) -> Result<(), Error> {
|
||||
let all_win_conditions = params.all_win_conditions();
|
||||
// We only need to verify signatures on win conditions where our pubkey might
|
||||
// win something.
|
||||
let relevant_win_conditions: BTreeSet<WinCondition> = params
|
||||
.win_conditions_controlled_by_pubkey(our_pubkey)
|
||||
.ok_or(Error)?;
|
||||
|
||||
// One signature per win condition.
|
||||
if split_tx_signatures.len() != all_win_conditions.len() {
|
||||
return Err(Error);
|
||||
}
|
||||
|
||||
let batch: Vec<BatchVerificationRow> = all_win_conditions
|
||||
let batch: Vec<BatchVerificationRow> = relevant_win_conditions
|
||||
.into_iter()
|
||||
.map(|win_cond| {
|
||||
let split_tx = split_build_out
|
||||
|
||||
73
src/lib.rs
73
src/lib.rs
@@ -21,7 +21,7 @@ use bitcoin::{OutPoint, Transaction, TxOut};
|
||||
use musig2::{AdaptorSignature, AggNonce, CompactSignature, PartialSignature, PubNonce, SecNonce};
|
||||
use secp::{MaybeScalar, Point, Scalar};
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
pub use contract::{ContractParameters, Outcome, SigMap, WinCondition};
|
||||
pub use oracles::EventAnnouncement;
|
||||
@@ -350,6 +350,7 @@ impl SigningSession<PartialSignatureSharingRound> {
|
||||
) -> Result<(), Error> {
|
||||
contract::outcome::verify_outcome_tx_aggregated_signatures(
|
||||
&self.dlc.params,
|
||||
self.our_public_key,
|
||||
&self.dlc.outcome_tx_build,
|
||||
&signatures.outcome_tx_signatures,
|
||||
signatures.expiry_tx_signature,
|
||||
@@ -357,6 +358,7 @@ impl SigningSession<PartialSignatureSharingRound> {
|
||||
|
||||
contract::split::verify_split_tx_aggregated_signatures(
|
||||
&self.dlc.params,
|
||||
self.our_public_key,
|
||||
&self.dlc.outcome_tx_build,
|
||||
&self.dlc.split_tx_build,
|
||||
&signatures.split_tx_signatures,
|
||||
@@ -395,17 +397,26 @@ fn validate_sigmaps_completeness<T>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A set of signatures produced by running a cooperative [`SigningSession`] on a
|
||||
/// [`TicketedDLC`]. These are only the signatures needed for enforcing outcomes
|
||||
/// which multiple members of the group must agree on.
|
||||
///
|
||||
/// Players do not need a fully copy of every outcome and split TX signature.
|
||||
/// Only some players care about certain outcomes, and a player only enforce one
|
||||
/// specific split TX unlock condition - the one corresponding to their ticket
|
||||
/// hash. We can save bandwidth and
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ContractSignatures {
|
||||
/// A complete signature on the expiry transaction. Set to `None` if the
|
||||
/// [`ContractParameters::outcome_payouts`] field did not contain an
|
||||
/// [`Outcome::Expiry`] payout condition.
|
||||
pub expiry_tx_signature: Option<CompactSignature>,
|
||||
/// An ordered vector of adaptor signatures, corresponding to each of the outcomes
|
||||
/// in [`EventAnnouncement::outcome_messages`]. Each adaptor signature can be decrypted
|
||||
/// A mapping of outcome attestation indexes to adaptor signatures on outcome transactions.
|
||||
/// The index of each entry corresponds to the outcomes in
|
||||
/// [`EventAnnouncement::outcome_messages`]. Each adaptor signature can be decrypted
|
||||
/// by the [`EventAnnouncement`]'s oracle producing an attestation signature using
|
||||
/// [`EventAnnouncement::attestation_secret`].
|
||||
pub outcome_tx_signatures: Vec<AdaptorSignature>,
|
||||
pub outcome_tx_signatures: BTreeMap<usize, AdaptorSignature>,
|
||||
/// A set of signatures needed for broadcasting split transactions. Each signature
|
||||
/// is specific to a certain combination of player and outcome.
|
||||
pub split_tx_signatures: BTreeMap<WinCondition, CompactSignature>,
|
||||
@@ -419,9 +430,59 @@ pub struct SignedContract {
|
||||
}
|
||||
|
||||
impl SignedContract {
|
||||
pub fn signatures(&self) -> &ContractSignatures {
|
||||
/// Returns the complete set of signatures for all outcomes and win conditions.
|
||||
pub fn all_signatures(&self) -> &ContractSignatures {
|
||||
&self.signatures
|
||||
}
|
||||
|
||||
/// Produce a pruned set of signatures, relevant to the specific signer's pubkey.
|
||||
/// If the pubkey belongs to the market maker, this simply returns a clone of the
|
||||
/// full set of signatures, since the market maker is involved in every multisignature
|
||||
/// spending condition.
|
||||
pub fn pruned_signatures(&self, player_pubkey: Point) -> Option<ContractSignatures> {
|
||||
if player_pubkey == self.dlc.params.market_maker.pubkey {
|
||||
return Some(self.signatures.clone());
|
||||
}
|
||||
|
||||
let relevant_win_conditions = self
|
||||
.dlc
|
||||
.params
|
||||
.win_conditions_controlled_by_pubkey(player_pubkey)?;
|
||||
|
||||
let relevant_outcomes: BTreeSet<Outcome> = relevant_win_conditions
|
||||
.iter()
|
||||
.map(|win_cond| win_cond.outcome)
|
||||
.collect();
|
||||
|
||||
let pruned_sigs = ContractSignatures {
|
||||
expiry_tx_signature: self
|
||||
.signatures
|
||||
.expiry_tx_signature
|
||||
.filter(|_| relevant_outcomes.contains(&Outcome::Expiry)),
|
||||
|
||||
outcome_tx_signatures: self
|
||||
.signatures
|
||||
.outcome_tx_signatures
|
||||
.iter()
|
||||
.filter(|(&outcome_index, _)| {
|
||||
relevant_outcomes.contains(&Outcome::Attestation(outcome_index))
|
||||
})
|
||||
.map(|(&outcome_index, &sig)| (outcome_index, sig))
|
||||
.collect(),
|
||||
|
||||
split_tx_signatures: self
|
||||
.signatures
|
||||
.split_tx_signatures
|
||||
.iter()
|
||||
.filter(|(win_cond, _)| relevant_win_conditions.contains(win_cond))
|
||||
.map(|(&win_cond, &sig)| (win_cond, sig))
|
||||
.collect(),
|
||||
};
|
||||
|
||||
Some(pruned_sigs)
|
||||
}
|
||||
|
||||
/// Returns the [`TicketedDLC`] which has been signed.
|
||||
pub fn dlc(&self) -> &TicketedDLC {
|
||||
&self.dlc
|
||||
}
|
||||
@@ -462,7 +523,7 @@ impl SignedContract {
|
||||
let adaptor_signature = self
|
||||
.signatures
|
||||
.outcome_tx_signatures
|
||||
.get(outcome_index)
|
||||
.get(&outcome_index)
|
||||
.ok_or(Error)?;
|
||||
|
||||
let compact_sig: CompactSignature = adaptor_signature.adapt(attestation).ok_or(Error)?;
|
||||
|
||||
Reference in New Issue
Block a user