feat: adding error struct (#2)

This commit is contained in:
tee8z
2025-07-21 18:24:21 -04:00
committed by GitHub
parent 463d294835
commit 37052b5573
9 changed files with 383 additions and 142 deletions

View File

@@ -16,7 +16,7 @@ where
O: IntoIterator<Item = usize>, O: IntoIterator<Item = usize>,
{ {
let tx_weight = bitcoin::transaction::predict_weight(input_weights, output_spk_lens); let tx_weight = bitcoin::transaction::predict_weight(input_weights, output_spk_lens);
let fee = fee_rate.fee_wu(tx_weight).ok_or(Error)?; let fee = fee_rate.fee_wu(tx_weight).ok_or(Error::WeightOverflow)?;
Ok(fee) Ok(fee)
} }
@@ -29,11 +29,13 @@ pub(crate) fn fee_subtract_safe(
dust_threshold: Amount, dust_threshold: Amount,
) -> Result<Amount, Error> { ) -> Result<Amount, Error> {
if fee >= available_coins { if fee >= available_coins {
return Err(Error); return Err(Error::InsufficientFunds);
} }
let after_fee = available_coins.checked_sub(fee).ok_or(Error)?; let after_fee = available_coins
.checked_sub(fee)
.ok_or(Error::InvalidFeeAmount)?;
if after_fee <= dust_threshold { if after_fee <= dust_threshold {
return Err(Error); return Err(Error::DustAmount);
} }
Ok(after_fee) Ok(after_fee)
} }

View File

@@ -125,46 +125,46 @@ impl ContractParameters {
// This would imply the players array contains duplicate ticket hashes. // This would imply the players array contains duplicate ticket hashes.
if uniq_ticket_hashes.len() != self.players.len() { if uniq_ticket_hashes.len() != self.players.len() {
return Err(Error); return Err(Error::DuplicateTicketHash);
} }
for (outcome, payout_map) in self.outcome_payouts.iter() { for (outcome, payout_map) in self.outcome_payouts.iter() {
// Check for unknown outcomes. // Check for unknown outcomes.
if !self.event.is_valid_outcome(outcome) { if !self.event.is_valid_outcome(outcome) {
return Err(Error); return Err(Error::UnknownOutcome);
} }
// Check for empty payout map. // Check for empty payout map.
if payout_map.len() == 0 { if payout_map.len() == 0 {
return Err(Error); return Err(Error::EmptyPayoutMap);
} }
for (&player_index, &weight) in payout_map.iter() { for (&player_index, &weight) in payout_map.iter() {
// Check for zero payout weights. // Check for zero payout weights.
if weight == 0 { if weight == 0 {
return Err(Error); return Err(Error::InvalidPayoutWeight);
} }
// Check for out-of-bounds player indexes. // Check for out-of-bounds player indexes.
if player_index >= self.players.len() { if player_index >= self.players.len() {
return Err(Error); return Err(Error::OutOfBoundsPlayerIndex);
} }
} }
} }
// Must use a non-zero fee rate. // Must use a non-zero fee rate.
if self.fee_rate == FeeRate::ZERO { if self.fee_rate == FeeRate::ZERO {
return Err(Error); return Err(Error::InvalidFeeAmount);
} }
// Must use a non-zero locktime delta // Must use a non-zero locktime delta
if self.relative_locktime_block_delta == 0 { if self.relative_locktime_block_delta == 0 {
return Err(Error); return Err(Error::InvalidLocktime);
} }
// Must be funded by some fixed non-zero amount. // Must be funded by some fixed non-zero amount.
if self.funding_value < Amount::ZERO { if self.funding_value < Amount::ZERO {
return Err(Error); return Err(Error::InsufficientFunds);
} }
Ok(()) Ok(())

View File

@@ -78,7 +78,9 @@ pub(crate) fn build_outcome_txs(
}; };
let lock_time = match outcome { let lock_time = match outcome {
Outcome::Expiry => LockTime::from_consensus(params.event.expiry.ok_or(Error)?), Outcome::Expiry => {
LockTime::from_consensus(params.event.expiry.ok_or(Error::InvalidLocktime)?)
}
Outcome::Attestation(_) => LockTime::ZERO, // Normal outcome transaction Outcome::Attestation(_) => LockTime::ZERO, // Normal outcome transaction
}; };
@@ -120,13 +122,17 @@ pub(crate) fn partial_sign_outcome_txs(
funding_spend_info funding_spend_info
.key_agg_ctx() .key_agg_ctx()
.pubkey_index(seckey.base_point_mul()) .pubkey_index(seckey.base_point_mul())
.ok_or(Error)?; .ok_or(Error::InvalidKey)?;
let mut outcome_partial_sigs = BTreeMap::<Outcome, PartialSignature>::new(); let mut outcome_partial_sigs = BTreeMap::<Outcome, PartialSignature>::new();
for (&outcome, outcome_tx) in outcome_txs { for (&outcome, outcome_tx) in outcome_txs {
let aggnonce = aggnonces.get(&outcome).ok_or(Error)?; // must provide all aggnonces let aggnonce = aggnonces
let secnonce = secnonces.remove(&outcome).ok_or(Error)?; // must provide all secnonces .get(&outcome)
.ok_or(Error::MissingNonce(String::from("aggnonce for outcome")))?; // must provide all aggnonces
let secnonce = secnonces
.remove(&outcome)
.ok_or(Error::MissingNonce(String::from("secnonce for outcome")))?; // must provide all secnonces
// Hash the outcome TX. // Hash the outcome TX.
let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?; let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?;
@@ -138,7 +144,7 @@ pub(crate) fn partial_sign_outcome_txs(
.event .event
.locking_points .locking_points
.get(outcome_index) .get(outcome_index)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
// sign under an attestation lock point // sign under an attestation lock point
musig2::adaptor::sign_partial( musig2::adaptor::sign_partial(
@@ -178,9 +184,17 @@ pub(crate) fn verify_outcome_tx_partial_signatures(
let funding_spend_info = &outcome_build_out.funding_spend_info; let funding_spend_info = &outcome_build_out.funding_spend_info;
for (&outcome, outcome_tx) in outcome_txs { for (&outcome, outcome_tx) in outcome_txs {
let aggnonce = aggnonces.get(&outcome).ok_or(Error)?; // must provide all aggnonces let aggnonce = aggnonces
let pubnonce = pubnonces.get(&outcome).ok_or(Error)?; // must provide all pubnonces .get(&outcome)
let &partial_sig = partial_signatures.get(&outcome).ok_or(Error)?; // must provide all sigs .ok_or(Error::MissingNonce(String::from("aggnonce for outcome")))?; // must provide all aggnonces
let pubnonce = pubnonces
.get(&outcome)
.ok_or(Error::MissingNonce(String::from("pubnonce for outcome")))?; // must provide all pubnonces
let &partial_sig = partial_signatures
.get(&outcome)
.ok_or(Error::MissingSignature(String::from(
"partial_signatures for outcome",
)))?; // must provide all sigs
// Hash the outcome TX. // Hash the outcome TX.
let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?; let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?;
@@ -192,7 +206,7 @@ pub(crate) fn verify_outcome_tx_partial_signatures(
.event .event
.locking_points .locking_points
.get(outcome_index) .get(outcome_index)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
musig2::adaptor::verify_partial( musig2::adaptor::verify_partial(
funding_spend_info.key_agg_ctx(), funding_spend_info.key_agg_ctx(),
@@ -263,10 +277,17 @@ where
for (&outcome, outcome_tx) in outcome_txs { for (&outcome, outcome_tx) in outcome_txs {
// must provide a set of sigs for each TX // must provide a set of sigs for each TX
let partial_sigs = partial_signature_groups.remove(&outcome).ok_or(Error)?; let partial_sigs =
partial_signature_groups
.remove(&outcome)
.ok_or(Error::MissingSignature(String::from(
"outcome from partial signature groups",
)))?;
// must provide all aggnonces // must provide all aggnonces
let aggnonce = aggnonces.get(&outcome).ok_or(Error)?; let aggnonce = aggnonces
.get(&outcome)
.ok_or(Error::MissingNonce(String::from("aggnonces for outcome")))?;
// Hash the outcome TX. // Hash the outcome TX.
let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?; let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?;
@@ -277,7 +298,7 @@ where
.event .event
.locking_points .locking_points
.get(outcome_index) .get(outcome_index)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
let adaptor_sig = musig2::adaptor::aggregate_partial_signatures( let adaptor_sig = musig2::adaptor::aggregate_partial_signatures(
funding_spend_info.key_agg_ctx(), funding_spend_info.key_agg_ctx(),
@@ -325,7 +346,7 @@ pub(crate) fn verify_outcome_tx_aggregated_signatures(
// win something. // win something.
let relevant_outcomes: BTreeSet<Outcome> = params let relevant_outcomes: BTreeSet<Outcome> = params
.win_conditions_claimable_by_pubkey(our_pubkey) .win_conditions_claimable_by_pubkey(our_pubkey)
.ok_or(Error)? .ok_or(Error::InvalidKey)?
.into_iter() .into_iter()
.map(|win_cond| win_cond.outcome) .map(|win_cond| win_cond.outcome)
.collect(); .collect();
@@ -334,7 +355,10 @@ pub(crate) fn verify_outcome_tx_aggregated_signatures(
let batch: Vec<BatchVerificationRow> = relevant_outcomes let batch: Vec<BatchVerificationRow> = relevant_outcomes
.into_iter() .into_iter()
.map(|outcome| { .map(|outcome| {
let outcome_tx = outcome_build_out.outcome_txs.get(&outcome).ok_or(Error)?; let outcome_tx = outcome_build_out
.outcome_txs
.get(&outcome)
.ok_or(Error::UnknownOutcome)?;
let sighash = outcome_build_out let sighash = outcome_build_out
.funding_spend_info .funding_spend_info
@@ -347,9 +371,13 @@ pub(crate) fn verify_outcome_tx_aggregated_signatures(
.event .event
.locking_points .locking_points
.get(outcome_index) .get(outcome_index)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
let &signature = outcome_tx_signatures.get(&outcome_index).ok_or(Error)?; let &signature = outcome_tx_signatures.get(&outcome_index).ok_or(
Error::MissingSignature(String::from(
"outcome index outcome tx signatures",
)),
)?;
BatchVerificationRow::from_adaptor_signature( BatchVerificationRow::from_adaptor_signature(
joint_pubkey, joint_pubkey,
sighash, sighash,
@@ -360,7 +388,9 @@ pub(crate) fn verify_outcome_tx_aggregated_signatures(
// One signature for the optional expiry transaction. // One signature for the optional expiry transaction.
Outcome::Expiry => { Outcome::Expiry => {
let signature = expiry_tx_signature.ok_or(Error)?.lift_nonce()?; let signature = expiry_tx_signature
.ok_or(Error::MissingSignature(String::from("expiry tx signature")))?
.lift_nonce()?;
BatchVerificationRow::from_signature(joint_pubkey, sighash, signature) BatchVerificationRow::from_signature(joint_pubkey, sighash, signature)
} }
}; };
@@ -383,7 +413,10 @@ pub(crate) fn outcome_tx_prevout<'x>(
outcome: &Outcome, outcome: &Outcome,
block_delay: u16, block_delay: u16,
) -> Result<(TxIn, &'x TxOut), Error> { ) -> Result<(TxIn, &'x TxOut), Error> {
let outcome_tx = outcome_build_out.outcome_txs().get(outcome).ok_or(Error)?; let outcome_tx = outcome_build_out
.outcome_txs()
.get(outcome)
.ok_or(Error::UnknownOutcome)?;
let outcome_input = TxIn { let outcome_input = TxIn {
previous_output: OutPoint { previous_output: OutPoint {
@@ -394,7 +427,10 @@ pub(crate) fn outcome_tx_prevout<'x>(
..TxIn::default() ..TxIn::default()
}; };
let prevout = outcome_tx.output.get(0).ok_or(Error)?; let prevout = outcome_tx
.output
.get(0)
.ok_or(Error::InvalidInput("missing outcome tx output"))?;
Ok((outcome_input, prevout)) Ok((outcome_input, prevout))
} }

View File

@@ -49,7 +49,7 @@ pub(crate) fn build_split_txs(
let outcome_spend_info = &outcome_build_output let outcome_spend_info = &outcome_build_output
.outcome_spend_infos() .outcome_spend_infos()
.get(&outcome) .get(&outcome)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
// Fee estimation // Fee estimation
let input_weight = outcome_spend_info.input_weight_for_split_tx(); let input_weight = outcome_spend_info.input_weight_for_split_tx();
@@ -72,7 +72,10 @@ pub(crate) fn build_split_txs(
// payout_values is a btree, so outputs are automatically sorted by player. // payout_values is a btree, so outputs are automatically sorted by player.
let mut split_tx_outputs = Vec::with_capacity(payout_map.len()); let mut split_tx_outputs = Vec::with_capacity(payout_map.len());
for (player_index, payout_value) in payout_values { for (player_index, payout_value) in payout_values {
let player = params.players.get(player_index).ok_or(Error)?; let player = params
.players
.get(player_index)
.ok_or(Error::OutOfBoundsPlayerIndex)?;
let split_spend_info = SplitSpendInfo::new( let split_spend_info = SplitSpendInfo::new(
player, player,
&params.market_maker, &params.market_maker,
@@ -131,7 +134,7 @@ pub(crate) fn partial_sign_split_txs(
let win_conditions_to_sign = params let win_conditions_to_sign = params
.win_conditions_controlled_by_pubkey(pubkey) .win_conditions_controlled_by_pubkey(pubkey)
.ok_or(Error)?; .ok_or(Error::InvalidKey)?;
if win_conditions_to_sign.is_empty() { if win_conditions_to_sign.is_empty() {
return Ok(partial_signatures); return Ok(partial_signatures);
} }
@@ -140,15 +143,23 @@ pub(crate) fn partial_sign_split_txs(
let split_tx = split_build_out let split_tx = split_build_out
.split_txs() .split_txs()
.get(&win_cond.outcome) .get(&win_cond.outcome)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
let aggnonce = aggnonces.get(&win_cond).ok_or(Error)?; // must provide all aggnonces let aggnonce = aggnonces
let secnonce = secnonces.remove(&win_cond).ok_or(Error)?; // must provide all secnonces .get(&win_cond)
.ok_or(Error::MissingNonce(String::from(
"aggnonce for win condition",
)))?; // must provide all aggnonces
let secnonce = secnonces
.remove(&win_cond)
.ok_or(Error::MissingNonce(String::from(
"secnonces for win condition",
)))?; // must provide all secnonces
let outcome_spend_info = outcome_build_out let outcome_spend_info = outcome_build_out
.outcome_spend_infos() .outcome_spend_infos()
.get(&win_cond.outcome) .get(&win_cond.outcome)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
// Hash the split TX. // Hash the split TX.
let sighash = outcome_spend_info.sighash_tx_split(split_tx, &win_cond.player_index)?; let sighash = outcome_spend_info.sighash_tx_split(split_tx, &win_cond.player_index)?;
@@ -187,22 +198,36 @@ pub(crate) fn verify_split_tx_partial_signatures(
) -> Result<(), Error> { ) -> Result<(), Error> {
let win_conditions_to_sign = params let win_conditions_to_sign = params
.win_conditions_controlled_by_pubkey(signer_pubkey) .win_conditions_controlled_by_pubkey(signer_pubkey)
.ok_or(Error)?; .ok_or(Error::InvalidKey)?;
for win_cond in win_conditions_to_sign { for win_cond in win_conditions_to_sign {
let split_tx = split_build_out let split_tx = split_build_out
.split_txs() .split_txs()
.get(&win_cond.outcome) .get(&win_cond.outcome)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
let aggnonce = aggnonces.get(&win_cond).ok_or(Error)?; // must provide all aggnonces let aggnonce = aggnonces
let pubnonce = pubnonces.get(&win_cond).ok_or(Error)?; // must provide all pubnonces .get(&win_cond)
let partial_sig = partial_signatures.get(&win_cond).copied().ok_or(Error)?; // must provide all sigs .ok_or(Error::MissingNonce(String::from(
"aggnonces for win condition",
)))?; // must provide all aggnonces
let pubnonce = pubnonces
.get(&win_cond)
.ok_or(Error::MissingNonce(String::from(
"pubnonce for win condition",
)))?; // must provide all pubnonces
let partial_sig =
partial_signatures
.get(&win_cond)
.copied()
.ok_or(Error::MissingSignature(String::from(
"partial signatures for win condition",
)))?;
let outcome_spend_info = outcome_build_out let outcome_spend_info = outcome_build_out
.outcome_spend_infos() .outcome_spend_infos()
.get(&win_cond.outcome) .get(&win_cond.outcome)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
// Hash the split TX. // Hash the split TX.
let sighash = outcome_spend_info.sighash_tx_split(split_tx, &win_cond.player_index)?; let sighash = outcome_spend_info.sighash_tx_split(split_tx, &win_cond.player_index)?;
@@ -243,18 +268,25 @@ where
let split_tx = split_build_out let split_tx = split_build_out
.split_txs() .split_txs()
.get(&win_cond.outcome) .get(&win_cond.outcome)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
let relevant_partial_sigs = partial_signatures_by_win_cond let relevant_partial_sigs =
.remove(&win_cond) partial_signatures_by_win_cond
.ok_or(Error)?; .remove(&win_cond)
.ok_or(Error::MissingSignature(String::from(
"partial signatures by win cond for win condition",
)))?;
let aggnonce = aggnonces.get(&win_cond).ok_or(Error)?; let aggnonce = aggnonces
.get(&win_cond)
.ok_or(Error::MissingNonce(String::from(
"aggnonces for win condition",
)))?;
let outcome_spend_info = outcome_build_out let outcome_spend_info = outcome_build_out
.outcome_spend_infos() .outcome_spend_infos()
.get(&win_cond.outcome) .get(&win_cond.outcome)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
// Hash the split TX. // Hash the split TX.
let sighash = outcome_spend_info.sighash_tx_split(split_tx, &win_cond.player_index)?; let sighash = outcome_spend_info.sighash_tx_split(split_tx, &win_cond.player_index)?;
@@ -283,7 +315,7 @@ pub(crate) fn verify_split_tx_aggregated_signatures(
// win something. // win something.
let relevant_win_conditions: BTreeSet<WinCondition> = params let relevant_win_conditions: BTreeSet<WinCondition> = params
.win_conditions_claimable_by_pubkey(our_pubkey) .win_conditions_claimable_by_pubkey(our_pubkey)
.ok_or(Error)?; .ok_or(Error::InvalidKey)?;
let batch: Vec<BatchVerificationRow> = relevant_win_conditions let batch: Vec<BatchVerificationRow> = relevant_win_conditions
.into_iter() .into_iter()
@@ -291,14 +323,18 @@ pub(crate) fn verify_split_tx_aggregated_signatures(
let split_tx = split_build_out let split_tx = split_build_out
.split_txs() .split_txs()
.get(&win_cond.outcome) .get(&win_cond.outcome)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
let signature = split_tx_signatures.get(&win_cond).ok_or(Error)?; let signature = split_tx_signatures
.get(&win_cond)
.ok_or(Error::MissingSignature(String::from(
"split tx signature for win condition",
)))?;
let outcome_spend_info = outcome_build_out let outcome_spend_info = outcome_build_out
.outcome_spend_infos() .outcome_spend_infos()
.get(&win_cond.outcome) .get(&win_cond.outcome)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
// Expect an untweaked signature by the group, so that the signature // Expect an untweaked signature by the group, so that the signature
// can be used to trigger one of the players' split tapscripts. // can be used to trigger one of the players' split tapscripts.
@@ -335,13 +371,16 @@ pub(crate) fn split_tx_prevout<'x>(
let split_tx = split_build_out let split_tx = split_build_out
.split_txs() .split_txs()
.get(&win_cond.outcome) .get(&win_cond.outcome)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
let payout_map = params.outcome_payouts.get(&win_cond.outcome).ok_or(Error)?; let payout_map = params
.outcome_payouts
.get(&win_cond.outcome)
.ok_or(Error::UnknownOutcome)?;
let split_tx_output_index = payout_map let split_tx_output_index = payout_map
.keys() .keys()
.position(|&player_index| player_index == win_cond.player_index) .position(|&player_index| player_index == win_cond.player_index)
.ok_or(Error)?; .ok_or(Error::OutOfBoundsPlayerIndex)?;
let input = TxIn { let input = TxIn {
previous_output: OutPoint { previous_output: OutPoint {
@@ -352,7 +391,10 @@ pub(crate) fn split_tx_prevout<'x>(
..TxIn::default() ..TxIn::default()
}; };
let prevout = split_tx.output.get(split_tx_output_index).ok_or(Error)?; let prevout = split_tx
.output
.get(split_tx_output_index)
.ok_or(Error::InvalidInput("missing split tx output"))?;
Ok((input, prevout)) Ok((input, prevout))
} }

View File

@@ -1,65 +1,164 @@
/// TODO actual error types. use std::fmt;
#[derive(Debug, Clone)]
pub struct Error;
impl std::fmt::Display for Error { #[derive(Debug)]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { pub enum Error {
f.write_str("generic error") // Fee calculation errors
InsufficientFunds,
InvalidFeeAmount,
DustAmount,
WeightOverflow,
// Contract validation errors
DuplicateTicketHash,
InvalidPayoutWeight,
OutOfBoundsPlayerIndex,
InvalidFeeRate,
InvalidLocktime,
InvalidFundingValue,
UnknownOutcome,
EmptyPayoutMap,
// Transaction signing errors
InvalidSignature,
MissingSignature(String),
MissingNonce(String),
InvalidKey,
// Dependencies errors
KeyAgg(musig2::errors::KeyAggError),
Tweak(musig2::errors::TweakError),
Verify(musig2::errors::VerifyError),
Signing(musig2::errors::SigningError),
InvalidPoint(secp::errors::InvalidPointBytes),
InvalidSecretKeys(musig2::errors::InvalidSecretKeysError),
TaprootBuilder(bitcoin::taproot::TaprootBuilderError),
IncompleteBuilder(bitcoin::taproot::IncompleteBuilderError),
TaprootSighash(bitcoin::sighash::TaprootError),
ParseOutcomeIndex(std::num::ParseIntError),
// General errors
InvalidInput(&'static str),
Conversion(&'static str),
}
impl std::error::Error for Error {
// Implement source() to expose inner errors
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use Error::*;
match self {
KeyAgg(e) => Some(e),
Tweak(e) => Some(e),
Verify(e) => Some(e),
Signing(e) => Some(e),
InvalidPoint(e) => Some(e),
InvalidSecretKeys(e) => Some(e),
TaprootBuilder(e) => Some(e),
IncompleteBuilder(e) => Some(e),
TaprootSighash(e) => Some(e),
ParseOutcomeIndex(e) => Some(e),
_ => None,
}
} }
} }
impl std::error::Error for Error {} impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Error::*;
match self {
InsufficientFunds => write!(f, "insufficient funds available"),
InvalidFeeAmount => write!(f, "invalid fee amount"),
DustAmount => write!(f, "output amount would be below dust threshold"),
WeightOverflow => write!(f, "transaction weight calculation overflow"),
DuplicateTicketHash => write!(f, "duplicate ticket hash found"),
InvalidPayoutWeight => write!(f, "invalid payout weight"),
OutOfBoundsPlayerIndex => write!(f, "player index out of bounds"),
InvalidFeeRate => write!(f, "invalid fee rate"),
InvalidLocktime => write!(f, "invalid relative locktime"),
InvalidFundingValue => write!(f, "invalid funding value"),
UnknownOutcome => write!(f, "unknown outcome"),
EmptyPayoutMap => write!(f, "empty payout map"),
InvalidSignature => write!(f, "invalid signature"),
MissingSignature(msg) => write!(f, "missing required signature: {}", msg),
MissingNonce(msg) => write!(f, "missing required nonce: {}", msg),
InvalidKey => write!(f, "invalid key"),
InvalidInput(msg) => write!(f, "invalid input: {}", msg),
Conversion(msg) => write!(f, "conversion error: {}", msg),
KeyAgg(e) => write!(f, "key aggregation error: {}", e),
Tweak(e) => write!(f, "key tweaking error: {}", e),
Verify(e) => write!(f, "signature verification error: {}", e),
Signing(e) => write!(f, "signing error: {}", e),
InvalidPoint(e) => write!(f, "invalid point error: {}", e),
InvalidSecretKeys(e) => write!(f, "invalid secret keys: {}", e),
TaprootBuilder(e) => write!(f, "taproot builder error: {}", e),
IncompleteBuilder(e) => write!(f, "incomplete taproot builder: {}", e),
TaprootSighash(e) => write!(f, "taproot sighash error: {}", e),
ParseOutcomeIndex(e) => write!(f, "invalid outcome index: {}", e),
}
}
}
// Implement From for common error types
impl From<musig2::errors::KeyAggError> for Error { impl From<musig2::errors::KeyAggError> for Error {
fn from(_: musig2::errors::KeyAggError) -> Self { fn from(e: musig2::errors::KeyAggError) -> Self {
Error Error::KeyAgg(e)
} }
} }
impl From<musig2::errors::TweakError> for Error { impl From<musig2::errors::TweakError> for Error {
fn from(_: musig2::errors::TweakError) -> Self { fn from(e: musig2::errors::TweakError) -> Self {
Error Error::Tweak(e)
} }
} }
impl From<musig2::errors::VerifyError> for Error { impl From<musig2::errors::VerifyError> for Error {
fn from(_: musig2::errors::VerifyError) -> Self { fn from(e: musig2::errors::VerifyError) -> Self {
Error Error::Verify(e)
} }
} }
impl From<musig2::errors::SigningError> for Error { impl From<musig2::errors::SigningError> for Error {
fn from(_: musig2::errors::SigningError) -> Self { fn from(e: musig2::errors::SigningError) -> Self {
Error Error::Signing(e)
} }
} }
impl From<secp::errors::InvalidPointBytes> for Error { impl From<secp::errors::InvalidPointBytes> for Error {
fn from(_: secp::errors::InvalidPointBytes) -> Self { fn from(e: secp::errors::InvalidPointBytes) -> Self {
Error Error::InvalidPoint(e)
} }
} }
impl From<musig2::errors::InvalidSecretKeysError> for Error { impl From<musig2::errors::InvalidSecretKeysError> for Error {
fn from(_: musig2::errors::InvalidSecretKeysError) -> Self { fn from(e: musig2::errors::InvalidSecretKeysError) -> Self {
Error Error::InvalidSecretKeys(e)
} }
} }
impl From<bitcoin::taproot::TaprootBuilderError> for Error { impl From<bitcoin::taproot::TaprootBuilderError> for Error {
fn from(_: bitcoin::taproot::TaprootBuilderError) -> Self { fn from(e: bitcoin::taproot::TaprootBuilderError) -> Self {
Error Error::TaprootBuilder(e)
} }
} }
impl From<bitcoin::taproot::IncompleteBuilderError> for Error { impl From<bitcoin::taproot::IncompleteBuilderError> for Error {
fn from(_: bitcoin::taproot::IncompleteBuilderError) -> Self { fn from(e: bitcoin::taproot::IncompleteBuilderError) -> Self {
Error Error::IncompleteBuilder(e)
} }
} }
impl From<bitcoin::sighash::TaprootError> for Error { impl From<bitcoin::sighash::TaprootError> for Error {
fn from(_: bitcoin::sighash::TaprootError) -> Self { fn from(e: bitcoin::sighash::TaprootError) -> Self {
Error Error::TaprootSighash(e)
}
}
impl From<std::num::ParseIntError> for Error {
fn from(e: std::num::ParseIntError) -> Self {
Error::ParseOutcomeIndex(e)
} }
} }

View File

@@ -202,7 +202,10 @@ impl SigningSession<NonceSharingRound> {
let signing_key = signing_key.into(); let signing_key = signing_key.into();
let our_public_key = signing_key.base_point_mul(); let our_public_key = signing_key.base_point_mul();
let base_sigmap = dlc.params.sigmap_for_pubkey(our_public_key).ok_or(Error)?; let base_sigmap = dlc
.params
.sigmap_for_pubkey(our_public_key)
.ok_or(Error::InvalidKey)?;
let our_secret_nonces = let our_secret_nonces =
base_sigmap.map_values(|_| SecNonce::build(&mut rng).with_seckey(signing_key).build()); base_sigmap.map_values(|_| SecNonce::build(&mut rng).with_seckey(signing_key).build());
@@ -344,11 +347,14 @@ impl SigningSession<CoordinatorPartialSignatureSharingRound> {
signer_pubkey: Point, signer_pubkey: Point,
partial_signatures: &SigMap<PartialSignature>, partial_signatures: &SigMap<PartialSignature>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let signer_nonces = self let signer_nonces =
.state self.state
.received_nonces .received_nonces
.get(&signer_pubkey) .get(&signer_pubkey)
.ok_or(Error)?; .ok_or(Error::MissingNonce(format!(
"no nonces received from signer with pubkey {:?}",
signer_pubkey
)))?;
contract::outcome::verify_outcome_tx_partial_signatures( contract::outcome::verify_outcome_tx_partial_signatures(
&self.dlc.params, &self.dlc.params,
@@ -528,21 +534,29 @@ fn validate_sigmaps_completeness<T>(
) -> Result<(), Error> { ) -> Result<(), Error> {
// Must receive signatures/nonces from all players and the market maker. // Must receive signatures/nonces from all players and the market maker.
if !received_maps.contains_key(&params.market_maker.pubkey) { if !received_maps.contains_key(&params.market_maker.pubkey) {
return Err(Error); return Err(Error::MissingSignature(format!(
"market makert pubkey: {}",
params.market_maker.pubkey
)));
} }
for player in params.players.iter() { for player in params.players.iter() {
if !received_maps.contains_key(&player.pubkey) { if !received_maps.contains_key(&player.pubkey) {
return Err(Error); return Err(Error::MissingSignature(format!(
"player pubkey: {}",
player.pubkey
)));
} }
} }
for (&signer_pubkey, sigmap) in received_maps.iter() { for (&signer_pubkey, sigmap) in received_maps.iter() {
// The expected sigmap each signer must provide nonces/signatures for. // The expected sigmap each signer must provide nonces/signatures for.
let base_sigmap = params.sigmap_for_pubkey(signer_pubkey).ok_or(Error)?; let base_sigmap = params
.sigmap_for_pubkey(signer_pubkey)
.ok_or(Error::InvalidKey)?;
// All signers' sigmaps must match exactly. // All signers' sigmaps must match exactly.
if !sigmap.is_mirror(&base_sigmap) { if !sigmap.is_mirror(&base_sigmap) {
return Err(Error); return Err(Error::InvalidSignature);
} }
} }
@@ -673,25 +687,30 @@ impl SignedContract {
.event .event
.locking_points .locking_points
.get(outcome_index) .get(outcome_index)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
// Invalid attestation. // Invalid attestation.
if &attestation.base_point_mul() != locking_point { if &attestation.base_point_mul() != locking_point {
return Err(Error)?; return Err(Error::InvalidSignature)?;
} }
let mut outcome_tx = self let mut outcome_tx = self
.unsigned_outcome_tx(outcome_index) .unsigned_outcome_tx(outcome_index)
.ok_or(Error)? .ok_or(Error::UnknownOutcome)?
.clone(); .clone();
let adaptor_signature = self let adaptor_signature = self
.signatures .signatures
.outcome_tx_signatures .outcome_tx_signatures
.get(&outcome_index) .get(&outcome_index)
.ok_or(Error)?; .ok_or(Error::MissingSignature(format!(
"adaptor signature for outcome index {}",
outcome_index
)))?;
let compact_sig: CompactSignature = adaptor_signature.adapt(attestation).ok_or(Error)?; let compact_sig: CompactSignature = adaptor_signature
.adapt(attestation)
.ok_or(Error::InvalidSignature)?;
outcome_tx.input[0].witness.push(compact_sig.serialize()); outcome_tx.input[0].witness.push(compact_sig.serialize());
Ok(outcome_tx) Ok(outcome_tx)
@@ -728,26 +747,28 @@ impl SignedContract {
.players .players
.get(win_cond.player_index) .get(win_cond.player_index)
.cloned() .cloned()
.ok_or(Error)?; .ok_or(Error::OutOfBoundsPlayerIndex)?;
// Verify the preimage will unlock this specific player's split TX // Verify the preimage will unlock this specific player's split TX
// condition. // condition.
if sha256(&ticket_preimage) != winner.ticket_hash { if sha256(&ticket_preimage) != winner.ticket_hash {
return Err(Error); return Err(Error::InvalidInput("ticket preimage does not match hash"));
} }
let signature = self let signature =
.signatures self.signatures
.split_tx_signatures .split_tx_signatures
.get(win_cond) .get(win_cond)
.ok_or(Error)?; .ok_or(Error::MissingSignature(String::from(
"split tx signature for win condition",
)))?;
let outcome_spend_info = self let outcome_spend_info = self
.dlc .dlc
.outcome_tx_build .outcome_tx_build
.outcome_spend_infos() .outcome_spend_infos()
.get(&win_cond.outcome) .get(&win_cond.outcome)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
let witness = outcome_spend_info.witness_tx_split( let witness = outcome_spend_info.witness_tx_split(
signature, signature,
@@ -757,7 +778,7 @@ impl SignedContract {
let mut split_tx = self let mut split_tx = self
.unsigned_split_tx(&win_cond.outcome) .unsigned_split_tx(&win_cond.outcome)
.ok_or(Error)? .ok_or(Error::UnknownOutcome)?
.clone(); .clone();
split_tx.input[0].witness = witness; split_tx.input[0].witness = witness;
@@ -945,7 +966,7 @@ impl SignedContract {
.outcome_tx_build .outcome_tx_build
.outcome_spend_infos() .outcome_spend_infos()
.get(outcome) .get(outcome)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
let witness = outcome_spend_info.witness_tx_reclaim( let witness = outcome_spend_info.witness_tx_reclaim(
reclaim_tx, reclaim_tx,
@@ -978,7 +999,7 @@ impl SignedContract {
) -> Result<(), Error> { ) -> Result<(), Error> {
let market_maker_secret_key = market_maker_secret_key.into(); let market_maker_secret_key = market_maker_secret_key.into();
if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey { if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey {
return Err(Error); return Err(Error::InvalidKey);
} }
// Confirm we're signing the correct input // Confirm we're signing the correct input
@@ -1025,14 +1046,18 @@ impl SignedContract {
) -> Result<(), Error> { ) -> Result<(), Error> {
let market_maker_secret_key = market_maker_secret_key.into(); let market_maker_secret_key = market_maker_secret_key.into();
if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey { if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey {
return Err(Error); return Err(Error::InvalidKey);
} }
// Confirm we're signing the correct input // Confirm we're signing the correct input
let (mut expected_input, expected_prevout) = self.funding_close_tx_input_and_prevout(); let (mut expected_input, expected_prevout) = self.funding_close_tx_input_and_prevout();
// The caller can use whatever sequence they want. // The caller can use whatever sequence they want.
expected_input.sequence = close_tx.input.get(input_index).ok_or(Error)?.sequence; expected_input.sequence = close_tx
.input
.get(input_index)
.ok_or(Error::InvalidInput("transaction input index out of bounds"))?
.sequence;
check_input_matches_expected( check_input_matches_expected(
close_tx, close_tx,
@@ -1081,7 +1106,7 @@ impl SignedContract {
) -> Result<(), Error> { ) -> Result<(), Error> {
let market_maker_secret_key = market_maker_secret_key.into(); let market_maker_secret_key = market_maker_secret_key.into();
if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey { if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey {
return Err(Error); return Err(Error::InvalidKey);
} }
// Confirm we're signing the correct input // Confirm we're signing the correct input
@@ -1089,7 +1114,11 @@ impl SignedContract {
self.outcome_close_tx_input_and_prevout(outcome)?; self.outcome_close_tx_input_and_prevout(outcome)?;
// The caller can use whatever sequence they want. // The caller can use whatever sequence they want.
expected_input.sequence = close_tx.input.get(input_index).ok_or(Error)?.sequence; expected_input.sequence = close_tx
.input
.get(input_index)
.ok_or(Error::InvalidInput("transaction input index out of bounds"))?
.sequence;
check_input_matches_expected( check_input_matches_expected(
close_tx, close_tx,
@@ -1104,7 +1133,7 @@ impl SignedContract {
.outcome_tx_build .outcome_tx_build
.outcome_spend_infos() .outcome_spend_infos()
.get(outcome) .get(outcome)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
let witness = outcome_spend_info.witness_tx_close( let witness = outcome_spend_info.witness_tx_close(
close_tx, close_tx,
@@ -1132,7 +1161,7 @@ impl SignedContract {
.split_tx_build .split_tx_build
.split_spend_infos() .split_spend_infos()
.get(win_cond) .get(win_cond)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
let witness = split_spend_info.witness_tx_win( let witness = split_spend_info.witness_tx_win(
win_tx, win_tx,
@@ -1172,13 +1201,13 @@ impl SignedContract {
.players .players
.get(win_cond.player_index) .get(win_cond.player_index)
.cloned() .cloned()
.ok_or(Error)?; .ok_or(Error::OutOfBoundsPlayerIndex)?;
let player_secret_key = player_secret_key.into(); let player_secret_key = player_secret_key.into();
if player_secret_key.base_point_mul() != winner.pubkey { if player_secret_key.base_point_mul() != winner.pubkey {
return Err(Error); return Err(Error::InvalidKey);
} else if sha256(&ticket_preimage) != winner.ticket_hash { } else if sha256(&ticket_preimage) != winner.ticket_hash {
return Err(Error); return Err(Error::InvalidInput("ticket preimage does not match hash"));
} }
// Confirm we're signing the correct input // Confirm we're signing the correct input
@@ -1214,7 +1243,7 @@ impl SignedContract {
.split_tx_build .split_tx_build
.split_spend_infos() .split_spend_infos()
.get(win_cond) .get(win_cond)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
let witness = split_spend_info.witness_tx_reclaim( let witness = split_spend_info.witness_tx_reclaim(
reclaim_tx, reclaim_tx,
@@ -1246,7 +1275,7 @@ impl SignedContract {
) -> Result<(), Error> { ) -> Result<(), Error> {
let market_maker_secret_key = market_maker_secret_key.into(); let market_maker_secret_key = market_maker_secret_key.into();
if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey { if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey {
return Err(Error); return Err(Error::InvalidKey);
} }
// Confirm we're signing the correct input // Confirm we're signing the correct input
@@ -1295,7 +1324,7 @@ impl SignedContract {
) -> Result<(), Error> { ) -> Result<(), Error> {
let market_maker_secret_key = market_maker_secret_key.into(); let market_maker_secret_key = market_maker_secret_key.into();
if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey { if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey {
return Err(Error); return Err(Error::InvalidKey);
} }
// Confirm we're signing the correct input // Confirm we're signing the correct input
@@ -1303,7 +1332,11 @@ impl SignedContract {
self.split_sellback_tx_input_and_prevout(win_cond)?; self.split_sellback_tx_input_and_prevout(win_cond)?;
// The caller can use whatever sequence they want. // The caller can use whatever sequence they want.
expected_input.sequence = sellback_tx.input.get(input_index).ok_or(Error)?.sequence; expected_input.sequence = sellback_tx
.input
.get(input_index)
.ok_or(Error::InvalidInput("transaction input index out of bounds"))?
.sequence;
check_input_matches_expected( check_input_matches_expected(
sellback_tx, sellback_tx,
@@ -1318,7 +1351,7 @@ impl SignedContract {
.split_tx_build .split_tx_build
.split_spend_infos() .split_spend_infos()
.get(win_cond) .get(win_cond)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
let witness = split_spend_info.witness_tx_sellback( let witness = split_spend_info.witness_tx_sellback(
sellback_tx, sellback_tx,
@@ -1360,7 +1393,7 @@ impl SignedContract {
) -> Result<(), Error> { ) -> Result<(), Error> {
let market_maker_secret_key = market_maker_secret_key.into(); let market_maker_secret_key = market_maker_secret_key.into();
if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey { if market_maker_secret_key.base_point_mul() != self.dlc.params.market_maker.pubkey {
return Err(Error); return Err(Error::InvalidKey);
} }
// Confirm we're signing the correct input // Confirm we're signing the correct input
@@ -1368,7 +1401,11 @@ impl SignedContract {
self.split_sellback_tx_input_and_prevout(win_cond)?; self.split_sellback_tx_input_and_prevout(win_cond)?;
// The caller can use whatever sequence they want. // The caller can use whatever sequence they want.
expected_input.sequence = close_tx.input.get(input_index).ok_or(Error)?.sequence; expected_input.sequence = close_tx
.input
.get(input_index)
.ok_or(Error::InvalidInput("transaction input index out of bounds"))?
.sequence;
check_input_matches_expected( check_input_matches_expected(
close_tx, close_tx,
@@ -1383,7 +1420,7 @@ impl SignedContract {
.split_tx_build .split_tx_build
.split_spend_infos() .split_spend_infos()
.get(win_cond) .get(win_cond)
.ok_or(Error)?; .ok_or(Error::UnknownOutcome)?;
let witness = split_spend_info.witness_tx_close( let witness = split_spend_info.witness_tx_close(
close_tx, close_tx,
@@ -1407,23 +1444,29 @@ fn check_input_matches_expected<T: Borrow<TxOut>>(
expected_input: &TxIn, expected_input: &TxIn,
expected_prevout: &TxOut, expected_prevout: &TxOut,
) -> Result<(), Error> { ) -> Result<(), Error> {
let input = tx.input.get(input_index).ok_or(Error)?; let input = tx
.input
.get(input_index)
.ok_or(Error::InvalidInput("transaction input index out of bounds"))?;
if input != expected_input { if input != expected_input {
return Err(Error); return Err(Error::InvalidInput("input does not match expected"));
} }
let prevout = match prevouts { let prevout = match prevouts {
Prevouts::All(all_prevouts) => all_prevouts.get(input_index).ok_or(Error)?.borrow(), Prevouts::All(all_prevouts) => all_prevouts
.get(input_index)
.ok_or(Error::InvalidInput("prevout index out of bounds"))?
.borrow(),
Prevouts::One(i, prevout) => { Prevouts::One(i, prevout) => {
if i != &input_index { if i != &input_index {
return Err(Error)?; return Err(Error::InvalidInput("prevout index mismatch"));
} }
prevout.borrow() prevout.borrow()
} }
}; };
if prevout != expected_prevout { if prevout != expected_prevout {
return Err(Error); return Err(Error::InvalidInput("prevout does not match expected"));
} }
Ok(()) Ok(())

View File

@@ -20,8 +20,12 @@ impl std::str::FromStr for Outcome {
match s { match s {
"exp" => Ok(Outcome::Expiry), "exp" => Ok(Outcome::Expiry),
s => { s => {
let index_str = s.strip_prefix("att").ok_or(Error)?; let index_str = s.strip_prefix("att").ok_or(Error::Conversion(
let outcome_index = index_str.parse().map_err(|_| Error)?; "invalid outcome format, expected 'att' prefix",
))?;
let outcome_index = index_str
.parse::<usize>()
.map_err(Error::ParseOutcomeIndex)?;
Ok(Outcome::Attestation(outcome_index)) Ok(Outcome::Attestation(outcome_index))
} }
} }
@@ -72,10 +76,14 @@ impl std::str::FromStr for WinCondition {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let (prefix, suffix) = s.split_once(":").ok_or(Error)?; let (prefix, suffix) = s.split_once(":").ok_or(Error::Conversion(
"invalid win condition format, missing ':' separator",
))?;
let outcome: Outcome = prefix.parse()?; let outcome: Outcome = prefix.parse()?;
let player_index_str = suffix.strip_prefix("p").ok_or(Error)?; let player_index_str = suffix.strip_prefix("p").ok_or(Error::Conversion(
let player_index = player_index_str.parse().map_err(|_| Error)?; "invalid win condition format, expected 'p' prefix",
))?;
let player_index = player_index_str.parse()?;
Ok(WinCondition { Ok(WinCondition {
outcome, outcome,
player_index, player_index,

View File

@@ -103,7 +103,10 @@ impl FundingSpendInfo {
if pubkey == mm_pubkey { if pubkey == mm_pubkey {
Ok(market_maker_secret_key) Ok(market_maker_secret_key)
} else { } else {
player_secret_keys.get(&pubkey).ok_or(Error).copied() player_secret_keys
.get(&pubkey)
.ok_or(Error::OutOfBoundsPlayerIndex)
.copied()
} }
}) })
.collect::<Result<_, Error>>()?; .collect::<Result<_, Error>>()?;

View File

@@ -58,7 +58,7 @@ impl OutcomeSpendInfo {
let winners: BTreeMap<PlayerIndex, &Player> = winner_indexes let winners: BTreeMap<PlayerIndex, &Player> = winner_indexes
.into_iter() .into_iter()
.map(|i| { .map(|i| {
let player = all_players.get(i).ok_or(Error)?; let player = all_players.get(i).ok_or(Error::OutOfBoundsPlayerIndex)?;
Ok((i, player.borrow())) Ok((i, player.borrow()))
}) })
.collect::<Result<_, Error>>()?; .collect::<Result<_, Error>>()?;
@@ -216,7 +216,10 @@ impl OutcomeSpendInfo {
script_pubkey: self.script_pubkey(), script_pubkey: self.script_pubkey(),
value: self.outcome_value, value: self.outcome_value,
}]; }];
let split_script = self.winner_split_scripts.get(player_index).ok_or(Error)?; let split_script = self
.winner_split_scripts
.get(player_index)
.ok_or(Error::OutOfBoundsPlayerIndex)?;
let leaf_hash = TapLeafHash::from_script(split_script, LeafVersion::TapScript); let leaf_hash = TapLeafHash::from_script(split_script, LeafVersion::TapScript);
let sighash = SighashCache::new(split_tx).taproot_script_spend_signature_hash( let sighash = SighashCache::new(split_tx).taproot_script_spend_signature_hash(
@@ -238,12 +241,12 @@ impl OutcomeSpendInfo {
let split_script = self let split_script = self
.winner_split_scripts .winner_split_scripts
.get(player_index) .get(player_index)
.ok_or(Error)? .ok_or(Error::OutOfBoundsPlayerIndex)?
.clone(); .clone();
let control_block = self let control_block = self
.spend_info .spend_info
.control_block(&(split_script.clone(), LeafVersion::TapScript)) .control_block(&(split_script.clone(), LeafVersion::TapScript))
.ok_or(Error)?; .ok_or(Error::InvalidKey)?;
let mut witness = Witness::new(); let mut witness = Witness::new();
witness.push(signature.serialize()); witness.push(signature.serialize());
@@ -319,7 +322,12 @@ impl OutcomeSpendInfo {
if pubkey == mm_pubkey { if pubkey == mm_pubkey {
Ok(market_maker_secret_key) Ok(market_maker_secret_key)
} else { } else {
player_secret_keys.get(&pubkey).ok_or(Error).copied() player_secret_keys
.get(&pubkey)
.ok_or(Error::MissingSignature(String::from(
"player secret keys for pubkey",
)))
.copied()
} }
}) })
.collect::<Result<_, Error>>()?; .collect::<Result<_, Error>>()?;