diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4e54ec66..e74c5c042 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,6 +76,8 @@ jobs: - name: Show Cargo.toml for the synthetic crate working-directory: ./ci-build run: cat Cargo.toml + - name: Copy pinned dependencies into synthetic crate + run: cp crate_root/Cargo.lock ci-build/ - name: Add target working-directory: ./ci-build run: rustup target add ${{ matrix.target }} diff --git a/.github/workflows/lints-stable.yml b/.github/workflows/lints-stable.yml index 9bf4c8ec6..10ff2eb34 100644 --- a/.github/workflows/lints-stable.yml +++ b/.github/workflows/lints-stable.yml @@ -53,6 +53,8 @@ jobs: - name: Show Cargo.toml for the synthetic crate working-directory: ./ci-build run: cat Cargo.toml + - name: Copy pinned dependencies into synthetic crate + run: cp crate_root/Cargo.lock ci-build/ - name: Add target working-directory: ./ci-build run: rustup target add ${{ matrix.target }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ee564427..96126a262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,56 @@ and this project adheres to Rust's notion of ## [Unreleased] +### Changed +- `orchard::pczt::Bundle::extract` now takes its `self` argument by + reference instead of by value. + +## [0.12.0] - 2025-12-05 + +### Added +- `orchard::pczt::Action::apply_signature` +- `orchard::value::BalanceError` +- `impl std::error::Error` for the following errors: + - `orchard::pczt`: + - `IoFinalizerError` + - `ParseError` + - `ProverError` + - `SignerError` + - `TxExtractorError` + - `UpdaterError` + - `VerifyError` + - `orchard::zip32::Error` + +### Changed +- `orchard::builder::BuildError::ValueSum` variant now contains + `orchard::value::BalanceError`. +- `orchard::pczt::SignerError` has added variants: + - `InvalidExternalSignature` +- All error enums in this crate are now `#[non_exhaustive]`, to allow future + error variants to be added without a SemVer break: + - `orchard::builder`: + - `BuildError` + - `SpendError` + - `orchard::pczt`: + - `IoFinalizerError` + - `ParseError` + - `ProverError` + - `SignerError` + - `TxExtractorError` + - `UpdaterError` + - `VerifyError` + - `orchard::zip32::Error` +- `orchard::builder::OutputError` has been changed from a zero-sized struct to + a `#[non_exhaustive]` enum with (for now) a single variant. + +### Removed +- `orchard::value::OverflowError` (use `BalanceError` instead). + +## [0.10.2] - 2025-05-08 + +### Fixed +- Fixes problems in test compilation under `--no-default-features` + ## [0.11.0] - 2025-02-20 ### Added diff --git a/Cargo.lock b/Cargo.lock index 3bca98a91..f63644de0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1014,8 +1014,9 @@ dependencies = [ [[package]] name = "halo2_proofs" -version = "0.3.1" -source = "git+https://github.com/zcash/halo2?rev=2308caf68c48c02468b66cfc452dad54e355e32f#2308caf68c48c02468b66cfc452dad54e355e32f" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05713f117155643ce10975e0bee44a274bcda2f4bb5ef29a999ad67c1fa8d4d3" dependencies = [ "blake2b_simd", "ff", @@ -1437,7 +1438,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "orchard" -version = "0.11.0" +version = "0.12.0" dependencies = [ "aes", "bitvec", @@ -2648,3 +2649,8 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] + +[[patch.unused]] +name = "halo2_proofs" +version = "0.3.1" +source = "git+https://github.com/zcash/halo2?rev=2308caf68c48c02468b66cfc452dad54e355e32f#2308caf68c48c02468b66cfc452dad54e355e32f" diff --git a/Cargo.toml b/Cargo.toml index 1e283b71c..368e0c9a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "orchard" -version = "0.11.0" +version = "0.12.0" authors = [ - "Sean Bowe ", + "Sean Bowe", "Jack Grigg ", "Daira-Emma Hopwood ", "Ying Tong Lai", @@ -42,7 +42,7 @@ nonempty = { version = "0.11", default-features = false } poseidon = { package = "halo2_poseidon", version = "0.1" } serde = { version = "1.0", default-features = false, features = ["derive"] } sinsemilla = "0.1" -subtle = { version = "2.3", default-features = false } +subtle = { version = "2.6", default-features = false } zcash_note_encryption = "0.4" incrementalmerkletree = "0.8.1" zcash_spec = "0.2.1" diff --git a/src/builder.rs b/src/builder.rs index 33b1d8f99..2fe3498b8 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -24,7 +24,7 @@ use crate::{ primitives::{OrchardDomain, OrchardPrimitives}, sighash_kind::{OrchardBindingSig, OrchardSighashKind, OrchardSpendAuthSig}, tree::{Anchor, MerklePath}, - value::{self, NoteValue, OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum}, + value::{self, BalanceError, NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum}, Proof, }; @@ -129,6 +129,7 @@ impl BundleType { /// An error type for the kinds of errors that can occur during bundle construction. #[derive(Debug)] +#[non_exhaustive] pub enum BuildError { /// Spends are disabled for the provided bundle type. SpendsDisabled, @@ -143,7 +144,7 @@ pub enum BuildError { Proof(halo2_proofs::plonk::Error), /// An overflow error occurred while attempting to construct the value /// for a bundle. - ValueSum(value::OverflowError), + ValueSum(value::BalanceError), /// External signature is not valid. InvalidExternalSignature, /// A signature is valid for more than one input. This should never happen if `alpha` @@ -191,14 +192,15 @@ impl From for BuildError { } } -impl From for BuildError { - fn from(e: value::OverflowError) -> Self { +impl From for BuildError { + fn from(e: value::BalanceError) -> Self { BuildError::ValueSum(e) } } /// An error type for adding a spend to the builder. #[derive(Debug, PartialEq, Eq)] +#[non_exhaustive] pub enum SpendError { /// Spends aren't enabled for this builder. SpendsDisabled, @@ -222,13 +224,20 @@ impl fmt::Display for SpendError { #[cfg(feature = "std")] impl std::error::Error for SpendError {} -/// The only error that can occur here is if outputs are disabled for this builder. +/// An error type for adding an output to the builder. #[derive(Debug, PartialEq, Eq)] -pub struct OutputError; +#[non_exhaustive] +pub enum OutputError { + /// Outputs aren't enabled for this builder. + OutputsDisabled, +} impl fmt::Display for OutputError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Outputs are not enabled for this builder") + use OutputError::*; + f.write_str(match self { + OutputsDisabled => "Outputs are not enabled for this builder", + }) } } @@ -673,7 +682,7 @@ impl Builder { ) -> Result<(), OutputError> { let flags = self.bundle_type.flags(); if !flags.outputs_enabled() { - return Err(OutputError); + return Err(OutputError::OutputsDisabled); } self.outputs @@ -730,7 +739,7 @@ impl Builder { /// /// [added]: https://zips.z.cash/protocol/protocol.pdf#orchardbalance /// [must not have a negative value]: https://zips.z.cash/protocol/protocol.pdf#transactions - pub fn value_balance>(&self) -> Result { + pub fn value_balance>(&self) -> Result { let value_balance = self .spends .iter() @@ -743,8 +752,9 @@ impl Builder { .map(|output| NoteValue::zero() - output.value), ) .try_fold(ValueSum::zero(), |acc, note_value| acc + note_value) - .ok_or(OverflowError)?; - i64::try_from(value_balance).and_then(|i| V::try_from(i).map_err(|_| value::OverflowError)) + .ok_or(BalanceError::Overflow)?; + i64::try_from(value_balance) + .and_then(|i| V::try_from(i).map_err(|_| value::BalanceError::Overflow)) } /// Builds a bundle containing the given spent notes and outputs. @@ -888,7 +898,7 @@ pub fn bundle, FL: OrchardFlavor>( i64::try_from(value_balance).map_err(BuildError::ValueSum)?; let result_value_balance = V::try_from(zatoshi_value_balance) - .map_err(|_| BuildError::ValueSum(value::OverflowError))?; + .map_err(|_| BuildError::ValueSum(value::BalanceError::Overflow))?; // Compute the transaction binding signing key. let bsk = pre_actions @@ -1076,7 +1086,7 @@ fn build_bundle( .iter() .filter(|action| action.spend.note.asset().is_zatoshi().into()) .try_fold(ValueSum::zero(), |acc, action| acc + action.value_sum()) - .ok_or(OverflowError)?; + .ok_or(BalanceError::Overflow)?; let burn_vec = burn.into_iter().collect(); diff --git a/src/pczt.rs b/src/pczt.rs index f55c855b4..dd59ca95e 100644 --- a/src/pczt.rs +++ b/src/pczt.rs @@ -500,6 +500,10 @@ mod tests { for action in pczt_bundle.actions_mut() { if action.spend.zip32_derivation.as_ref() == Some(&zip32_derivation) { action.sign(sighash, &ask, rng).unwrap(); + + // We can also apply the signature as an external signature. + let signature = action.spend().spend_auth_sig().clone().expect("signed"); + action.apply_signature(sighash, signature).unwrap(); } } diff --git a/src/pczt/io_finalizer.rs b/src/pczt/io_finalizer.rs index 7474d428e..dd97997ef 100644 --- a/src/pczt/io_finalizer.rs +++ b/src/pczt/io_finalizer.rs @@ -1,3 +1,5 @@ +use core::fmt; + use alloc::vec::Vec; use rand::{CryptoRng, RngCore}; @@ -61,6 +63,7 @@ impl super::Bundle { /// Errors that can occur while finalizing the I/O for a PCZT bundle. #[derive(Debug)] +#[non_exhaustive] pub enum IoFinalizerError { /// An error occurred while signing a dummy spend. DummySignature(SignerError), @@ -70,3 +73,24 @@ pub enum IoFinalizerError { /// inconsistent. ValueCommitMismatch, } + +impl fmt::Display for IoFinalizerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + IoFinalizerError::DummySignature(e) => { + write!(f, "An error occurred while signing a dummy spend: {e}") + } + IoFinalizerError::MissingValueCommitTrapdoor => write!( + f, + "The IO Finalizer role requires all `rcv` fields to be set" + ), + IoFinalizerError::ValueCommitMismatch => write!( + f, + "`cv_net`, `rcv`, and `value_sum` within the Orchard bundle are inconsistent." + ), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for IoFinalizerError {} diff --git a/src/pczt/parse.rs b/src/pczt/parse.rs index fa98c98cf..2009b278d 100644 --- a/src/pczt/parse.rs +++ b/src/pczt/parse.rs @@ -1,3 +1,5 @@ +use core::fmt; + use alloc::collections::BTreeMap; use alloc::string::String; use alloc::vec::Vec; @@ -298,6 +300,7 @@ impl Zip32Derivation { /// Errors that can occur while parsing a PCZT bundle. #[derive(Debug)] +#[non_exhaustive] pub enum ParseError { /// An invalid anchor was provided. InvalidAnchor, @@ -338,3 +341,34 @@ pub enum ParseError { /// The provided `flags` field had unexpected bits set. UnexpectedFlagBitsSet, } + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ParseError::InvalidAnchor => write!(f, "invalid anchor"), + ParseError::InvalidBindingSignatureSigningKey => write!(f, "invalid `bsk`"), + ParseError::InvalidDummySpendingKey => write!(f, "invalid `dummy_sk`"), + ParseError::InvalidEncCiphertext => write!(f, "invalid `enc_ciphertext`"), + ParseError::InvalidExtractedNoteCommitment => write!(f, "invalid `cmx`"), + ParseError::InvalidFullViewingKey => write!(f, "invalid `fvk`"), + ParseError::InvalidNullifier => write!(f, "invalid `nullifier`"), + ParseError::InvalidOutCiphertext => write!(f, "invalid `out_ciphertext`"), + ParseError::InvalidRandomizedKey => write!(f, "invalid `rk`"), + ParseError::InvalidRandomSeed => write!(f, "invalid `rseed`"), + ParseError::InvalidRecipient => write!(f, "invalid `recipient`"), + ParseError::InvalidRho => write!(f, "invalid `rho`"), + ParseError::InvalidSpendAuthRandomizer => write!(f, "invalid `alpha`"), + ParseError::InvalidValueCommitment => write!(f, "invalid `cv_net`"), + ParseError::InvalidValueCommitTrapdoor => write!(f, "invalid `rcv`"), + ParseError::InvalidWitness => write!(f, "invalid `witness`"), + ParseError::InvalidZip32Derivation => write!(f, "invalid `zip32_derivation`"), + ParseError::MissingRho => { + write!(f, "`rho` must be provided whenever `rseed` is provided") + } + ParseError::UnexpectedFlagBitsSet => write!(f, "`flags` field had unexpected bits set"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseError {} diff --git a/src/pczt/prover.rs b/src/pczt/prover.rs index 364718d2b..8caeaea45 100644 --- a/src/pczt/prover.rs +++ b/src/pczt/prover.rs @@ -1,3 +1,5 @@ +use core::fmt; + use alloc::vec::Vec; use halo2_proofs::plonk; @@ -114,6 +116,7 @@ impl super::Bundle { /// Errors that can occur while creating Orchard proofs for a PCZT. #[derive(Debug)] +#[non_exhaustive] pub enum ProverError { /// The output note's components do not produce a valid note commitment. InvalidOutputNote, @@ -142,3 +145,40 @@ pub enum ProverError { /// The provided `fvk` does not own the spent note. WrongFvkForNote, } + +impl fmt::Display for ProverError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ProverError::InvalidOutputNote => write!(f, "output note is invalid"), + ProverError::InvalidSpendNote => write!(f, "spent note is invalid"), + ProverError::MissingFullViewingKey => { + write!(f, "`fvk` must be set for the Prover role") + } + ProverError::MissingRandomSeed => { + write!(f, "`rseed` fields must be set for the Prover role") + } + ProverError::MissingRecipient => { + write!(f, "`recipient` fields must be set for the Prover role") + } + ProverError::MissingRho => write!(f, "`rho` must be set for the Prover role"), + ProverError::MissingSpendAuthRandomizer => { + write!(f, "`alpha` must be set for the Prover role") + } + ProverError::MissingValue => { + write!(f, "`value` fields must be set for the Prover role") + } + ProverError::MissingValueCommitTrapdoor => { + write!(f, "`rcv` must be set for the Prover role") + } + ProverError::MissingWitness => write!(f, "`witness` must be set for the Prover role"), + ProverError::ProofFailed(e) => write!(f, "Failed to create proof: {e}"), + ProverError::RhoMismatch => { + write!(f, "output's `rho` does not match spent note's nullifier") + } + ProverError::WrongFvkForNote => write!(f, "`fvk` does not own the action's spent note"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ProverError {} diff --git a/src/pczt/signer.rs b/src/pczt/signer.rs index 7f5f8028c..f31977710 100644 --- a/src/pczt/signer.rs +++ b/src/pczt/signer.rs @@ -1,6 +1,11 @@ +use core::fmt; + use rand::{CryptoRng, RngCore}; -use crate::{keys::SpendAuthorizingKey, primitives::redpallas}; +use crate::{ + keys::SpendAuthorizingKey, + primitives::redpallas::{self, SpendAuth}, +}; impl super::Action { /// Signs the Orchard spend with the given spend authorizing key. @@ -29,13 +34,53 @@ impl super::Action { Err(SignerError::WrongSpendAuthorizingKey) } } + + /// Applies the given signature to the Orchard spend, if valid. + /// + /// It is the caller's responsibility to perform any semantic validity checks on the + /// PCZT (for example, comfirming that the change amounts are correct) before calling + /// this method. + pub fn apply_signature( + &mut self, + sighash: [u8; 32], + signature: redpallas::Signature, + ) -> Result<(), SignerError> { + if self.spend.rk.verify(&sighash, &signature).is_ok() { + self.spend.spend_auth_sig = Some(signature); + Ok(()) + } else { + Err(SignerError::InvalidExternalSignature) + } + } } /// Errors that can occur while signing an Orchard action in a PCZT. #[derive(Debug)] +#[non_exhaustive] pub enum SignerError { + /// A provided external signature was not valid for the action's spend. + InvalidExternalSignature, /// The Signer role requires `alpha` to be set. MissingSpendAuthRandomizer, /// The provided `ask` does not own the action's spent note. WrongSpendAuthorizingKey, } + +impl fmt::Display for SignerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SignerError::InvalidExternalSignature => { + write!(f, "External signature is invalid for the action's spend") + } + SignerError::MissingSpendAuthRandomizer => { + write!(f, "`alpha` must be set for the Signer role") + } + SignerError::WrongSpendAuthorizingKey => { + write!(f, "provided `ask` does not own the action's spent note") + } + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for SignerError {} diff --git a/src/pczt/tx_extractor.rs b/src/pczt/tx_extractor.rs index 4ca47d4ed..1b17634fb 100644 --- a/src/pczt/tx_extractor.rs +++ b/src/pczt/tx_extractor.rs @@ -1,3 +1,5 @@ +use core::fmt; + use nonempty::NonEmpty; use rand::{CryptoRng, RngCore}; @@ -28,7 +30,7 @@ impl super::Bundle { /// /// [regular `Bundle`]: crate::Bundle pub fn extract>( - self, + &self, ) -> Result>, TxExtractorError> { self.to_tx_data( |action| { @@ -107,6 +109,7 @@ impl super::Bundle { /// Errors that can occur while extracting a regular Orchard bundle from a PCZT bundle. #[derive(Debug)] +#[non_exhaustive] pub enum TxExtractorError { /// The Transaction Extractor role requires `bsk` to be set. MissingBindingSignatureSigningKey, @@ -118,6 +121,30 @@ pub enum TxExtractorError { ValueSumOutOfRange, } +impl fmt::Display for TxExtractorError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TxExtractorError::MissingBindingSignatureSigningKey => { + write!(f, "`bsk` must be set for the Transaction Extractor role") + } + TxExtractorError::MissingProof => write!( + f, + "Orchard `zkproof` must be set for the Transaction Extractor role" + ), + TxExtractorError::MissingSpendAuthSig => write!( + f, + "`spend_auth_sig` fields must all be set for the Transaction Extractor role" + ), + TxExtractorError::ValueSumOutOfRange => { + write!(f, "value sum does not fit into a `valueBalance`") + } + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TxExtractorError {} + /// Authorizing data for a bundle of actions that is just missing a binding signature. #[derive(Debug)] pub struct Unbound { diff --git a/src/pczt/updater.rs b/src/pczt/updater.rs index 5d032b1f0..b1214af44 100644 --- a/src/pczt/updater.rs +++ b/src/pczt/updater.rs @@ -1,3 +1,5 @@ +use core::fmt; + use alloc::string::String; use alloc::vec::Vec; @@ -71,7 +73,19 @@ impl ActionUpdater<'_> { /// Errors that can occur while updating an Orchard bundle in a PCZT. #[derive(Debug)] +#[non_exhaustive] pub enum UpdaterError { /// An out-of-bounds index was provided when looking up an action. InvalidIndex, } + +impl fmt::Display for UpdaterError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + UpdaterError::InvalidIndex => write!(f, "Action index is out-of-bounds"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for UpdaterError {} diff --git a/src/pczt/verify.rs b/src/pczt/verify.rs index 24f3995a3..9a9146ccb 100644 --- a/src/pczt/verify.rs +++ b/src/pczt/verify.rs @@ -1,3 +1,5 @@ +use core::fmt; + use crate::{ keys::{FullViewingKey, SpendValidatingKey}, note::{AssetBase, ExtractedNoteCommitment, Rho}, @@ -145,6 +147,7 @@ impl super::Output { /// Errors that can occur while verifying a PCZT bundle. #[derive(Debug)] +#[non_exhaustive] pub enum VerifyError { /// The output note's components do not produce the expected `cmx`. InvalidExtractedNoteCommitment, @@ -177,3 +180,44 @@ pub enum VerifyError { /// The provided `fvk` does not own the spent note. WrongFvkForNote, } + +impl fmt::Display for VerifyError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + VerifyError::InvalidExtractedNoteCommitment => { + write!(f, "output note doesn't match `cmx`") + } + VerifyError::InvalidNullifier => write!(f, "spent note doesn't match `nullifier`"), + VerifyError::InvalidOutputNote => write!(f, "invalid output note"), + VerifyError::InvalidRandomizedVerificationKey => { + write!(f, "spend's `fvk` and `alpha` do not match `rk`") + } + VerifyError::InvalidSpendNote => write!(f, "invalid spent note"), + VerifyError::InvalidValueCommitment => { + write!(f, "`cv_net` doesn't match the note values and `rcv`") + } + VerifyError::MismatchedFullViewingKey => { + write!(f, "Provided full viewing key doesn't match the `fvk` field") + } + VerifyError::MissingFullViewingKey => write!(f, "`fvk` missing for dummy note"), + VerifyError::MissingRandomSeed => { + write!(f, "`rseed` missing for `nullifier` verification") + } + VerifyError::MissingRecipient => { + write!(f, "`recipient` missing for `nullifier` verification") + } + VerifyError::MissingRho => write!(f, "`rho` missing for `nullifier` verification"), + VerifyError::MissingSpendAuthRandomizer => { + write!(f, "`alpha` missing for `rk` verification") + } + VerifyError::MissingValue => write!(f, "`value` missing"), + VerifyError::MissingValueCommitTrapdoor => { + write!(f, "`rcv` missing for `cv_net` verification") + } + VerifyError::WrongFvkForNote => write!(f, "`fvk` does not own the action's spent note"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for VerifyError {} diff --git a/src/spec.rs b/src/spec.rs index 4a51f2820..c7194562b 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -326,6 +326,7 @@ mod tests { use group::Group; use halo2_proofs::arithmetic::CurveExt; use pasta_curves::pallas; + assert!(!bool::from( pallas::Point::hash_to_curve("z.cash:Orchard-gd")(&[]).is_identity() )); diff --git a/src/value.rs b/src/value.rs index 85967e482..281814518 100644 --- a/src/value.rs +++ b/src/value.rs @@ -71,18 +71,29 @@ pub const MAX_NOTE_VALUE: u64 = u64::MAX; pub const VALUE_SUM_RANGE: RangeInclusive = -(MAX_NOTE_VALUE as i128)..=MAX_NOTE_VALUE as i128; -/// A value operation overflowed. +/// A type for balance violations in amount addition and subtraction +/// (overflow and underflow of allowed ranges). #[derive(Debug)] -pub struct OverflowError; +#[non_exhaustive] +pub enum BalanceError { + /// Two values were added or subtracted, and the result overflowed the valid range for + /// the value. + /// + /// Normally this range is [`VALUE_SUM_RANGE`], but when interacting with value + /// balances it may be `i64`. + Overflow, +} -impl fmt::Display for OverflowError { +impl fmt::Display for BalanceError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Orchard value operation overflowed") + match self { + Self::Overflow => write!(f, "Orchard value operation overflowed"), + } } } #[cfg(feature = "std")] -impl std::error::Error for OverflowError {} +impl std::error::Error for BalanceError {} /// The non-negative value of an individual Orchard note. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] @@ -226,7 +237,7 @@ impl ValueSum { pub(crate) fn into_value_balance>(self) -> Result { i64::try_from(self) .map_err(BuildError::ValueSum) - .and_then(|i| V::try_from(i).map_err(|_| BuildError::ValueSum(OverflowError))) + .and_then(|i| V::try_from(i).map_err(|_| BuildError::ValueSum(BalanceError::Overflow))) } } @@ -241,25 +252,25 @@ impl Add for ValueSum { } } -impl<'a> Sum<&'a ValueSum> for Result { +impl<'a> Sum<&'a ValueSum> for Result { fn sum>(mut iter: I) -> Self { iter.try_fold(ValueSum(0), |acc, v| acc + *v) - .ok_or(OverflowError) + .ok_or(BalanceError::Overflow) } } -impl Sum for Result { +impl Sum for Result { fn sum>(mut iter: I) -> Self { iter.try_fold(ValueSum(0), |acc, v| acc + v) - .ok_or(OverflowError) + .ok_or(BalanceError::Overflow) } } impl TryFrom for i64 { - type Error = OverflowError; + type Error = BalanceError; fn try_from(v: ValueSum) -> Result { - i64::try_from(v.0).map_err(|_| OverflowError) + i64::try_from(v.0).map_err(|_| BalanceError::Overflow) } } @@ -494,7 +505,7 @@ mod tests { use super::{ testing::{arb_note_value_bounded, arb_trapdoor, arb_value_sum_bounded}, - OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum, MAX_NOTE_VALUE, + ValueCommitTrapdoor, ValueCommitment, ValueSum, MAX_NOTE_VALUE, }; use crate::{ note::asset_base::testing::arb_asset_base, note::AssetBase, primitives::redpallas, @@ -529,7 +540,7 @@ mod tests { let sum = four_values .iter() .cloned() - .sum::>() + .sum::>() .expect("we generate values that won't overflow"); let fifth = negate_value_sum(sum); @@ -555,7 +566,7 @@ mod tests { let zatoshi_value_balance = zatoshi_values .iter() .map(|(value, _)| value) - .sum::>() + .sum::>() .expect("we generate values that won't overflow"); let zatoshi_values_with_asset: Vec<(ValueSum, ValueCommitTrapdoor, AssetBase)> = diff --git a/src/zip32.rs b/src/zip32.rs index 6fee2d104..b1ffaf2f8 100644 --- a/src/zip32.rs +++ b/src/zip32.rs @@ -22,6 +22,7 @@ const ZIP32_ORCHARD_FVFP_PERSONALIZATION: &[u8; 16] = b"ZcashOrchardFVFP"; /// Errors produced in derivation of extended spending keys #[derive(Debug, PartialEq, Eq)] +#[non_exhaustive] pub enum Error { /// A seed resulted in an invalid spending key InvalidSpendingKey, @@ -35,7 +36,8 @@ impl fmt::Display for Error { } } -//impl std::error::Error for Error {} +#[cfg(feature = "std")] +impl std::error::Error for Error {} /// An Orchard full viewing key fingerprint struct FvkFingerprint([u8; 32]);