diff --git a/src/contract/fees.rs b/src/contract/fees.rs index d227f29..db82d66 100644 --- a/src/contract/fees.rs +++ b/src/contract/fees.rs @@ -16,7 +16,7 @@ where O: IntoIterator, { 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) } @@ -29,11 +29,13 @@ pub(crate) fn fee_subtract_safe( dust_threshold: Amount, ) -> Result { 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 { - return Err(Error); + return Err(Error::DustAmount); } Ok(after_fee) } diff --git a/src/contract/mod.rs b/src/contract/mod.rs index c0718ed..20959c7 100644 --- a/src/contract/mod.rs +++ b/src/contract/mod.rs @@ -125,46 +125,46 @@ impl ContractParameters { // This would imply the players array contains duplicate ticket hashes. if uniq_ticket_hashes.len() != self.players.len() { - return Err(Error); + return Err(Error::DuplicateTicketHash); } for (outcome, payout_map) in self.outcome_payouts.iter() { // Check for unknown outcomes. if !self.event.is_valid_outcome(outcome) { - return Err(Error); + return Err(Error::UnknownOutcome); } // Check for empty payout map. if payout_map.len() == 0 { - return Err(Error); + return Err(Error::EmptyPayoutMap); } for (&player_index, &weight) in payout_map.iter() { // Check for zero payout weights. if weight == 0 { - return Err(Error); + return Err(Error::InvalidPayoutWeight); } // Check for out-of-bounds player indexes. if player_index >= self.players.len() { - return Err(Error); + return Err(Error::OutOfBoundsPlayerIndex); } } } // Must use a non-zero fee rate. if self.fee_rate == FeeRate::ZERO { - return Err(Error); + return Err(Error::InvalidFeeAmount); } // Must use a non-zero locktime delta if self.relative_locktime_block_delta == 0 { - return Err(Error); + return Err(Error::InvalidLocktime); } // Must be funded by some fixed non-zero amount. if self.funding_value < Amount::ZERO { - return Err(Error); + return Err(Error::InsufficientFunds); } Ok(()) diff --git a/src/contract/outcome.rs b/src/contract/outcome.rs index 9e9d656..e95cc69 100644 --- a/src/contract/outcome.rs +++ b/src/contract/outcome.rs @@ -78,7 +78,9 @@ pub(crate) fn build_outcome_txs( }; 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 }; @@ -120,13 +122,17 @@ pub(crate) fn partial_sign_outcome_txs( funding_spend_info .key_agg_ctx() .pubkey_index(seckey.base_point_mul()) - .ok_or(Error)?; + .ok_or(Error::InvalidKey)?; let mut outcome_partial_sigs = BTreeMap::::new(); for (&outcome, outcome_tx) in outcome_txs { - let aggnonce = aggnonces.get(&outcome).ok_or(Error)?; // must provide all aggnonces - let secnonce = secnonces.remove(&outcome).ok_or(Error)?; // must provide all secnonces + let aggnonce = aggnonces + .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. let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?; @@ -138,7 +144,7 @@ pub(crate) fn partial_sign_outcome_txs( .event .locking_points .get(outcome_index) - .ok_or(Error)?; + .ok_or(Error::UnknownOutcome)?; // sign under an attestation lock point 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; for (&outcome, outcome_tx) in outcome_txs { - 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 + let aggnonce = aggnonces + .get(&outcome) + .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. let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?; @@ -192,7 +206,7 @@ pub(crate) fn verify_outcome_tx_partial_signatures( .event .locking_points .get(outcome_index) - .ok_or(Error)?; + .ok_or(Error::UnknownOutcome)?; musig2::adaptor::verify_partial( funding_spend_info.key_agg_ctx(), @@ -263,10 +277,17 @@ where for (&outcome, outcome_tx) in outcome_txs { // 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 - 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. let sighash = funding_spend_info.sighash_tx_outcome(outcome_tx)?; @@ -277,7 +298,7 @@ where .event .locking_points .get(outcome_index) - .ok_or(Error)?; + .ok_or(Error::UnknownOutcome)?; let adaptor_sig = musig2::adaptor::aggregate_partial_signatures( funding_spend_info.key_agg_ctx(), @@ -325,7 +346,7 @@ pub(crate) fn verify_outcome_tx_aggregated_signatures( // win something. let relevant_outcomes: BTreeSet = params .win_conditions_claimable_by_pubkey(our_pubkey) - .ok_or(Error)? + .ok_or(Error::InvalidKey)? .into_iter() .map(|win_cond| win_cond.outcome) .collect(); @@ -334,7 +355,10 @@ pub(crate) fn verify_outcome_tx_aggregated_signatures( let batch: Vec = relevant_outcomes .into_iter() .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 .funding_spend_info @@ -347,9 +371,13 @@ pub(crate) fn verify_outcome_tx_aggregated_signatures( .event .locking_points .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( joint_pubkey, sighash, @@ -360,7 +388,9 @@ pub(crate) fn verify_outcome_tx_aggregated_signatures( // One signature for the optional expiry transaction. 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) } }; @@ -383,7 +413,10 @@ pub(crate) fn outcome_tx_prevout<'x>( outcome: &Outcome, block_delay: u16, ) -> 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 { previous_output: OutPoint { @@ -394,7 +427,10 @@ pub(crate) fn outcome_tx_prevout<'x>( ..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)) } diff --git a/src/contract/split.rs b/src/contract/split.rs index 7b23f38..93044d2 100644 --- a/src/contract/split.rs +++ b/src/contract/split.rs @@ -49,7 +49,7 @@ pub(crate) fn build_split_txs( let outcome_spend_info = &outcome_build_output .outcome_spend_infos() .get(&outcome) - .ok_or(Error)?; + .ok_or(Error::UnknownOutcome)?; // Fee estimation 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. let mut split_tx_outputs = Vec::with_capacity(payout_map.len()); 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( player, ¶ms.market_maker, @@ -131,7 +134,7 @@ pub(crate) fn partial_sign_split_txs( let win_conditions_to_sign = params .win_conditions_controlled_by_pubkey(pubkey) - .ok_or(Error)?; + .ok_or(Error::InvalidKey)?; if win_conditions_to_sign.is_empty() { return Ok(partial_signatures); } @@ -140,15 +143,23 @@ pub(crate) fn partial_sign_split_txs( let split_tx = split_build_out .split_txs() .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 secnonce = secnonces.remove(&win_cond).ok_or(Error)?; // must provide all secnonces + let aggnonce = aggnonces + .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 .outcome_spend_infos() .get(&win_cond.outcome) - .ok_or(Error)?; + .ok_or(Error::UnknownOutcome)?; // Hash the split TX. 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> { let win_conditions_to_sign = params .win_conditions_controlled_by_pubkey(signer_pubkey) - .ok_or(Error)?; + .ok_or(Error::InvalidKey)?; for win_cond in win_conditions_to_sign { let split_tx = split_build_out .split_txs() .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 pubnonce = pubnonces.get(&win_cond).ok_or(Error)?; // must provide all pubnonces - let partial_sig = partial_signatures.get(&win_cond).copied().ok_or(Error)?; // must provide all sigs + let aggnonce = aggnonces + .get(&win_cond) + .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 .outcome_spend_infos() .get(&win_cond.outcome) - .ok_or(Error)?; + .ok_or(Error::UnknownOutcome)?; // Hash the split TX. 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 .split_txs() .get(&win_cond.outcome) - .ok_or(Error)?; + .ok_or(Error::UnknownOutcome)?; - let relevant_partial_sigs = partial_signatures_by_win_cond - .remove(&win_cond) - .ok_or(Error)?; + let relevant_partial_sigs = + partial_signatures_by_win_cond + .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 .outcome_spend_infos() .get(&win_cond.outcome) - .ok_or(Error)?; + .ok_or(Error::UnknownOutcome)?; // Hash the split TX. 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. let relevant_win_conditions: BTreeSet = params .win_conditions_claimable_by_pubkey(our_pubkey) - .ok_or(Error)?; + .ok_or(Error::InvalidKey)?; let batch: Vec = relevant_win_conditions .into_iter() @@ -291,14 +323,18 @@ pub(crate) fn verify_split_tx_aggregated_signatures( let split_tx = split_build_out .split_txs() .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 .outcome_spend_infos() .get(&win_cond.outcome) - .ok_or(Error)?; + .ok_or(Error::UnknownOutcome)?; // Expect an untweaked signature by the group, so that the signature // 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 .split_txs() .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 .keys() .position(|&player_index| player_index == win_cond.player_index) - .ok_or(Error)?; + .ok_or(Error::OutOfBoundsPlayerIndex)?; let input = TxIn { previous_output: OutPoint { @@ -352,7 +391,10 @@ pub(crate) fn split_tx_prevout<'x>( ..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)) } diff --git a/src/errors.rs b/src/errors.rs index b3b8866..7ac8a31 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,65 +1,164 @@ -/// TODO actual error types. -#[derive(Debug, Clone)] -pub struct Error; +use std::fmt; -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.write_str("generic error") +#[derive(Debug)] +pub enum 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 for Error { - fn from(_: musig2::errors::KeyAggError) -> Self { - Error + fn from(e: musig2::errors::KeyAggError) -> Self { + Error::KeyAgg(e) } } impl From for Error { - fn from(_: musig2::errors::TweakError) -> Self { - Error + fn from(e: musig2::errors::TweakError) -> Self { + Error::Tweak(e) } } impl From for Error { - fn from(_: musig2::errors::VerifyError) -> Self { - Error + fn from(e: musig2::errors::VerifyError) -> Self { + Error::Verify(e) } } impl From for Error { - fn from(_: musig2::errors::SigningError) -> Self { - Error + fn from(e: musig2::errors::SigningError) -> Self { + Error::Signing(e) } } impl From for Error { - fn from(_: secp::errors::InvalidPointBytes) -> Self { - Error + fn from(e: secp::errors::InvalidPointBytes) -> Self { + Error::InvalidPoint(e) } } impl From for Error { - fn from(_: musig2::errors::InvalidSecretKeysError) -> Self { - Error + fn from(e: musig2::errors::InvalidSecretKeysError) -> Self { + Error::InvalidSecretKeys(e) } } impl From for Error { - fn from(_: bitcoin::taproot::TaprootBuilderError) -> Self { - Error + fn from(e: bitcoin::taproot::TaprootBuilderError) -> Self { + Error::TaprootBuilder(e) } } impl From for Error { - fn from(_: bitcoin::taproot::IncompleteBuilderError) -> Self { - Error + fn from(e: bitcoin::taproot::IncompleteBuilderError) -> Self { + Error::IncompleteBuilder(e) } } impl From for Error { - fn from(_: bitcoin::sighash::TaprootError) -> Self { - Error + fn from(e: bitcoin::sighash::TaprootError) -> Self { + Error::TaprootSighash(e) + } +} + +impl From for Error { + fn from(e: std::num::ParseIntError) -> Self { + Error::ParseOutcomeIndex(e) } } diff --git a/src/lib.rs b/src/lib.rs index 616d266..8490dfc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -202,7 +202,10 @@ impl SigningSession { 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)?; + 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()); @@ -344,11 +347,14 @@ impl SigningSession { signer_pubkey: Point, partial_signatures: &SigMap, ) -> Result<(), Error> { - let signer_nonces = self - .state - .received_nonces - .get(&signer_pubkey) - .ok_or(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, @@ -528,21 +534,29 @@ fn validate_sigmaps_completeness( ) -> Result<(), Error> { // Must receive signatures/nonces from all players and the market maker. if !received_maps.contains_key(¶ms.market_maker.pubkey) { - return Err(Error); + 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); + return Err(Error::MissingSignature(format!( + "player pubkey: {}", + player.pubkey + ))); } } 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)?; + let base_sigmap = params + .sigmap_for_pubkey(signer_pubkey) + .ok_or(Error::InvalidKey)?; // All signers' sigmaps must match exactly. if !sigmap.is_mirror(&base_sigmap) { - return Err(Error); + return Err(Error::InvalidSignature); } } @@ -673,25 +687,30 @@ impl SignedContract { .event .locking_points .get(outcome_index) - .ok_or(Error)?; + .ok_or(Error::UnknownOutcome)?; // Invalid attestation. if &attestation.base_point_mul() != locking_point { - return Err(Error)?; + return Err(Error::InvalidSignature)?; } let mut outcome_tx = self .unsigned_outcome_tx(outcome_index) - .ok_or(Error)? + .ok_or(Error::UnknownOutcome)? .clone(); let adaptor_signature = self .signatures .outcome_tx_signatures .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()); Ok(outcome_tx) @@ -728,26 +747,28 @@ impl SignedContract { .players .get(win_cond.player_index) .cloned() - .ok_or(Error)?; + .ok_or(Error::OutOfBoundsPlayerIndex)?; // Verify the preimage will unlock this specific player's split TX // condition. if sha256(&ticket_preimage) != winner.ticket_hash { - return Err(Error); + return Err(Error::InvalidInput("ticket preimage does not match hash")); } - let signature = self - .signatures - .split_tx_signatures - .get(win_cond) - .ok_or(Error)?; + 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)?; + .ok_or(Error::UnknownOutcome)?; let witness = outcome_spend_info.witness_tx_split( signature, @@ -757,7 +778,7 @@ impl SignedContract { let mut split_tx = self .unsigned_split_tx(&win_cond.outcome) - .ok_or(Error)? + .ok_or(Error::UnknownOutcome)? .clone(); split_tx.input[0].witness = witness; @@ -945,7 +966,7 @@ impl SignedContract { .outcome_tx_build .outcome_spend_infos() .get(outcome) - .ok_or(Error)?; + .ok_or(Error::UnknownOutcome)?; let witness = outcome_spend_info.witness_tx_reclaim( reclaim_tx, @@ -978,7 +999,7 @@ impl SignedContract { ) -> 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); + return Err(Error::InvalidKey); } // Confirm we're signing the correct input @@ -1025,14 +1046,18 @@ impl SignedContract { ) -> 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); + return Err(Error::InvalidKey); } // Confirm we're signing the correct input let (mut expected_input, expected_prevout) = self.funding_close_tx_input_and_prevout(); // 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( close_tx, @@ -1081,7 +1106,7 @@ impl SignedContract { ) -> 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); + return Err(Error::InvalidKey); } // Confirm we're signing the correct input @@ -1089,7 +1114,11 @@ impl SignedContract { self.outcome_close_tx_input_and_prevout(outcome)?; // 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( close_tx, @@ -1104,7 +1133,7 @@ impl SignedContract { .outcome_tx_build .outcome_spend_infos() .get(outcome) - .ok_or(Error)?; + .ok_or(Error::UnknownOutcome)?; let witness = outcome_spend_info.witness_tx_close( close_tx, @@ -1132,7 +1161,7 @@ impl SignedContract { .split_tx_build .split_spend_infos() .get(win_cond) - .ok_or(Error)?; + .ok_or(Error::UnknownOutcome)?; let witness = split_spend_info.witness_tx_win( win_tx, @@ -1172,13 +1201,13 @@ impl SignedContract { .players .get(win_cond.player_index) .cloned() - .ok_or(Error)?; + .ok_or(Error::OutOfBoundsPlayerIndex)?; let player_secret_key = player_secret_key.into(); if player_secret_key.base_point_mul() != winner.pubkey { - return Err(Error); + return Err(Error::InvalidKey); } 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 @@ -1214,7 +1243,7 @@ impl SignedContract { .split_tx_build .split_spend_infos() .get(win_cond) - .ok_or(Error)?; + .ok_or(Error::UnknownOutcome)?; let witness = split_spend_info.witness_tx_reclaim( reclaim_tx, @@ -1246,7 +1275,7 @@ impl SignedContract { ) -> 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); + return Err(Error::InvalidKey); } // Confirm we're signing the correct input @@ -1295,7 +1324,7 @@ impl SignedContract { ) -> 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); + return Err(Error::InvalidKey); } // Confirm we're signing the correct input @@ -1303,7 +1332,11 @@ impl SignedContract { self.split_sellback_tx_input_and_prevout(win_cond)?; // 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( sellback_tx, @@ -1318,7 +1351,7 @@ impl SignedContract { .split_tx_build .split_spend_infos() .get(win_cond) - .ok_or(Error)?; + .ok_or(Error::UnknownOutcome)?; let witness = split_spend_info.witness_tx_sellback( sellback_tx, @@ -1360,7 +1393,7 @@ impl SignedContract { ) -> 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); + return Err(Error::InvalidKey); } // Confirm we're signing the correct input @@ -1368,7 +1401,11 @@ impl SignedContract { self.split_sellback_tx_input_and_prevout(win_cond)?; // 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( close_tx, @@ -1383,7 +1420,7 @@ impl SignedContract { .split_tx_build .split_spend_infos() .get(win_cond) - .ok_or(Error)?; + .ok_or(Error::UnknownOutcome)?; let witness = split_spend_info.witness_tx_close( close_tx, @@ -1407,23 +1444,29 @@ fn check_input_matches_expected>( expected_input: &TxIn, expected_prevout: &TxOut, ) -> 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 { - return Err(Error); + 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)?.borrow(), + 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)?; + return Err(Error::InvalidInput("prevout index mismatch")); } prevout.borrow() } }; if prevout != expected_prevout { - return Err(Error); + return Err(Error::InvalidInput("prevout does not match expected")); } Ok(()) diff --git a/src/serialization.rs b/src/serialization.rs index d72db98..e991edb 100644 --- a/src/serialization.rs +++ b/src/serialization.rs @@ -20,8 +20,12 @@ impl std::str::FromStr for Outcome { match s { "exp" => Ok(Outcome::Expiry), s => { - let index_str = s.strip_prefix("att").ok_or(Error)?; - let outcome_index = index_str.parse().map_err(|_| Error)?; + let index_str = s.strip_prefix("att").ok_or(Error::Conversion( + "invalid outcome format, expected 'att' prefix", + ))?; + let outcome_index = index_str + .parse::() + .map_err(Error::ParseOutcomeIndex)?; Ok(Outcome::Attestation(outcome_index)) } } @@ -72,10 +76,14 @@ impl std::str::FromStr for WinCondition { type Err = Error; fn from_str(s: &str) -> Result { - 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 player_index_str = suffix.strip_prefix("p").ok_or(Error)?; - let player_index = player_index_str.parse().map_err(|_| Error)?; + let player_index_str = suffix.strip_prefix("p").ok_or(Error::Conversion( + "invalid win condition format, expected 'p' prefix", + ))?; + let player_index = player_index_str.parse()?; Ok(WinCondition { outcome, player_index, diff --git a/src/spend_info/funding.rs b/src/spend_info/funding.rs index 61f3ca9..da0645c 100644 --- a/src/spend_info/funding.rs +++ b/src/spend_info/funding.rs @@ -103,7 +103,10 @@ impl FundingSpendInfo { if pubkey == mm_pubkey { Ok(market_maker_secret_key) } else { - player_secret_keys.get(&pubkey).ok_or(Error).copied() + player_secret_keys + .get(&pubkey) + .ok_or(Error::OutOfBoundsPlayerIndex) + .copied() } }) .collect::>()?; diff --git a/src/spend_info/outcome.rs b/src/spend_info/outcome.rs index 1f3d246..2009dc6 100644 --- a/src/spend_info/outcome.rs +++ b/src/spend_info/outcome.rs @@ -58,7 +58,7 @@ impl OutcomeSpendInfo { let winners: BTreeMap = winner_indexes .into_iter() .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())) }) .collect::>()?; @@ -216,7 +216,10 @@ impl OutcomeSpendInfo { script_pubkey: self.script_pubkey(), 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 sighash = SighashCache::new(split_tx).taproot_script_spend_signature_hash( @@ -238,12 +241,12 @@ impl OutcomeSpendInfo { let split_script = self .winner_split_scripts .get(player_index) - .ok_or(Error)? + .ok_or(Error::OutOfBoundsPlayerIndex)? .clone(); let control_block = self .spend_info .control_block(&(split_script.clone(), LeafVersion::TapScript)) - .ok_or(Error)?; + .ok_or(Error::InvalidKey)?; let mut witness = Witness::new(); witness.push(signature.serialize()); @@ -319,7 +322,12 @@ impl OutcomeSpendInfo { if pubkey == mm_pubkey { Ok(market_maker_secret_key) } 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::>()?;