Merge pull request #366 from zingolabs/add_concrete_errors_for_spend_and_output_construction

Add concrete error types for add_spend and add_output
This commit is contained in:
Kris Nuttycombe 2022-12-08 14:19:32 -07:00 committed by GitHub
commit 06cea3fa53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 92 additions and 26 deletions

View File

@ -6,6 +6,18 @@ and this project adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html). [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [Unreleased]
### Added
- `orchard::builder`:
- `SpendError`
- `OutputError`
### Changed
- `orchard::builder`:
- `Builder::{add_spend, add_output}` now use concrete error types instead of
`&'static str`s.
- `Error` has been renamed to `BuildError` to differentiate from new error
types.
- `BuildError` now implements `std::error::Error` and `std::fmt::Display`.
## [0.3.0] - 2022-10-19 ## [0.3.0] - 2022-10-19
### Added ### Added

View File

@ -2,6 +2,7 @@
use core::fmt; use core::fmt;
use core::iter; use core::iter;
use std::fmt::Display;
use ff::Field; use ff::Field;
use nonempty::NonEmpty; use nonempty::NonEmpty;
@ -28,7 +29,7 @@ const MIN_ACTIONS: usize = 2;
/// An error type for the kinds of errors that can occur during bundle construction. /// An error type for the kinds of errors that can occur during bundle construction.
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum BuildError {
/// A bundle could not be built because required signatures were missing. /// A bundle could not be built because required signatures were missing.
MissingSignatures, MissingSignatures,
/// An error occurred in the process of producing a proof for a bundle. /// An error occurred in the process of producing a proof for a bundle.
@ -43,15 +44,66 @@ pub enum Error {
DuplicateSignature, DuplicateSignature,
} }
impl From<halo2_proofs::plonk::Error> for Error { impl Display for BuildError {
fn from(e: halo2_proofs::plonk::Error) -> Self { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Error::Proof(e) use BuildError::*;
match self {
MissingSignatures => f.write_str("Required signatures were missing during build"),
Proof(e) => f.write_str(&format!("Could not create proof: {}", e.to_string())),
ValueSum(_) => f.write_str("Overflow occured during value construction"),
InvalidExternalSignature => f.write_str("External signature was invalid"),
DuplicateSignature => f.write_str("Signature valid for more than one input"),
}
} }
} }
impl From<value::OverflowError> for Error { impl std::error::Error for BuildError {}
/// An error type for adding a spend to the builder.
#[derive(Debug, PartialEq, Eq)]
pub enum SpendError {
/// Spends aren't enabled for this builder.
SpendsDisabled,
/// The anchor provided to this builder doesn't match the merkle path used to add a spend.
AnchorMismatch,
/// The full viewing key provided didn't match the note provided
FvkMismatch,
}
impl Display for SpendError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use SpendError::*;
f.write_str(match self {
SpendsDisabled => "Spends are not enabled for this builder",
AnchorMismatch => "All anchors must be equal.",
FvkMismatch => "FullViewingKey does not correspond to the given note",
})
}
}
impl std::error::Error for SpendError {}
/// The only error that can occur here is if outputs are disabled for this builder.
#[derive(Debug, PartialEq, Eq)]
pub struct OutputError;
impl Display for OutputError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Outputs are not enabled for this builder")
}
}
impl std::error::Error for OutputError {}
impl From<halo2_proofs::plonk::Error> for BuildError {
fn from(e: halo2_proofs::plonk::Error) -> Self {
BuildError::Proof(e)
}
}
impl From<value::OverflowError> for BuildError {
fn from(e: value::OverflowError) -> Self { fn from(e: value::OverflowError) -> Self {
Error::ValueSum(e) BuildError::ValueSum(e)
} }
} }
@ -243,23 +295,22 @@ impl Builder {
fvk: FullViewingKey, fvk: FullViewingKey,
note: Note, note: Note,
merkle_path: MerklePath, merkle_path: MerklePath,
) -> Result<(), &'static str> { ) -> Result<(), SpendError> {
if !self.flags.spends_enabled() { if !self.flags.spends_enabled() {
return Err("Spends are not enabled for this builder"); return Err(SpendError::SpendsDisabled);
} }
// Consistency check: all anchors must be equal. // Consistency check: all anchors must be equal.
let cm = note.commitment(); let cm = note.commitment();
let path_root: Anchor = let path_root = merkle_path.root(cm.into());
<Option<_>>::from(merkle_path.root(cm.into())).ok_or("Derived the bottom anchor")?;
if path_root != self.anchor { if path_root != self.anchor {
return Err("All anchors must be equal."); return Err(SpendError::AnchorMismatch);
} }
// Check if note is internal or external. // Check if note is internal or external.
let scope = fvk let scope = fvk
.scope_for_address(&note.recipient()) .scope_for_address(&note.recipient())
.ok_or("FullViewingKey does not correspond to the given note")?; .ok_or(SpendError::FvkMismatch)?;
self.spends.push(SpendInfo { self.spends.push(SpendInfo {
dummy_sk: None, dummy_sk: None,
@ -279,9 +330,9 @@ impl Builder {
recipient: Address, recipient: Address,
value: NoteValue, value: NoteValue,
memo: Option<[u8; 512]>, memo: Option<[u8; 512]>,
) -> Result<(), &'static str> { ) -> Result<(), OutputError> {
if !self.flags.outputs_enabled() { if !self.flags.outputs_enabled() {
return Err("Outputs are not enabled for this builder"); return Err(OutputError);
} }
self.recipients.push(RecipientInfo { self.recipients.push(RecipientInfo {
@ -326,7 +377,7 @@ impl Builder {
pub fn build<V: TryFrom<i64>>( pub fn build<V: TryFrom<i64>>(
mut self, mut self,
mut rng: impl RngCore, mut rng: impl RngCore,
) -> Result<Bundle<InProgress<Unproven, Unauthorized>, V>, Error> { ) -> Result<Bundle<InProgress<Unproven, Unauthorized>, V>, BuildError> {
// Pair up the spends and recipients, extending with dummy values as necessary. // Pair up the spends and recipients, extending with dummy values as necessary.
let pre_actions: Vec<_> = { let pre_actions: Vec<_> = {
let num_spends = self.spends.len(); let num_spends = self.spends.len();
@ -371,8 +422,8 @@ impl Builder {
.ok_or(OverflowError)?; .ok_or(OverflowError)?;
let result_value_balance: V = i64::try_from(value_balance) let result_value_balance: V = i64::try_from(value_balance)
.map_err(Error::ValueSum) .map_err(BuildError::ValueSum)
.and_then(|i| V::try_from(i).map_err(|_| Error::ValueSum(value::OverflowError)))?; .and_then(|i| V::try_from(i).map_err(|_| BuildError::ValueSum(value::OverflowError)))?;
// Compute the transaction binding signing key. // Compute the transaction binding signing key.
let bsk = pre_actions let bsk = pre_actions
@ -447,7 +498,7 @@ impl<S: InProgressSignatures, V> Bundle<InProgress<Unproven, S>, V> {
self, self,
pk: &ProvingKey, pk: &ProvingKey,
mut rng: impl RngCore, mut rng: impl RngCore,
) -> Result<Bundle<InProgress<Proof, S>, V>, Error> { ) -> Result<Bundle<InProgress<Proof, S>, V>, BuildError> {
let instances: Vec<_> = self let instances: Vec<_> = self
.actions() .actions()
.iter() .iter()
@ -522,10 +573,10 @@ pub enum MaybeSigned {
} }
impl MaybeSigned { impl MaybeSigned {
fn finalize(self) -> Result<redpallas::Signature<SpendAuth>, Error> { fn finalize(self) -> Result<redpallas::Signature<SpendAuth>, BuildError> {
match self { match self {
Self::Signature(sig) => Ok(sig), Self::Signature(sig) => Ok(sig),
_ => Err(Error::MissingSignatures), _ => Err(BuildError::MissingSignatures),
} }
} }
} }
@ -569,7 +620,7 @@ impl<V> Bundle<InProgress<Proof, Unauthorized>, V> {
mut rng: R, mut rng: R,
sighash: [u8; 32], sighash: [u8; 32],
signing_keys: &[SpendAuthorizingKey], signing_keys: &[SpendAuthorizingKey],
) -> Result<Bundle<Authorized, V>, Error> { ) -> Result<Bundle<Authorized, V>, BuildError> {
signing_keys signing_keys
.iter() .iter()
.fold(self.prepare(&mut rng, sighash), |partial, ask| { .fold(self.prepare(&mut rng, sighash), |partial, ask| {
@ -608,11 +659,14 @@ impl<P: fmt::Debug, V> Bundle<InProgress<P, PartiallyAuthorized>, V> {
pub fn append_signatures( pub fn append_signatures(
self, self,
signatures: &[redpallas::Signature<SpendAuth>], signatures: &[redpallas::Signature<SpendAuth>],
) -> Result<Self, Error> { ) -> Result<Self, BuildError> {
signatures.iter().try_fold(self, Self::append_signature) signatures.iter().try_fold(self, Self::append_signature)
} }
fn append_signature(self, signature: &redpallas::Signature<SpendAuth>) -> Result<Self, Error> { fn append_signature(
self,
signature: &redpallas::Signature<SpendAuth>,
) -> Result<Self, BuildError> {
let mut signature_valid_for = 0usize; let mut signature_valid_for = 0usize;
let bundle = self.map_authorization( let bundle = self.map_authorization(
&mut signature_valid_for, &mut signature_valid_for,
@ -632,9 +686,9 @@ impl<P: fmt::Debug, V> Bundle<InProgress<P, PartiallyAuthorized>, V> {
|_, partial| partial, |_, partial| partial,
); );
match signature_valid_for { match signature_valid_for {
0 => Err(Error::InvalidExternalSignature), 0 => Err(BuildError::InvalidExternalSignature),
1 => Ok(bundle), 1 => Ok(bundle),
_ => Err(Error::DuplicateSignature), _ => Err(BuildError::DuplicateSignature),
} }
} }
} }
@ -643,7 +697,7 @@ impl<V> Bundle<InProgress<Proof, PartiallyAuthorized>, V> {
/// Finalizes this bundle, enabling it to be included in a transaction. /// Finalizes this bundle, enabling it to be included in a transaction.
/// ///
/// Returns an error if any signatures are missing. /// Returns an error if any signatures are missing.
pub fn finalize(self) -> Result<Bundle<Authorized, V>, Error> { pub fn finalize(self) -> Result<Bundle<Authorized, V>, BuildError> {
self.try_map_authorization( self.try_map_authorization(
&mut (), &mut (),
|_, _, maybe| maybe.finalize(), |_, _, maybe| maybe.finalize(),