diff --git a/Cargo.lock b/Cargo.lock index bc6c4590c..d5446b44b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2768,7 +2768,7 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "orchard" version = "0.10.0" -source = "git+https://github.com/zcash/orchard.git?rev=bcd08e1d23e70c42a338f3e3f79d6f4c0c219805#bcd08e1d23e70c42a338f3e3f79d6f4c0c219805" +source = "git+https://github.com/zcash/orchard.git?rev=3d951b4201a63f3c07cba8b179dd9abde142cf33#3d951b4201a63f3c07cba8b179dd9abde142cf33" dependencies = [ "aes", "bitvec", @@ -3798,7 +3798,7 @@ dependencies = [ [[package]] name = "sapling-crypto" version = "0.3.0" -source = "git+https://github.com/zcash/sapling-crypto.git?rev=29cff9683cdf2f0c522ff3224081dfb4fbc80248#29cff9683cdf2f0c522ff3224081dfb4fbc80248" +source = "git+https://github.com/zcash/sapling-crypto.git?rev=231f81911628499a8877be57e66e60c09e55bdea#231f81911628499a8877be57e66e60c09e55bdea" dependencies = [ "aes", "bellman", diff --git a/Cargo.toml b/Cargo.toml index 43f95c92c..3e64790f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -190,5 +190,5 @@ debug = true unexpected_cfgs = { level = "warn", check-cfg = ['cfg(zcash_unstable, values("zfuture"))'] } [patch.crates-io] -orchard = { git = "https://github.com/zcash/orchard.git", rev = "bcd08e1d23e70c42a338f3e3f79d6f4c0c219805" } -sapling-crypto = { git = "https://github.com/zcash/sapling-crypto.git", rev = "29cff9683cdf2f0c522ff3224081dfb4fbc80248" } +orchard = { git = "https://github.com/zcash/orchard.git", rev = "3d951b4201a63f3c07cba8b179dd9abde142cf33" } +sapling-crypto = { git = "https://github.com/zcash/sapling-crypto.git", rev = "231f81911628499a8877be57e66e60c09e55bdea" } diff --git a/pczt/src/roles.rs b/pczt/src/roles.rs index 711da8f7b..8361fd9ba 100644 --- a/pczt/src/roles.rs +++ b/pczt/src/roles.rs @@ -3,6 +3,8 @@ pub mod creator; #[cfg(feature = "io-finalizer")] pub mod io_finalizer; +pub mod verifier; + pub mod updater; #[cfg(feature = "prover")] diff --git a/pczt/src/roles/signer/mod.rs b/pczt/src/roles/signer/mod.rs index 61e041b6b..5923c6218 100644 --- a/pczt/src/roles/signer/mod.rs +++ b/pczt/src/roles/signer/mod.rs @@ -140,37 +140,16 @@ impl Signer { .get_mut(index) .ok_or(Error::InvalidIndex)?; - // Check consistency of the input being signed. - let note_from_fields = spend - .recipient() - .zip(spend.value().as_ref()) - .zip(spend.rseed().as_ref()) - .map(|((recipient, value), rseed)| { - sapling::Note::from_parts(recipient, *value, *rseed) - }); - - if let Some(note) = note_from_fields { - let tx_spend = self - .tx_data - .sapling_bundle() - .expect("index checked above") - .shielded_spends() - .get(index) - .expect("index checked above"); - - let proof_generation_key = spend - .proof_generation_key() - .as_ref() - .ok_or(Error::MissingProofGenerationKey)?; - - let nk = proof_generation_key.to_viewing_key().nk; - - let merkle_path = spend.witness().as_ref().ok_or(Error::MissingWitness)?; - - if ¬e.nf(&nk, merkle_path.position().into()) != tx_spend.nullifier() { - return Err(Error::InvalidNullifier); - } + // Check consistency of the input being signed if we have its note components. + match spend.verify_nullifier(None) { + Err( + sapling::pczt::VerifyError::MissingRecipient + | sapling::pczt::VerifyError::MissingValue + | sapling::pczt::VerifyError::MissingRandomSeed, + ) => Ok(()), + r => r, } + .map_err(Error::SaplingVerify)?; spend .sign(self.shielded_sighash, ask, OsRng) @@ -201,39 +180,17 @@ impl Signer { .get_mut(index) .ok_or(Error::InvalidIndex)?; - // Check consistency of the input being signed. - let note_from_fields = action - .spend() - .recipient() - .zip(action.spend().value().as_ref()) - .zip(action.spend().rho().as_ref()) - .zip(action.spend().rseed().as_ref()) - .map(|(((recipient, value), rho), rseed)| { - orchard::Note::from_parts(recipient, *value, *rho, *rseed) - .into_option() - .ok_or(Error::InvalidNote) - }) - .transpose()?; - - if let Some(note) = note_from_fields { - let tx_action = self - .tx_data - .orchard_bundle() - .expect("index checked above") - .actions() - .get(index) - .expect("index checked above"); - - let fvk = action - .spend() - .fvk() - .as_ref() - .ok_or(Error::MissingFullViewingKey)?; - - if ¬e.nullifier(fvk) != tx_action.nullifier() { - return Err(Error::InvalidNullifier); - } + // Check consistency of the input being signed if we have its note components. + match action.spend().verify_nullifier(None) { + Err( + orchard::pczt::VerifyError::MissingRecipient + | orchard::pczt::VerifyError::MissingValue + | orchard::pczt::VerifyError::MissingRho + | orchard::pczt::VerifyError::MissingRandomSeed, + ) => Ok(()), + r => r, } + .map_err(Error::OrchardVerify)?; action .sign(self.shielded_sighash, ask, OsRng) @@ -317,17 +274,14 @@ pub enum Error { Global(GlobalError), IncompatibleLockTimes, InvalidIndex, - InvalidNote, - InvalidNullifier, - MissingFullViewingKey, - MissingProofGenerationKey, - MissingWitness, OrchardExtract(orchard::pczt::TxExtractorError), OrchardParse(orchard::pczt::ParseError), OrchardSign(orchard::pczt::SignerError), + OrchardVerify(orchard::pczt::VerifyError), SaplingExtract(sapling::pczt::TxExtractorError), SaplingParse(sapling::pczt::ParseError), SaplingSign(sapling::pczt::SignerError), + SaplingVerify(sapling::pczt::VerifyError), TransparentExtract(transparent::pczt::TxExtractorError), TransparentParse(transparent::pczt::ParseError), TransparentSign(transparent::pczt::SignerError), diff --git a/pczt/src/roles/verifier/mod.rs b/pczt/src/roles/verifier/mod.rs new file mode 100644 index 000000000..fe2a66fc8 --- /dev/null +++ b/pczt/src/roles/verifier/mod.rs @@ -0,0 +1,32 @@ +use crate::Pczt; + +#[cfg(feature = "orchard")] +mod orchard; +#[cfg(feature = "orchard")] +pub use orchard::OrchardError; + +#[cfg(feature = "sapling")] +mod sapling; +#[cfg(feature = "sapling")] +pub use sapling::SaplingError; + +#[cfg(feature = "transparent")] +mod transparent; +#[cfg(feature = "transparent")] +pub use transparent::TransparentError; + +pub struct Verifier { + pczt: Pczt, +} + +impl Verifier { + /// Instantiates the Verifier role with the given PCZT. + pub fn new(pczt: Pczt) -> Self { + Self { pczt } + } + + /// Finishes the Verifier role, returning the updated PCZT. + pub fn finish(self) -> Pczt { + self.pczt + } +} diff --git a/pczt/src/roles/verifier/orchard.rs b/pczt/src/roles/verifier/orchard.rs new file mode 100644 index 000000000..5f5046a9d --- /dev/null +++ b/pczt/src/roles/verifier/orchard.rs @@ -0,0 +1,43 @@ +use crate::Pczt; + +impl super::Verifier { + /// Parses the Orchard bundle and then verifies it in the given closure. + pub fn with_orchard(self, f: F) -> Result> + where + F: FnOnce(&orchard::pczt::Bundle) -> Result<(), OrchardError>, + { + let Pczt { + global, + transparent, + sapling, + orchard, + } = self.pczt; + + let bundle = orchard.into_parsed().map_err(OrchardError::Parse)?; + + f(&bundle)?; + + Ok(Self { + pczt: Pczt { + global, + transparent, + sapling, + orchard: crate::orchard::Bundle::serialize_from(bundle), + }, + }) + } +} + +/// Errors that can occur while verifying the Orchard bundle of a PCZT. +#[derive(Debug)] +pub enum OrchardError { + Parse(orchard::pczt::ParseError), + Verify(orchard::pczt::VerifyError), + Custom(E), +} + +impl From for OrchardError { + fn from(e: orchard::pczt::VerifyError) -> Self { + OrchardError::Verify(e) + } +} diff --git a/pczt/src/roles/verifier/sapling.rs b/pczt/src/roles/verifier/sapling.rs new file mode 100644 index 000000000..36415aace --- /dev/null +++ b/pczt/src/roles/verifier/sapling.rs @@ -0,0 +1,43 @@ +use crate::Pczt; + +impl super::Verifier { + /// Parses the Sapling bundle and then verifies it in the given closure. + pub fn with_sapling(self, f: F) -> Result> + where + F: FnOnce(&sapling::pczt::Bundle) -> Result<(), SaplingError>, + { + let Pczt { + global, + transparent, + sapling, + orchard, + } = self.pczt; + + let bundle = sapling.into_parsed().map_err(SaplingError::Parser)?; + + f(&bundle)?; + + Ok(Self { + pczt: Pczt { + global, + transparent, + sapling: crate::sapling::Bundle::serialize_from(bundle), + orchard, + }, + }) + } +} + +/// Errors that can occur while verifying the Sapling bundle of a PCZT. +#[derive(Debug)] +pub enum SaplingError { + Parser(sapling::pczt::ParseError), + Verifier(sapling::pczt::VerifyError), + Custom(E), +} + +impl From for SaplingError { + fn from(e: sapling::pczt::VerifyError) -> Self { + SaplingError::Verifier(e) + } +} diff --git a/pczt/src/roles/verifier/transparent.rs b/pczt/src/roles/verifier/transparent.rs new file mode 100644 index 000000000..6084d8ff8 --- /dev/null +++ b/pczt/src/roles/verifier/transparent.rs @@ -0,0 +1,47 @@ +use zcash_primitives::transaction::components::transparent; + +use crate::Pczt; + +impl super::Verifier { + /// Parses the Transparent bundle and then verifies it in the given closure. + pub fn with_transparent(self, f: F) -> Result> + where + F: FnOnce(&transparent::pczt::Bundle) -> Result<(), TransparentError>, + { + let Pczt { + global, + transparent, + sapling, + orchard, + } = self.pczt; + + let bundle = transparent + .into_parsed() + .map_err(TransparentError::Parser)?; + + f(&bundle)?; + + Ok(Self { + pczt: Pczt { + global, + transparent: crate::transparent::Bundle::serialize_from(bundle), + sapling, + orchard, + }, + }) + } +} + +/// Errors that can occur while verifying the Transparent bundle of a PCZT. +#[derive(Debug)] +pub enum TransparentError { + Parser(transparent::pczt::ParseError), + Verifier(transparent::pczt::VerifyError), + Custom(E), +} + +impl From for TransparentError { + fn from(e: transparent::pczt::VerifyError) -> Self { + TransparentError::Verifier(e) + } +} diff --git a/zcash_primitives/src/transaction/components/transparent/pczt.rs b/zcash_primitives/src/transaction/components/transparent/pczt.rs index 72daa2a1a..12835808c 100644 --- a/zcash_primitives/src/transaction/components/transparent/pczt.rs +++ b/zcash_primitives/src/transaction/components/transparent/pczt.rs @@ -14,6 +14,9 @@ use crate::{ mod parse; pub use parse::ParseError; +mod verify; +pub use verify::VerifyError; + mod updater; pub use updater::{InputUpdater, OutputUpdater, Updater, UpdaterError}; diff --git a/zcash_primitives/src/transaction/components/transparent/pczt/verify.rs b/zcash_primitives/src/transaction/components/transparent/pczt/verify.rs new file mode 100644 index 000000000..74d7c2a89 --- /dev/null +++ b/zcash_primitives/src/transaction/components/transparent/pczt/verify.rs @@ -0,0 +1,65 @@ +use ripemd::Ripemd160; +use sha2::{Digest, Sha256}; + +use crate::legacy::TransparentAddress; + +impl super::Input { + /// Verifies the consistency of this transparent input. + /// + /// If the `redeem_script` field is set, its validity will be checked. + pub fn verify(&self) -> Result<(), VerifyError> { + match self.script_pubkey().address() { + Some(TransparentAddress::PublicKeyHash(_)) => { + if self.redeem_script().is_some() { + return Err(VerifyError::NotP2sh); + } + } + Some(TransparentAddress::ScriptHash(hash)) => { + if let Some(redeem_script) = self.redeem_script() { + if hash[..] != Ripemd160::digest(Sha256::digest(&redeem_script.0))[..] { + return Err(VerifyError::WrongRedeemScript); + } + } + } + None => return Err(VerifyError::UnsupportedScriptPubkey), + } + + Ok(()) + } +} + +impl super::Output { + /// Verifies the consistency of this transparent output. + /// + /// If the `redeem_script` field is set, its validity will be checked. + pub fn verify(&self) -> Result<(), VerifyError> { + match self.script_pubkey().address() { + Some(TransparentAddress::PublicKeyHash(_)) => { + if self.redeem_script().is_some() { + return Err(VerifyError::NotP2sh); + } + } + Some(TransparentAddress::ScriptHash(hash)) => { + if let Some(redeem_script) = self.redeem_script() { + if hash[..] != Ripemd160::digest(Sha256::digest(&redeem_script.0))[..] { + return Err(VerifyError::WrongRedeemScript); + } + } + } + None => return Err(VerifyError::UnsupportedScriptPubkey), + } + + Ok(()) + } +} + +/// Errors that can occur while verifying a PCZT bundle. +#[derive(Debug)] +pub enum VerifyError { + /// A `redeem_script` can only be set on a P2SH coin. + NotP2sh, + /// The `script_pubkey` kind is unsupported. + UnsupportedScriptPubkey, + /// The provided `redeem_script` does not match the input's `script_pubkey`. + WrongRedeemScript, +}