add second round of state machine for finalizing and verifying signatures

This adds additional states to the SigningSession state machine
which encapsulate the 2nd round of signing, and the final state
after signing is completed. Once all signatures have been received,
the Ticketed DLC is active and enforceable. Players can begin
buying ticket preimages from the market maker.
This commit is contained in:
conduition
2024-03-06 00:17:43 +00:00
parent d0940e0024
commit 482521a1e9
7 changed files with 421 additions and 112 deletions

15
Cargo.lock generated
View File

@@ -95,6 +95,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
@@ -149,6 +150,15 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]]
name = "libc"
version = "0.2.151"
@@ -157,11 +167,12 @@ checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
[[package]]
name = "musig2"
version = "0.0.4"
version = "0.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f43e7abc6e724a2c8caf0e558ab838d71e70826f98df85da6a607243efbc83f"
checksum = "d7453c75681e62712bcd3e3f9e630df0bc940037fdcded8d4839e3cc9bfc947a"
dependencies = [
"base16ct",
"hmac",
"once_cell",
"rand",
"secp",

View File

@@ -12,7 +12,7 @@ keywords = ["dlc", "smart", "contract", "ticket", "auction"]
[dependencies]
bitcoin = { version = "0.31.1" }
hex = "0.4.3"
musig2 = { version = "0.0.4", features = ["rand"] }
musig2 = { version = "0.0.7", features = ["rand"] }
rand = "0.8.5"
secp = { version = "0.2.1" }
secp256k1 = { version = "0.28.2", features = ["global-context"] }
@@ -20,3 +20,4 @@ sha2 = "0.10.8"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

View File

@@ -177,26 +177,34 @@ impl ContractParameters {
Some(sigmap)
}
/// Returns an empty sigmap covering every outcome and every win condition.
/// This encompasses every possible message whose signatures are needed
/// to set up the contract.
pub fn full_sigmap(&self) -> SigMap<()> {
let mut all_win_conditions = BTreeMap::new();
/// Return a full set of all possible win conditions for this DLC.
pub fn all_win_conditions(&self) -> BTreeSet<WinCondition> {
let mut all_win_conditions = BTreeSet::new();
for (&outcome, payout_map) in self.outcome_payouts.iter() {
all_win_conditions.extend(
payout_map
.keys()
.map(|&winner| (WinCondition { winner, outcome }, ())),
.map(|&winner| WinCondition { winner, outcome }),
);
}
all_win_conditions
}
/// Returns an empty sigmap covering every outcome and every win condition.
/// This encompasses every possible message whose signatures are needed
/// to set up the contract.
pub fn full_sigmap(&self) -> SigMap<()> {
SigMap {
by_outcome: self
.outcome_payouts
.iter()
.map(|(&outcome, _)| (outcome, ()))
.collect(),
by_win_condition: all_win_conditions,
by_win_condition: self
.all_win_conditions()
.into_iter()
.map(|win_cond| (win_cond, ()))
.collect(),
}
}
}

View File

@@ -1,13 +1,15 @@
use bitcoin::{absolute::LockTime, OutPoint, Sequence, Transaction, TxIn, TxOut};
use musig2::{AdaptorSignature, AggNonce, CompactSignature, PartialSignature, PubNonce, SecNonce};
use secp::Scalar;
use musig2::{
AdaptorSignature, AggNonce, BatchVerificationRow, CompactSignature, PartialSignature, PubNonce,
SecNonce,
};
use secp::{Point, Scalar};
use std::collections::BTreeMap;
use crate::{
contract::{ContractParameters, Outcome},
errors::Error,
parties::Player,
spend_info::{FundingSpendInfo, OutcomeSpendInfo},
};
@@ -36,15 +38,6 @@ impl OutcomeTransactionBuildOutput {
pub(crate) fn funding_spend_info(&self) -> &FundingSpendInfo {
&self.funding_spend_info
}
/// Returns the number of [`musig2`] partial signatures required by each player
/// in the DLC (and the market maker).
///
/// If the contract has an expiry payout condition, this is simply the number
/// of outcomes plus one. Otherwise it is the number of outcomes exactly.
pub(crate) fn signatures_required(&self) -> usize {
self.outcome_txs.len()
}
}
/// Construct a set of unsigned outcome transactions which spend from the funding TX.
@@ -111,10 +104,7 @@ pub(crate) fn build_outcome_txs(
}
/// Construct a set of partial signatures for the outcome transactions.
///
/// The number of signatures and nonces required can be computed by using
/// checking the length of [`OutcomeTransactionBuildOutput::signatures_required`].
pub(crate) fn partial_sign_outcome_txs<'a>(
pub(crate) fn partial_sign_outcome_txs(
params: &ContractParameters,
outcome_build_out: &OutcomeTransactionBuildOutput,
seckey: Scalar,
@@ -173,28 +163,21 @@ pub(crate) fn partial_sign_outcome_txs<'a>(
}
/// Verify a player's partial adaptor signatures on the outcome transactions.
///
/// The number of signatures and nonces required can be computed by using
/// checking the length of [`OutcomeTransactionBuildOutput::signatures_required`].
pub(crate) fn verify_outcome_tx_partial_signatures<'p, 'a>(
pub(crate) fn verify_outcome_tx_partial_signatures(
params: &ContractParameters,
outcome_build_out: &OutcomeTransactionBuildOutput,
player: &Player,
pubnonces: impl IntoIterator<Item = &'p PubNonce>,
aggnonces: impl IntoIterator<Item = &'a AggNonce>,
partial_signatures: impl IntoIterator<Item = PartialSignature>,
signer_pubkey: Point,
pubnonces: &BTreeMap<Outcome, PubNonce>,
aggnonces: &BTreeMap<Outcome, AggNonce>,
partial_signatures: &BTreeMap<Outcome, PartialSignature>,
) -> Result<(), Error> {
let outcome_txs = &outcome_build_out.outcome_txs;
let funding_spend_info = &outcome_build_out.funding_spend_info;
let mut aggnonce_iter = aggnonces.into_iter();
let mut pubnonce_iter = pubnonces.into_iter();
let mut partial_sig_iter = partial_signatures.into_iter();
for (&outcome, outcome_tx) in outcome_txs {
let aggnonce = aggnonce_iter.next().ok_or(Error)?; // must provide enough aggnonces
let pubnonce = pubnonce_iter.next().ok_or(Error)?; // must provide enough pubnonces
let partial_sig = partial_sig_iter.next().ok_or(Error)?; // must provide enough sigs
let aggnonce = aggnonces.get(&outcome).ok_or(Error)?; // must provide all aggnonces
let pubnonce = pubnonces.get(&outcome).ok_or(Error)?; // must provide all pubnonces
let &partial_sig = partial_signatures.get(&outcome).ok_or(Error)?; // must provide all sigs
// Hash the outcome TX.
let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?;
@@ -212,7 +195,7 @@ pub(crate) fn verify_outcome_tx_partial_signatures<'p, 'a>(
partial_sig,
aggnonce,
attestation_lock_point,
player.pubkey,
signer_pubkey,
pubnonce,
sighash,
)?;
@@ -223,7 +206,7 @@ pub(crate) fn verify_outcome_tx_partial_signatures<'p, 'a>(
funding_spend_info.key_agg_ctx(),
partial_sig,
aggnonce,
player.pubkey,
signer_pubkey,
pubnonce,
sighash,
)?;
@@ -240,7 +223,7 @@ pub(crate) fn verify_outcome_tx_partial_signatures<'p, 'a>(
pub(crate) struct OutcomeSignatures {
/// A set of adaptor signatures which can be unlocked by the oracle's attestation
/// for each outcome.
pub(crate) adaptor_signatures: Vec<AdaptorSignature>,
pub(crate) outcome_tx_signatures: Vec<AdaptorSignature>,
/// The complete signature on the expiry transaction. This is `None` if the
/// [`ContractParameters::outcome_payouts`] field does not contain an
@@ -257,11 +240,11 @@ pub(crate) struct OutcomeSignatures {
/// If all partial signatures are valid, then aggregation succeeds and this
/// function outputs a set of adaptor signatures which are valid once adapted
/// with the oracle's attestation.
pub(crate) fn aggregate_outcome_tx_adaptor_signatures<'a, S>(
pub(crate) fn aggregate_outcome_tx_adaptor_signatures<S>(
params: &ContractParameters,
outcome_build_out: &OutcomeTransactionBuildOutput,
aggnonces: impl IntoIterator<Item = &'a AggNonce>,
partial_signature_groups: impl IntoIterator<Item = S>,
aggnonces: &BTreeMap<Outcome, AggNonce>,
mut partial_signature_groups: BTreeMap<Outcome, S>,
) -> Result<OutcomeSignatures, Error>
where
S: IntoIterator<Item = PartialSignature>,
@@ -269,19 +252,17 @@ where
let outcome_txs = &outcome_build_out.outcome_txs;
let funding_spend_info = &outcome_build_out.funding_spend_info;
let mut aggnonce_iter = aggnonces.into_iter();
let mut partial_sig_group_iter = partial_signature_groups.into_iter();
let mut signatures = OutcomeSignatures {
adaptor_signatures: Vec::with_capacity(params.event.outcome_messages.len()),
outcome_tx_signatures: Vec::with_capacity(params.event.outcome_messages.len()),
expiry_tx_signature: None,
};
for (&outcome, outcome_tx) in outcome_txs {
// must provide a set of sigs for each TX
let partial_sigs = partial_sig_group_iter.next().ok_or(Error)?;
let partial_sigs = partial_signature_groups.remove(&outcome).ok_or(Error)?;
let aggnonce = aggnonce_iter.next().ok_or(Error)?; // must provide enough aggnonces
// must provide all aggnonces
let aggnonce = aggnonces.get(&outcome).ok_or(Error)?;
// Hash the outcome TX.
let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?;
@@ -301,7 +282,7 @@ where
sighash,
)?;
signatures.adaptor_signatures.push(adaptor_sig);
signatures.outcome_tx_signatures.push(adaptor_sig);
}
Outcome::Expiry => {
@@ -320,6 +301,66 @@ where
Ok(signatures)
}
/// Verify the set of complete aggregated signatures on the
/// outcome and expiry transactions.
pub(crate) fn verify_outcome_tx_aggregated_signatures(
params: &ContractParameters,
outcome_build_out: &OutcomeTransactionBuildOutput,
outcome_tx_signatures: &[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();
// Construct a batch for efficient mass signature verification.
let batch: Vec<BatchVerificationRow> = outcome_build_out
.outcome_txs
.iter()
.map(|(outcome, outcome_tx)| {
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) => {
let adaptor_point = params
.event
.attestation_lock_point(outcome_index)
.ok_or(Error)?;
let &signature = outcome_tx_signatures.get(outcome_index).ok_or(Error)?;
BatchVerificationRow::from_adaptor_signature(
joint_pubkey,
sighash,
signature,
adaptor_point,
)
}
// One signature for the optional expiry transaction.
Outcome::Expiry => {
let signature = expiry_tx_signature.ok_or(Error)?.lift_nonce()?;
BatchVerificationRow::from_signature(joint_pubkey, sighash, signature)
}
};
Ok(batch_row)
})
.collect::<Result<_, Error>>()?;
// Verify all outcome signatures at once.
musig2::verify_batch(&batch)?;
Ok(())
}
/// Construct an input to spend an outcome transaction for a specific outcome.
/// Also returns a reference to the outcome TX's output so it can be used
/// to construct a set of [`bitcoin::sighash::Prevouts`].

View File

@@ -1,6 +1,8 @@
use bitcoin::{absolute::LockTime, Amount, OutPoint, Sequence, Transaction, TxIn, TxOut};
use musig2::{AggNonce, CompactSignature, PartialSignature, PubNonce, SecNonce};
use secp::Scalar;
use musig2::{
AggNonce, BatchVerificationRow, CompactSignature, PartialSignature, PubNonce, SecNonce,
};
use secp::{Point, Scalar};
use crate::{
consts::{P2TR_DUST_VALUE, P2TR_SCRIPT_PUBKEY_SIZE},
@@ -11,7 +13,7 @@ use crate::{
spend_info::SplitSpendInfo,
};
use std::{borrow::Borrow, collections::BTreeMap};
use std::collections::BTreeMap;
/// Represents the output of building the set of split transactions.
/// This contains cached data used for constructing further transactions,
@@ -110,7 +112,7 @@ pub(crate) fn build_split_txs(
///
/// The market maker must sign every split script spending path of every
/// split transaction.
pub(crate) fn partial_sign_split_txs<'a>(
pub(crate) fn partial_sign_split_txs(
params: &ContractParameters,
outcome_build_out: &OutcomeTransactionBuildOutput,
split_build_out: &SplitTransactionBuildOutput,
@@ -163,7 +165,7 @@ pub(crate) fn partial_sign_split_txs<'a>(
Ok(partial_signatures)
}
/// Verify the partial signatures provided by a signer across every split transaction.
/// Verify the partial signatures provided by a signer across every relevant split transaction.
///
/// Since each split transaction may require multiple signatures (one for each [`WinCondition`]),
/// the nonces and signatures must be provided in the form of a [`BTreeMap`], which maps
@@ -173,13 +175,13 @@ pub(crate) fn verify_split_tx_partial_signatures(
params: &ContractParameters,
outcome_build_out: &OutcomeTransactionBuildOutput,
split_build_out: &SplitTransactionBuildOutput,
player: &Player,
signer_pubkey: Point,
pubnonces: &BTreeMap<WinCondition, PubNonce>,
aggnonces: &BTreeMap<WinCondition, AggNonce>,
partial_signatures: &BTreeMap<WinCondition, PartialSignature>,
) -> Result<(), Error> {
let win_conditions_to_sign = params
.win_conditions_controlled_by_pubkey(player.pubkey)
.win_conditions_controlled_by_pubkey(signer_pubkey)
.ok_or(Error)?;
for win_cond in win_conditions_to_sign {
@@ -205,7 +207,7 @@ pub(crate) fn verify_split_tx_partial_signatures(
outcome_spend_info.key_agg_ctx_untweaked(),
partial_sig,
aggnonce,
player.pubkey,
signer_pubkey,
pubnonce,
sighash,
)?;
@@ -220,15 +222,14 @@ pub(crate) fn verify_split_tx_partial_signatures(
/// the nonces and signatures must be provided in the form of a [`BTreeMap`], which maps
/// a [`WinCondition`] to the appropriate signature for that script spending path.
/// If any signatures or nonces are missing, this method returns an error.
pub(crate) fn aggregate_split_tx_signatures<'s, S, P>(
pub(crate) fn aggregate_split_tx_signatures<S>(
outcome_build_out: &OutcomeTransactionBuildOutput,
split_build_out: &SplitTransactionBuildOutput,
aggnonces: &BTreeMap<WinCondition, AggNonce>,
partial_signatures_by_win_cond: &'s BTreeMap<WinCondition, S>,
mut partial_signatures_by_win_cond: BTreeMap<WinCondition, S>,
) -> Result<BTreeMap<WinCondition, CompactSignature>, Error>
where
&'s S: IntoIterator<Item = P>,
P: Borrow<PartialSignature>,
S: IntoIterator<Item = PartialSignature>,
{
split_build_out
.split_spend_infos
@@ -240,10 +241,8 @@ where
.ok_or(Error)?;
let relevant_partial_sigs = partial_signatures_by_win_cond
.get(&win_cond)
.ok_or(Error)?
.into_iter()
.map(|sig| sig.borrow().clone());
.remove(&win_cond)
.ok_or(Error)?;
let aggnonce = aggnonces.get(&win_cond).ok_or(Error)?;
@@ -267,6 +266,58 @@ where
.collect()
}
/// Verify the set of complete aggregated signatures on the split transactions.
pub(crate) fn verify_split_tx_aggregated_signatures(
params: &ContractParameters,
outcome_build_out: &OutcomeTransactionBuildOutput,
split_build_out: &SplitTransactionBuildOutput,
split_tx_signatures: &BTreeMap<WinCondition, CompactSignature>,
) -> Result<(), Error> {
let all_win_conditions = params.all_win_conditions();
// One signature per win condition.
if split_tx_signatures.len() != all_win_conditions.len() {
return Err(Error);
}
let batch: Vec<BatchVerificationRow> = all_win_conditions
.into_iter()
.map(|win_cond| {
let split_tx = split_build_out
.split_txs()
.get(&win_cond.outcome)
.ok_or(Error)?;
let signature = split_tx_signatures.get(&win_cond).ok_or(Error)?;
let outcome_spend_info = outcome_build_out
.outcome_spend_infos()
.get(&win_cond.outcome)
.ok_or(Error)?;
// Expect an untweaked signature by the group, so that the signature
// can be used to trigger one of the players' split tapscripts.
let winners_joint_pubkey: Point = outcome_spend_info
.key_agg_ctx_untweaked()
.aggregated_pubkey();
let sighash = outcome_spend_info.sighash_tx_split(split_tx, &win_cond.winner)?;
let batch_row = BatchVerificationRow::from_signature(
winners_joint_pubkey,
sighash,
signature.lift_nonce()?,
);
Ok(batch_row)
})
.collect::<Result<_, Error>>()?;
musig2::verify_batch(&batch)?;
Ok(())
}
/// Construct an input to spend a given player's output of the split transaction
/// for a specific outcome. Also returns a reference to the split TX's output so
/// it can be used to construct a set of [`bitcoin::sighash::Prevouts`].

View File

@@ -34,6 +34,12 @@ impl From<musig2::errors::SigningError> for Error {
}
}
impl From<secp::errors::InvalidPointBytes> for Error {
fn from(_: secp::errors::InvalidPointBytes) -> Self {
Error
}
}
impl From<bitcoin::taproot::TaprootBuilderError> for Error {
fn from(_: bitcoin::taproot::TaprootBuilderError) -> Self {
Error

View File

@@ -13,15 +13,15 @@ pub use secp;
pub use parties::{MarketMaker, Player};
use contract::outcome::{
build_outcome_txs, partial_sign_outcome_txs, OutcomeTransactionBuildOutput,
use contract::{
outcome::{OutcomeSignatures, OutcomeTransactionBuildOutput},
split::SplitTransactionBuildOutput,
ContractParameters, SigMap, WinCondition,
};
use contract::split::{build_split_txs, partial_sign_split_txs, SplitTransactionBuildOutput};
use contract::{ContractParameters, SigMap};
use errors::Error;
use bitcoin::{OutPoint, TxOut};
use musig2::{AggNonce, PartialSignature, PubNonce, SecNonce};
use musig2::{AdaptorSignature, AggNonce, CompactSignature, PartialSignature, PubNonce, SecNonce};
use secp::{Point, Scalar};
use std::collections::BTreeMap;
@@ -40,8 +40,8 @@ impl TicketedDLC {
params: ContractParameters,
funding_outpoint: OutPoint,
) -> Result<TicketedDLC, Error> {
let outcome_tx_build = build_outcome_txs(&params, funding_outpoint)?;
let split_tx_build = build_split_txs(&params, &outcome_tx_build)?;
let outcome_tx_build = contract::outcome::build_outcome_txs(&params, funding_outpoint)?;
let split_tx_build = contract::split::build_split_txs(&params, &outcome_tx_build)?;
let txs = TicketedDLC {
params,
@@ -85,12 +85,37 @@ pub struct PartialSignatureSharingRound {
our_partial_signatures: SigMap<PartialSignature>,
}
#[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::outcomes`]. 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>,
/// 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>,
}
/// A [`SigningSessionState`] used for a complete signing session once
/// all signatures on the [`TicketedDLC`] have been aggregated and verified
/// successfully.
pub struct CompleteState {
signatures: ContractSignatures,
}
impl SigningSessionState for NonceSharingRound {}
impl SigningSessionState for PartialSignatureSharingRound {}
impl SigningSessionState for CompleteState {}
/// This is a state machine to manage signing the various transactions in a [`TicketedDLC`].
pub struct SigningSession<S: SigningSessionState> {
dlc: TicketedDLC,
our_public_key: Point,
state: S,
}
@@ -99,20 +124,24 @@ impl<S: SigningSessionState> SigningSession<S> {
pub fn dlc(&self) -> &TicketedDLC {
&self.dlc
}
/// Return the public key of our signer.
pub fn our_public_key(&self) -> Point {
self.our_public_key
}
}
impl SigningSession<NonceSharingRound> {
/// Start a new signing session on the given TicketedDLC.
pub fn new<R: rand::RngCore + rand::CryptoRng>(
dlc: TicketedDLC,
mut rng: &mut R,
signing_key: impl Into<Scalar>,
) -> Result<SigningSession<NonceSharingRound>, Error> {
let signing_key = signing_key.into();
let our_public_key = signing_key.base_point_mul();
let base_sigmap = dlc
.params
.sigmap_for_pubkey(signing_key.base_point_mul())
.ok_or(Error)?;
let base_sigmap = dlc.params.sigmap_for_pubkey(our_public_key).ok_or(Error)?;
let our_secret_nonces = base_sigmap.map_values(|_| {
SecNonce::build(&mut rng)
@@ -127,6 +156,7 @@ impl SigningSession<NonceSharingRound> {
let session = SigningSession {
dlc,
our_public_key,
state: NonceSharingRound {
signing_key: signing_key.into(),
our_secret_nonces,
@@ -136,45 +166,22 @@ impl SigningSession<NonceSharingRound> {
Ok(session)
}
/// The public nonces we should send to other signers.
/// The public nonces we should broadcast to other signers.
pub fn our_public_nonces(&self) -> &SigMap<PubNonce> {
&self.state.our_public_nonces
}
/// Receive the nonces from all other signers.
pub fn compute_all_signatures(
/// 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(
self,
mut received_nonces: BTreeMap<Point, SigMap<PubNonce>>,
) -> Result<SigningSession<PartialSignatureSharingRound>, Error> {
// Insert our own public nonces so that callers don't need
// to inject them manually.
received_nonces.insert(
self.state.signing_key.base_point_mul(),
self.state.our_public_nonces,
);
received_nonces.insert(self.our_public_key, self.state.our_public_nonces);
// Must receive nonces from all players and the market maker.
if !received_nonces.contains_key(&self.dlc.params.market_maker.pubkey) {
return Err(Error);
}
for player in self.dlc.params.players.iter() {
if !received_nonces.contains_key(&player.pubkey) {
return Err(Error);
}
}
// The expected sigmaps each signer must provide nonces for.
let base_sigmaps: BTreeMap<Point, SigMap<()>> = received_nonces
.keys()
.map(|&key| Ok((key, self.dlc.params.sigmap_for_pubkey(key).ok_or(Error)?)))
.collect::<Result<_, Error>>()?;
for (&signer_pubkey, nonces) in received_nonces.iter() {
// All signers' sigmaps must match exactly.
if !nonces.is_mirror(&base_sigmaps[&signer_pubkey]) {
return Err(Error);
}
}
validate_sigmaps_completeness(&self.dlc.params, &received_nonces)?;
let aggregated_nonces: SigMap<AggNonce> = self.dlc.params.full_sigmap().map(
|outcome, _| {
@@ -192,14 +199,14 @@ impl SigningSession<NonceSharingRound> {
);
let our_partial_signatures = SigMap {
by_outcome: partial_sign_outcome_txs(
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: partial_sign_split_txs(
by_win_condition: contract::split::partial_sign_split_txs(
&self.dlc.params,
&self.dlc.outcome_tx_build,
&self.dlc.split_tx_build,
@@ -211,6 +218,7 @@ impl SigningSession<NonceSharingRound> {
let session = SigningSession {
dlc: self.dlc,
our_public_key: self.our_public_key,
state: PartialSignatureSharingRound {
received_nonces,
aggregated_nonces,
@@ -220,3 +228,186 @@ impl SigningSession<NonceSharingRound> {
Ok(session)
}
}
impl SigningSession<PartialSignatureSharingRound> {
/// 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
}
/// Verify the signatures received from a particular signer. Returns an
/// error if any signatures are missing, or if any signatures are not
/// correct.
pub fn verify_partial_signatures(
&self,
signer_pubkey: Point,
partial_signatures: &SigMap<PartialSignature>,
) -> Result<(), Error> {
let signer_nonces = self
.state
.received_nonces
.get(&signer_pubkey)
.ok_or(Error)?;
contract::outcome::verify_outcome_tx_partial_signatures(
&self.dlc.params,
&self.dlc.outcome_tx_build,
signer_pubkey,
&signer_nonces.by_outcome,
&self.state.aggregated_nonces.by_outcome,
&partial_signatures.by_outcome,
)?;
contract::split::verify_split_tx_partial_signatures(
&self.dlc.params,
&self.dlc.outcome_tx_build,
&self.dlc.split_tx_build,
signer_pubkey,
&signer_nonces.by_win_condition,
&self.state.aggregated_nonces.by_win_condition,
&partial_signatures.by_win_condition,
)?;
Ok(())
}
/// 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,
mut received_signatures: BTreeMap<Point, SigMap<PartialSignature>>,
) -> Result<SigningSession<CompleteState>, Error> {
// Insert our own signatures so that callers don't need to inject them manually.
received_signatures.insert(self.our_public_key, self.state.our_partial_signatures);
validate_sigmaps_completeness(&self.dlc.params, &received_signatures)?;
let full_sigmap = self.dlc.params.full_sigmap();
// We were given a map of the partial signatures made by each signer. We
// restructure them to map outcomes and win conditions into the full set of
// partial signatures which should be aggregated per sighash.
//
// For each sighash we need to sign...
let partial_signature_sets: SigMap<Vec<PartialSignature>> = full_sigmap.map(
// Collect all the partial signatures needed for each outcome
|outcome, _| {
received_signatures
.values()
.filter_map(|sig_sigmap| sig_sigmap.by_outcome.get(&outcome).copied())
.collect::<Vec<PartialSignature>>()
},
// Collect all the partial signatures needed for each win condition
|win_cond, _| {
received_signatures
.values()
.filter_map(|sig_sigmap| sig_sigmap.by_win_condition.get(&win_cond).copied())
.collect::<Vec<PartialSignature>>()
},
);
// Aggregate all the outcome TX signatures.
let OutcomeSignatures {
outcome_tx_signatures,
expiry_tx_signature,
} = contract::outcome::aggregate_outcome_tx_adaptor_signatures(
&self.dlc.params,
&self.dlc.outcome_tx_build,
&self.state.aggregated_nonces.by_outcome,
partial_signature_sets.by_outcome,
)?;
// Aggregate all the split TX signatures.
let split_tx_signatures = contract::split::aggregate_split_tx_signatures(
&self.dlc.outcome_tx_build,
&self.dlc.split_tx_build,
&self.state.aggregated_nonces.by_win_condition,
partial_signature_sets.by_win_condition,
)?;
// Signing complete! Just have to send `signatures` to our peers.
let complete_session = SigningSession {
dlc: self.dlc,
our_public_key: self.our_public_key,
state: CompleteState {
signatures: ContractSignatures {
expiry_tx_signature,
outcome_tx_signatures,
split_tx_signatures,
},
},
};
Ok(complete_session)
}
/// Verifies the complete set of contract signatures were aggregated correctly by a peer.
///
/// This completes the signing session.
pub fn verify_aggregated_signatures(
&self,
signatures: &ContractSignatures,
) -> Result<(), Error> {
contract::outcome::verify_outcome_tx_aggregated_signatures(
&self.dlc.params,
&self.dlc.outcome_tx_build,
&signatures.outcome_tx_signatures,
signatures.expiry_tx_signature,
)?;
contract::split::verify_split_tx_aggregated_signatures(
&self.dlc.params,
&self.dlc.outcome_tx_build,
&self.dlc.split_tx_build,
&signatures.split_tx_signatures,
)?;
Ok(())
}
}
/// This validates a set of sigmaps received from untrusted peers. Ensures
/// each sigmap contains a full set, matching the expected sigmap for the sender.
fn validate_sigmaps_completeness<T>(
params: &ContractParameters,
received_maps: &BTreeMap<Point, SigMap<T>>,
) -> Result<(), Error> {
// Must receive signatures/nonces from all players and the market maker.
if !received_maps.contains_key(&params.market_maker.pubkey) {
return Err(Error);
}
for player in params.players.iter() {
if !received_maps.contains_key(&player.pubkey) {
return Err(Error);
}
}
for (&signer_pubkey, sigmap) in received_maps.iter() {
// The expected sigmap each signer must provide nonces/signatures for.
let base_sigmap = params.sigmap_for_pubkey(signer_pubkey).ok_or(Error)?;
// All signers' sigmaps must match exactly.
if !sigmap.is_mirror(&base_sigmap) {
return Err(Error);
}
}
Ok(())
}
impl SigningSession<CompleteState> {
pub fn signatures(&self) -> &ContractSignatures {
&self.state.signatures
}
}