#![warn(missing_docs)]
#[cfg(test)]
mod regtest;
pub(crate) mod consts;
pub(crate) mod contract;
pub(crate) mod errors;
pub(crate) mod oracles;
pub(crate) mod parties;
pub(crate) mod serialization;
pub(crate) mod spend_info;
pub mod hashlock;
pub use bitcoin;
pub use musig2;
pub use secp;
use contract::{
outcome::{OutcomeSignatures, OutcomeTransactionBuildOutput},
split::SplitTransactionBuildOutput,
};
use errors::Error;
use hashlock::{sha256, Preimage};
use bitcoin::{
secp256k1::XOnlyPublicKey as BitcoinXOnly, sighash::Prevouts,
transaction::InputWeightPrediction, OutPoint, Transaction, TxIn, TxOut,
};
use musig2::{
secp256k1::XOnlyPublicKey as Musig2XOnly, AdaptorSignature, AggNonce, CompactSignature,
PartialSignature, PubNonce, SecNonce,
};
use secp::{MaybeScalar, Point, Scalar};
use serde::{Deserialize, Serialize};
pub use contract::{
ContractParameters, Outcome, OutcomeIndex, PayoutWeights, PlayerIndex, SigMap, WinCondition,
};
pub use oracles::{attestation_locking_point, attestation_secret, EventLockingConditions};
pub use parties::{MarketMaker, Player};
use std::{
borrow::Borrow,
collections::{BTreeMap, BTreeSet},
};
pub fn convert_xonly_key(key: Musig2XOnly) -> BitcoinXOnly {
BitcoinXOnly::from_slice(&key.serialize()).expect("Valid key")
}
pub fn convert_point(key: Point) -> BitcoinXOnly {
BitcoinXOnly::from_slice(&key.serialize_xonly()).expect("Valid key")
}
#[derive(Clone, Eq, PartialEq)]
pub struct TicketedDLC {
params: ContractParameters,
funding_outpoint: OutPoint,
outcome_tx_build: OutcomeTransactionBuildOutput,
split_tx_build: SplitTransactionBuildOutput,
}
impl TicketedDLC {
pub fn new(
params: ContractParameters,
funding_outpoint: OutPoint,
) -> Result<TicketedDLC, Error> {
params.validate()?;
let outcome_tx_build = contract::outcome::build_outcome_txs(¶ms, funding_outpoint)?;
let split_tx_build = contract::split::build_split_txs(¶ms, &outcome_tx_build)?;
let dlc = TicketedDLC {
params,
funding_outpoint,
outcome_tx_build,
split_tx_build,
};
Ok(dlc)
}
pub fn params(&self) -> &ContractParameters {
&self.params
}
pub fn funding_outpoint(&self) -> OutPoint {
self.funding_outpoint
}
pub fn funding_output(&self) -> TxOut {
TxOut {
script_pubkey: self.outcome_tx_build.funding_spend_info().script_pubkey(),
value: self.params.funding_value,
}
}
}
impl std::fmt::Debug for TicketedDLC {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TicketedDLC")
.field("params", self.params())
.field("funding_outpoint", &self.funding_outpoint)
.finish()
}
}
pub trait SigningSessionState {}
pub struct NonceSharingRound {
signing_key: Scalar,
our_secret_nonces: SigMap<SecNonce>,
our_public_nonces: SigMap<PubNonce>,
}
pub struct CoordinatorPartialSignatureSharingRound {
received_nonces: BTreeMap<Point, SigMap<PubNonce>>,
aggregated_nonces: SigMap<AggNonce>,
our_partial_signatures: SigMap<PartialSignature>,
}
pub struct ContributorPartialSignatureSharingRound {
aggregated_nonces: SigMap<AggNonce>,
our_partial_signatures: SigMap<PartialSignature>,
}
impl SigningSessionState for NonceSharingRound {}
impl SigningSessionState for CoordinatorPartialSignatureSharingRound {}
impl SigningSessionState for ContributorPartialSignatureSharingRound {}
pub struct SigningSession<S: SigningSessionState> {
dlc: TicketedDLC,
our_public_key: Point,
state: S,
}
impl<S: SigningSessionState> SigningSession<S> {
pub fn dlc(&self) -> &TicketedDLC {
&self.dlc
}
pub fn our_public_key(&self) -> Point {
self.our_public_key
}
}
impl SigningSession<NonceSharingRound> {
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(our_public_key)
.ok_or(Error::InvalidKey)?;
let our_secret_nonces =
base_sigmap.map_values(|_| SecNonce::build(&mut rng).with_seckey(signing_key).build());
let our_public_nonces = our_secret_nonces
.by_ref()
.map_values(|secnonce| secnonce.public_nonce());
let session = SigningSession {
dlc,
our_public_key,
state: NonceSharingRound {
signing_key: signing_key.into(),
our_secret_nonces,
our_public_nonces,
},
};
Ok(session)
}
pub fn our_public_nonces(&self) -> &SigMap<PubNonce> {
&self.state.our_public_nonces
}
pub fn aggregate_nonces_and_compute_partial_signatures(
self,
mut received_nonces: BTreeMap<Point, SigMap<PubNonce>>,
) -> Result<SigningSession<CoordinatorPartialSignatureSharingRound>, Error> {
received_nonces.insert(self.our_public_key, self.state.our_public_nonces);
validate_sigmaps_completeness(&self.dlc.params, &received_nonces)?;
let aggregated_nonces = self.dlc.params.full_sigmap().map(
|outcome, _| {
received_nonces
.values()
.filter_map(|nonce_sigmap| nonce_sigmap.by_outcome.get(&outcome))
.sum::<AggNonce>()
},
|win_cond, _| {
received_nonces
.values()
.filter_map(|nonce_sigmap| nonce_sigmap.by_win_condition.get(&win_cond))
.sum::<AggNonce>()
},
);
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 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)
}
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: ContributorPartialSignatureSharingRound {
aggregated_nonces,
our_partial_signatures,
},
};
Ok(session)
}
}
impl SigningSession<CoordinatorPartialSignatureSharingRound> {
pub fn our_partial_signatures(&self) -> &SigMap<PartialSignature> {
&self.state.our_partial_signatures
}
pub fn aggregated_nonces(&self) -> &SigMap<AggNonce> {
&self.state.aggregated_nonces
}
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::MissingNonce(format!(
"no nonces received from signer with pubkey {:?}",
signer_pubkey
)))?;
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(())
}
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,
},
}
}
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> {
pub fn our_partial_signatures(&self) -> &SigMap<PartialSignature> {
&self.state.our_partial_signatures
}
pub fn aggregated_nonces(&self) -> &SigMap<AggNonce> {
&self.state.aggregated_nonces
}
pub fn aggregate_all_signatures(
self,
mut received_signatures: BTreeMap<Point, SigMap<PartialSignature>>,
) -> Result<SignedContract, Error> {
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();
let partial_signature_sets: SigMap<Vec<PartialSignature>> = full_sigmap.map(
|outcome, _| {
received_signatures
.values()
.filter_map(|sig_sigmap| sig_sigmap.by_outcome.get(&outcome).copied())
.collect::<Vec<PartialSignature>>()
},
|win_cond, _| {
received_signatures
.values()
.filter_map(|sig_sigmap| sig_sigmap.by_win_condition.get(&win_cond).copied())
.collect::<Vec<PartialSignature>>()
},
);
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,
)?;
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,
)?;
let signed_contract = SignedContract {
dlc: self.dlc,
signatures: ContractSignatures {
expiry_tx_signature,
outcome_tx_signatures,
split_tx_signatures,
},
};
Ok(signed_contract)
}
pub fn verify_aggregated_signatures(
&self,
signatures: &ContractSignatures,
) -> 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,
)?;
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,
)?;
Ok(())
}
pub fn into_signed_contract(self, signatures: ContractSignatures) -> SignedContract {
SignedContract {
signatures,
dlc: self.dlc,
}
}
}
fn validate_sigmaps_completeness<T>(
params: &ContractParameters,
received_maps: &BTreeMap<Point, SigMap<T>>,
) -> Result<(), Error> {
if !received_maps.contains_key(¶ms.market_maker.pubkey) {
return Err(Error::MissingSignature(format!(
"market makert pubkey: {}",
params.market_maker.pubkey
)));
}
for player in params.players.iter() {
if !received_maps.contains_key(&player.pubkey) {
return Err(Error::MissingSignature(format!(
"player pubkey: {}",
player.pubkey
)));
}
}
for (&signer_pubkey, sigmap) in received_maps.iter() {
let base_sigmap = params
.sigmap_for_pubkey(signer_pubkey)
.ok_or(Error::InvalidKey)?;
if !sigmap.is_mirror(&base_sigmap) {
return Err(Error::InvalidSignature);
}
}
Ok(())
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ContractSignatures {
pub expiry_tx_signature: Option<CompactSignature>,
pub outcome_tx_signatures: BTreeMap<OutcomeIndex, AdaptorSignature>,
pub split_tx_signatures: BTreeMap<WinCondition, CompactSignature>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct SignedContract {
signatures: ContractSignatures,
dlc: TicketedDLC,
}
impl SignedContract {
pub fn all_signatures(&self) -> &ContractSignatures {
&self.signatures
}
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_claimable_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)
}
pub fn dlc(&self) -> &TicketedDLC {
&self.dlc
}
pub fn params(&self) -> &ContractParameters {
&self.dlc.params
}
pub fn unsigned_outcome_tx<'a>(
&'a self,
outcome_index: OutcomeIndex,
) -> Option<&'a Transaction> {
self.dlc
.outcome_tx_build
.outcome_txs()
.get(&Outcome::Attestation(outcome_index))
}
pub fn signed_outcome_tx(
&self,
outcome_index: OutcomeIndex,
attestation: impl Into<MaybeScalar>,
) -> Result<Transaction, Error> {
let attestation = attestation.into();
let locking_point = self
.dlc
.params
.event
.locking_points
.get(outcome_index)
.ok_or(Error::UnknownOutcome)?;
if &attestation.base_point_mul() != locking_point {
return Err(Error::InvalidSignature)?;
}
let mut outcome_tx = self
.unsigned_outcome_tx(outcome_index)
.ok_or(Error::UnknownOutcome)?
.clone();
let adaptor_signature = self
.signatures
.outcome_tx_signatures
.get(&outcome_index)
.ok_or(Error::MissingSignature(format!(
"adaptor signature for outcome index {}",
outcome_index
)))?;
let compact_sig: CompactSignature = adaptor_signature
.adapt(attestation)
.ok_or(Error::InvalidSignature)?;
outcome_tx.input[0].witness.push(compact_sig.serialize());
Ok(outcome_tx)
}
pub fn expiry_tx(&self) -> Option<Transaction> {
let mut expiry_tx = self
.dlc
.outcome_tx_build
.outcome_txs()
.get(&Outcome::Expiry)?
.clone();
let signature: CompactSignature = self.signatures.expiry_tx_signature?;
expiry_tx.input[0].witness.push(signature.serialize());
Some(expiry_tx)
}
pub fn unsigned_split_tx<'a>(&'a self, outcome: &Outcome) -> Option<&'a Transaction> {
self.dlc.split_tx_build.split_txs().get(outcome)
}
pub fn signed_split_tx(
&self,
win_cond: &WinCondition,
ticket_preimage: Preimage,
) -> Result<Transaction, Error> {
let winner = self
.dlc
.params
.players
.get(win_cond.player_index)
.cloned()
.ok_or(Error::OutOfBoundsPlayerIndex)?;
if sha256(&ticket_preimage) != winner.ticket_hash {
return Err(Error::InvalidInput("ticket preimage does not match hash"));
}
let signature =
self.signatures
.split_tx_signatures
.get(win_cond)
.ok_or(Error::MissingSignature(String::from(
"split tx signature for win condition",
)))?;
let outcome_spend_info = self
.dlc
.outcome_tx_build
.outcome_spend_infos()
.get(&win_cond.outcome)
.ok_or(Error::UnknownOutcome)?;
let witness = outcome_spend_info.witness_tx_split(
signature,
ticket_preimage,
&win_cond.player_index,
)?;
let mut split_tx = self
.unsigned_split_tx(&win_cond.outcome)
.ok_or(Error::UnknownOutcome)?
.clone();
split_tx.input[0].witness = witness;
Ok(split_tx)
}
pub fn funding_close_tx_input_and_prevout(&self) -> (TxIn, TxOut) {
let input = TxIn {
previous_output: self.dlc.funding_outpoint(),
..TxIn::default()
};
let prevout = self.dlc.funding_output();
(input, prevout)
}
pub fn outcome_reclaim_tx_input_and_prevout<'a>(
&'a self,
outcome: &Outcome,
) -> Result<(TxIn, &'a TxOut), Error> {
contract::outcome::outcome_tx_prevout(
&self.dlc.outcome_tx_build,
outcome,
2 * self.dlc.params.relative_locktime_block_delta,
)
}
pub fn outcome_close_tx_input_and_prevout<'a>(
&'a self,
outcome: &Outcome,
) -> Result<(TxIn, &'a TxOut), Error> {
contract::outcome::outcome_tx_prevout(&self.dlc.outcome_tx_build, outcome, 0)
}
pub fn split_win_tx_input_and_prevout<'a>(
&'a self,
win_cond: &WinCondition,
) -> Result<(TxIn, &'a TxOut), Error> {
contract::split::split_tx_prevout(
&self.dlc.params,
&self.dlc.split_tx_build,
win_cond,
self.dlc.params.relative_locktime_block_delta,
)
}
pub fn split_reclaim_tx_input_and_prevout<'a>(
&'a self,
win_cond: &WinCondition,
) -> Result<(TxIn, &'a TxOut), Error> {
contract::split::split_tx_prevout(
&self.dlc.params,
&self.dlc.split_tx_build,
win_cond,
2 * self.dlc.params.relative_locktime_block_delta,
)
}
pub fn split_sellback_tx_input_and_prevout<'a>(
&'a self,
win_cond: &WinCondition,
) -> Result<(TxIn, &'a TxOut), Error> {
contract::split::split_tx_prevout(&self.dlc.params, &self.dlc.split_tx_build, win_cond, 0)
}
pub fn split_close_tx_input_and_prevout<'a>(
&'a self,
win_cond: &WinCondition,
) -> Result<(TxIn, &'a TxOut), Error> {
contract::split::split_tx_prevout(&self.dlc.params, &self.dlc.split_tx_build, win_cond, 0)
}
pub fn outcome_reclaim_tx_input_weight(
&self,
outcome: &Outcome,
) -> Option<InputWeightPrediction> {
self.dlc
.outcome_tx_build
.outcome_spend_infos()
.get(outcome)
.map(|outcome_spend_info| outcome_spend_info.input_weight_for_reclaim_tx())
}
pub fn split_win_tx_input_weight(&self) -> InputWeightPrediction {
self.dlc
.split_tx_build
.split_spend_infos()
.values()
.next()
.unwrap()
.input_weight_for_win_tx()
}
pub fn split_reclaim_tx_input_weight(&self) -> InputWeightPrediction {
self.dlc
.split_tx_build
.split_spend_infos()
.values()
.next()
.unwrap()
.input_weight_for_reclaim_tx()
}
pub fn split_sellback_tx_input_weight(&self) -> InputWeightPrediction {
self.dlc
.split_tx_build
.split_spend_infos()
.values()
.next()
.unwrap()
.input_weight_for_sellback_tx()
}
pub fn close_tx_input_weight(&self) -> InputWeightPrediction {
InputWeightPrediction::P2TR_KEY_DEFAULT_SIGHASH
}
pub(crate) fn unchecked_sign_outcome_reclaim_tx_input<T: Borrow<TxOut>>(
&self,
outcome: &Outcome,
reclaim_tx: &mut Transaction,
input_index: usize,
prevouts: &Prevouts<T>,
market_maker_secret_key: Scalar,
) -> Result<(), Error> {
let outcome_spend_info = self
.dlc
.outcome_tx_build
.outcome_spend_infos()
.get(outcome)
.ok_or(Error::UnknownOutcome)?;
let witness = outcome_spend_info.witness_tx_reclaim(
reclaim_tx,
input_index,
prevouts,
market_maker_secret_key,
)?;
reclaim_tx.input[input_index].witness = witness;
Ok(())
}
pub fn sign_outcome_reclaim_tx_input<T: Borrow<TxOut>>(
&self,
outcome: &Outcome,
reclaim_tx: &mut Transaction,
input_index: usize,
prevouts: &Prevouts<T>,
market_maker_secret_key: impl Into<Scalar>,
) -> Result<(), Error> {
let market_maker_secret_key = market_maker_secret_key.into();
if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey {
return Err(Error::InvalidKey);
}
let (expected_input, expected_prevout) =
self.outcome_reclaim_tx_input_and_prevout(outcome)?;
check_input_matches_expected(
reclaim_tx,
prevouts,
input_index,
&expected_input,
expected_prevout,
)?;
self.unchecked_sign_outcome_reclaim_tx_input(
outcome,
reclaim_tx,
input_index,
prevouts,
market_maker_secret_key,
)
}
pub fn sign_funding_close_tx_input<T: Borrow<TxOut>>(
&self,
close_tx: &mut Transaction,
input_index: usize,
prevouts: &Prevouts<T>,
market_maker_secret_key: impl Into<Scalar>,
player_secret_keys: &BTreeMap<Point, Scalar>,
) -> Result<(), Error> {
let market_maker_secret_key = market_maker_secret_key.into();
if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey {
return Err(Error::InvalidKey);
}
let (mut expected_input, expected_prevout) = self.funding_close_tx_input_and_prevout();
expected_input.sequence = close_tx
.input
.get(input_index)
.ok_or(Error::InvalidInput("transaction input index out of bounds"))?
.sequence;
check_input_matches_expected(
close_tx,
prevouts,
input_index,
&expected_input,
&expected_prevout,
)?;
let funding_spend_info = self.dlc.outcome_tx_build.funding_spend_info();
let witness = funding_spend_info.witness_tx_close(
close_tx,
input_index,
prevouts,
market_maker_secret_key,
player_secret_keys,
)?;
close_tx.input[input_index].witness = witness;
Ok(())
}
pub fn sign_outcome_close_tx_input<T: Borrow<TxOut>>(
&self,
outcome: &Outcome,
close_tx: &mut Transaction,
input_index: usize,
prevouts: &Prevouts<T>,
market_maker_secret_key: impl Into<Scalar>,
player_secret_keys: &BTreeMap<Point, Scalar>,
) -> Result<(), Error> {
let market_maker_secret_key = market_maker_secret_key.into();
if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey {
return Err(Error::InvalidKey);
}
let (mut expected_input, expected_prevout) =
self.outcome_close_tx_input_and_prevout(outcome)?;
expected_input.sequence = close_tx
.input
.get(input_index)
.ok_or(Error::InvalidInput("transaction input index out of bounds"))?
.sequence;
check_input_matches_expected(
close_tx,
prevouts,
input_index,
&expected_input,
expected_prevout,
)?;
let outcome_spend_info = self
.dlc
.outcome_tx_build
.outcome_spend_infos()
.get(outcome)
.ok_or(Error::UnknownOutcome)?;
let witness = outcome_spend_info.witness_tx_close(
close_tx,
input_index,
prevouts,
market_maker_secret_key,
player_secret_keys,
)?;
close_tx.input[input_index].witness = witness;
Ok(())
}
pub(crate) fn unchecked_sign_split_win_tx_input<T: Borrow<TxOut>>(
&self,
win_cond: &WinCondition,
win_tx: &mut Transaction,
input_index: usize,
prevouts: &Prevouts<T>,
ticket_preimage: Preimage,
player_secret_key: Scalar,
) -> Result<(), Error> {
let split_spend_info = self
.dlc
.split_tx_build
.split_spend_infos()
.get(win_cond)
.ok_or(Error::UnknownOutcome)?;
let witness = split_spend_info.witness_tx_win(
win_tx,
input_index,
prevouts,
ticket_preimage,
player_secret_key,
)?;
win_tx.input[input_index].witness = witness;
Ok(())
}
pub fn sign_split_win_tx_input<T: Borrow<TxOut>>(
&self,
win_cond: &WinCondition,
win_tx: &mut Transaction,
input_index: usize,
prevouts: &Prevouts<T>,
ticket_preimage: Preimage,
player_secret_key: impl Into<Scalar>,
) -> Result<(), Error> {
let winner = self
.dlc
.params
.players
.get(win_cond.player_index)
.cloned()
.ok_or(Error::OutOfBoundsPlayerIndex)?;
let player_secret_key = player_secret_key.into();
if player_secret_key.base_point_mul() != winner.pubkey {
return Err(Error::InvalidKey);
} else if sha256(&ticket_preimage) != winner.ticket_hash {
return Err(Error::InvalidInput("ticket preimage does not match hash"));
}
let (expected_input, expected_prevout) = self.split_win_tx_input_and_prevout(win_cond)?;
check_input_matches_expected(
win_tx,
prevouts,
input_index,
&expected_input,
expected_prevout,
)?;
self.unchecked_sign_split_win_tx_input(
win_cond,
win_tx,
input_index,
prevouts,
ticket_preimage,
player_secret_key,
)
}
pub(crate) fn unchecked_sign_split_reclaim_tx_input<T: Borrow<TxOut>>(
&self,
win_cond: &WinCondition,
reclaim_tx: &mut Transaction,
input_index: usize,
prevouts: &Prevouts<T>,
market_maker_secret_key: Scalar,
) -> Result<(), Error> {
let split_spend_info = self
.dlc
.split_tx_build
.split_spend_infos()
.get(win_cond)
.ok_or(Error::UnknownOutcome)?;
let witness = split_spend_info.witness_tx_reclaim(
reclaim_tx,
input_index,
prevouts,
market_maker_secret_key,
)?;
reclaim_tx.input[input_index].witness = witness;
Ok(())
}
pub fn sign_split_reclaim_tx_input<T: Borrow<TxOut>>(
&self,
win_cond: &WinCondition,
reclaim_tx: &mut Transaction,
input_index: usize,
prevouts: &Prevouts<T>,
market_maker_secret_key: impl Into<Scalar>,
) -> Result<(), Error> {
let market_maker_secret_key = market_maker_secret_key.into();
if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey {
return Err(Error::InvalidKey);
}
let (expected_input, expected_prevout) =
self.split_reclaim_tx_input_and_prevout(win_cond)?;
check_input_matches_expected(
reclaim_tx,
prevouts,
input_index,
&expected_input,
expected_prevout,
)?;
self.unchecked_sign_split_reclaim_tx_input(
win_cond,
reclaim_tx,
input_index,
prevouts,
market_maker_secret_key,
)
}
pub fn sign_split_sellback_tx_input<T: Borrow<TxOut>>(
&self,
win_cond: &WinCondition,
sellback_tx: &mut Transaction,
input_index: usize,
prevouts: &Prevouts<T>,
payout_preimage: Preimage,
market_maker_secret_key: impl Into<Scalar>,
) -> Result<(), Error> {
let market_maker_secret_key = market_maker_secret_key.into();
if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey {
return Err(Error::InvalidKey);
}
let (mut expected_input, expected_prevout) =
self.split_sellback_tx_input_and_prevout(win_cond)?;
expected_input.sequence = sellback_tx
.input
.get(input_index)
.ok_or(Error::InvalidInput("transaction input index out of bounds"))?
.sequence;
check_input_matches_expected(
sellback_tx,
prevouts,
input_index,
&expected_input,
expected_prevout,
)?;
let split_spend_info = self
.dlc
.split_tx_build
.split_spend_infos()
.get(win_cond)
.ok_or(Error::UnknownOutcome)?;
let witness = split_spend_info.witness_tx_sellback(
sellback_tx,
input_index,
prevouts,
payout_preimage,
market_maker_secret_key,
)?;
sellback_tx.input[input_index].witness = witness;
Ok(())
}
pub fn sign_split_close_tx_input<T: Borrow<TxOut>>(
&self,
win_cond: &WinCondition,
close_tx: &mut Transaction,
input_index: usize,
prevouts: &Prevouts<T>,
market_maker_secret_key: impl Into<Scalar>,
player_secret_key: impl Into<Scalar>,
) -> Result<(), Error> {
let market_maker_secret_key = market_maker_secret_key.into();
if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey {
return Err(Error::InvalidKey);
}
let (mut expected_input, expected_prevout) =
self.split_sellback_tx_input_and_prevout(win_cond)?;
expected_input.sequence = close_tx
.input
.get(input_index)
.ok_or(Error::InvalidInput("transaction input index out of bounds"))?
.sequence;
check_input_matches_expected(
close_tx,
prevouts,
input_index,
&expected_input,
expected_prevout,
)?;
let split_spend_info = self
.dlc
.split_tx_build
.split_spend_infos()
.get(win_cond)
.ok_or(Error::UnknownOutcome)?;
let witness = split_spend_info.witness_tx_close(
close_tx,
input_index,
prevouts,
market_maker_secret_key,
player_secret_key.into(),
)?;
close_tx.input[input_index].witness = witness;
Ok(())
}
}
fn check_input_matches_expected<T: Borrow<TxOut>>(
tx: &Transaction,
prevouts: &Prevouts<T>,
input_index: usize,
expected_input: &TxIn,
expected_prevout: &TxOut,
) -> Result<(), Error> {
let input = tx
.input
.get(input_index)
.ok_or(Error::InvalidInput("transaction input index out of bounds"))?;
if input != expected_input {
return Err(Error::InvalidInput("input does not match expected"));
}
let prevout = match prevouts {
Prevouts::All(all_prevouts) => all_prevouts
.get(input_index)
.ok_or(Error::InvalidInput("prevout index out of bounds"))?
.borrow(),
Prevouts::One(i, prevout) => {
if i != &input_index {
return Err(Error::InvalidInput("prevout index mismatch"));
}
prevout.borrow()
}
};
if prevout != expected_prevout {
return Err(Error::InvalidInput("prevout does not match expected"));
}
Ok(())
}