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>,
{
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<Amount, Error> {
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)
}

View File

@@ -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(())

View File

@@ -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::<Outcome, PartialSignature>::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<Outcome> = 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<BatchVerificationRow> = 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))
}

View File

@@ -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,
&params.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<WinCondition> = params
.win_conditions_claimable_by_pubkey(our_pubkey)
.ok_or(Error)?;
.ok_or(Error::InvalidKey)?;
let batch: Vec<BatchVerificationRow> = 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))
}

View File

@@ -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<musig2::errors::KeyAggError> for Error {
fn from(_: musig2::errors::KeyAggError) -> Self {
Error
fn from(e: musig2::errors::KeyAggError) -> Self {
Error::KeyAgg(e)
}
}
impl From<musig2::errors::TweakError> for Error {
fn from(_: musig2::errors::TweakError) -> Self {
Error
fn from(e: musig2::errors::TweakError) -> Self {
Error::Tweak(e)
}
}
impl From<musig2::errors::VerifyError> for Error {
fn from(_: musig2::errors::VerifyError) -> Self {
Error
fn from(e: musig2::errors::VerifyError) -> Self {
Error::Verify(e)
}
}
impl From<musig2::errors::SigningError> for Error {
fn from(_: musig2::errors::SigningError) -> Self {
Error
fn from(e: musig2::errors::SigningError) -> Self {
Error::Signing(e)
}
}
impl From<secp::errors::InvalidPointBytes> for Error {
fn from(_: secp::errors::InvalidPointBytes) -> Self {
Error
fn from(e: secp::errors::InvalidPointBytes) -> Self {
Error::InvalidPoint(e)
}
}
impl From<musig2::errors::InvalidSecretKeysError> for Error {
fn from(_: musig2::errors::InvalidSecretKeysError) -> Self {
Error
fn from(e: musig2::errors::InvalidSecretKeysError) -> Self {
Error::InvalidSecretKeys(e)
}
}
impl From<bitcoin::taproot::TaprootBuilderError> for Error {
fn from(_: bitcoin::taproot::TaprootBuilderError) -> Self {
Error
fn from(e: bitcoin::taproot::TaprootBuilderError) -> Self {
Error::TaprootBuilder(e)
}
}
impl From<bitcoin::taproot::IncompleteBuilderError> for Error {
fn from(_: bitcoin::taproot::IncompleteBuilderError) -> Self {
Error
fn from(e: bitcoin::taproot::IncompleteBuilderError) -> Self {
Error::IncompleteBuilder(e)
}
}
impl From<bitcoin::sighash::TaprootError> for Error {
fn from(_: bitcoin::sighash::TaprootError) -> Self {
Error
fn from(e: bitcoin::sighash::TaprootError) -> Self {
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 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<CoordinatorPartialSignatureSharingRound> {
signer_pubkey: Point,
partial_signatures: &SigMap<PartialSignature>,
) -> 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<T>(
) -> Result<(), Error> {
// Must receive signatures/nonces from all players and the market maker.
if !received_maps.contains_key(&params.market_maker.pubkey) {
return Err(Error);
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<T: Borrow<TxOut>>(
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(())

View File

@@ -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::<usize>()
.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<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 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,

View File

@@ -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::<Result<_, Error>>()?;

View File

@@ -58,7 +58,7 @@ impl OutcomeSpendInfo {
let winners: BTreeMap<PlayerIndex, &Player> = 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::<Result<_, Error>>()?;
@@ -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::<Result<_, Error>>()?;