Skip to content

Commit

Permalink
pczt: Add a Verifier role
Browse files Browse the repository at this point in the history
This isn't a real role per se; it's instead a way for accessing the
parsed protocol-specific bundles for individual access and verification.
  • Loading branch information
str4d committed Dec 13, 2024
1 parent 205d4c9 commit f97f6fa
Show file tree
Hide file tree
Showing 10 changed files with 260 additions and 71 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
2 changes: 2 additions & 0 deletions pczt/src/roles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ pub mod creator;
#[cfg(feature = "io-finalizer")]
pub mod io_finalizer;

pub mod verifier;

pub mod updater;

#[cfg(feature = "prover")]
Expand Down
88 changes: 21 additions & 67 deletions pczt/src/roles/signer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 &note.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)
Expand Down Expand Up @@ -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 &note.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)
Expand Down Expand Up @@ -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),
Expand Down
32 changes: 32 additions & 0 deletions pczt/src/roles/verifier/mod.rs
Original file line number Diff line number Diff line change
@@ -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 {

Check warning on line 24 in pczt/src/roles/verifier/mod.rs

View check run for this annotation

Codecov / codecov/patch

pczt/src/roles/verifier/mod.rs#L24

Added line #L24 was not covered by tests
Self { pczt }
}

/// Finishes the Verifier role, returning the updated PCZT.
pub fn finish(self) -> Pczt {
self.pczt

Check warning on line 30 in pczt/src/roles/verifier/mod.rs

View check run for this annotation

Codecov / codecov/patch

pczt/src/roles/verifier/mod.rs#L29-L30

Added lines #L29 - L30 were not covered by tests
}
}
43 changes: 43 additions & 0 deletions pczt/src/roles/verifier/orchard.rs
Original file line number Diff line number Diff line change
@@ -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<E, F>(self, f: F) -> Result<Self, OrchardError<E>>
where
F: FnOnce(&orchard::pczt::Bundle) -> Result<(), OrchardError<E>>,
{
let Pczt {
global,
transparent,
sapling,
orchard,
} = self.pczt;

Check warning on line 14 in pczt/src/roles/verifier/orchard.rs

View check run for this annotation

Codecov / codecov/patch

pczt/src/roles/verifier/orchard.rs#L9-L14

Added lines #L9 - L14 were not covered by tests

let bundle = orchard.into_parsed().map_err(OrchardError::Parse)?;

Check warning on line 16 in pczt/src/roles/verifier/orchard.rs

View check run for this annotation

Codecov / codecov/patch

pczt/src/roles/verifier/orchard.rs#L16

Added line #L16 was not covered by tests

f(&bundle)?;

Check warning on line 18 in pczt/src/roles/verifier/orchard.rs

View check run for this annotation

Codecov / codecov/patch

pczt/src/roles/verifier/orchard.rs#L18

Added line #L18 was not covered by tests

Ok(Self {
pczt: Pczt {
global,
transparent,
sapling,
orchard: crate::orchard::Bundle::serialize_from(bundle),

Check warning on line 25 in pczt/src/roles/verifier/orchard.rs

View check run for this annotation

Codecov / codecov/patch

pczt/src/roles/verifier/orchard.rs#L20-L25

Added lines #L20 - L25 were not covered by tests
},
})
}
}

/// Errors that can occur while verifying the Orchard bundle of a PCZT.
#[derive(Debug)]
pub enum OrchardError<E> {
Parse(orchard::pczt::ParseError),
Verify(orchard::pczt::VerifyError),
Custom(E),
}

impl<E> From<orchard::pczt::VerifyError> for OrchardError<E> {
fn from(e: orchard::pczt::VerifyError) -> Self {
OrchardError::Verify(e)

Check warning on line 41 in pczt/src/roles/verifier/orchard.rs

View check run for this annotation

Codecov / codecov/patch

pczt/src/roles/verifier/orchard.rs#L40-L41

Added lines #L40 - L41 were not covered by tests
}
}
43 changes: 43 additions & 0 deletions pczt/src/roles/verifier/sapling.rs
Original file line number Diff line number Diff line change
@@ -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<E, F>(self, f: F) -> Result<Self, SaplingError<E>>
where
F: FnOnce(&sapling::pczt::Bundle) -> Result<(), SaplingError<E>>,
{
let Pczt {
global,
transparent,
sapling,
orchard,
} = self.pczt;

Check warning on line 14 in pczt/src/roles/verifier/sapling.rs

View check run for this annotation

Codecov / codecov/patch

pczt/src/roles/verifier/sapling.rs#L9-L14

Added lines #L9 - L14 were not covered by tests

let bundle = sapling.into_parsed().map_err(SaplingError::Parser)?;

Check warning on line 16 in pczt/src/roles/verifier/sapling.rs

View check run for this annotation

Codecov / codecov/patch

pczt/src/roles/verifier/sapling.rs#L16

Added line #L16 was not covered by tests

f(&bundle)?;

Check warning on line 18 in pczt/src/roles/verifier/sapling.rs

View check run for this annotation

Codecov / codecov/patch

pczt/src/roles/verifier/sapling.rs#L18

Added line #L18 was not covered by tests

Ok(Self {
pczt: Pczt {
global,
transparent,
sapling: crate::sapling::Bundle::serialize_from(bundle),
orchard,

Check warning on line 25 in pczt/src/roles/verifier/sapling.rs

View check run for this annotation

Codecov / codecov/patch

pczt/src/roles/verifier/sapling.rs#L20-L25

Added lines #L20 - L25 were not covered by tests
},
})
}
}

/// Errors that can occur while verifying the Sapling bundle of a PCZT.
#[derive(Debug)]
pub enum SaplingError<E> {
Parser(sapling::pczt::ParseError),
Verifier(sapling::pczt::VerifyError),
Custom(E),
}

impl<E> From<sapling::pczt::VerifyError> for SaplingError<E> {
fn from(e: sapling::pczt::VerifyError) -> Self {
SaplingError::Verifier(e)

Check warning on line 41 in pczt/src/roles/verifier/sapling.rs

View check run for this annotation

Codecov / codecov/patch

pczt/src/roles/verifier/sapling.rs#L40-L41

Added lines #L40 - L41 were not covered by tests
}
}
47 changes: 47 additions & 0 deletions pczt/src/roles/verifier/transparent.rs
Original file line number Diff line number Diff line change
@@ -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<E, F>(self, f: F) -> Result<Self, TransparentError<E>>
where
F: FnOnce(&transparent::pczt::Bundle) -> Result<(), TransparentError<E>>,
{
let Pczt {
global,
transparent,
sapling,
orchard,
} = self.pczt;

Check warning on line 16 in pczt/src/roles/verifier/transparent.rs

View check run for this annotation

Codecov / codecov/patch

pczt/src/roles/verifier/transparent.rs#L11-L16

Added lines #L11 - L16 were not covered by tests

let bundle = transparent

Check warning on line 18 in pczt/src/roles/verifier/transparent.rs

View check run for this annotation

Codecov / codecov/patch

pczt/src/roles/verifier/transparent.rs#L18

Added line #L18 was not covered by tests
.into_parsed()
.map_err(TransparentError::Parser)?;

Check warning on line 20 in pczt/src/roles/verifier/transparent.rs

View check run for this annotation

Codecov / codecov/patch

pczt/src/roles/verifier/transparent.rs#L20

Added line #L20 was not covered by tests

f(&bundle)?;

Check warning on line 22 in pczt/src/roles/verifier/transparent.rs

View check run for this annotation

Codecov / codecov/patch

pczt/src/roles/verifier/transparent.rs#L22

Added line #L22 was not covered by tests

Ok(Self {
pczt: Pczt {
global,
transparent: crate::transparent::Bundle::serialize_from(bundle),
sapling,
orchard,

Check warning on line 29 in pczt/src/roles/verifier/transparent.rs

View check run for this annotation

Codecov / codecov/patch

pczt/src/roles/verifier/transparent.rs#L24-L29

Added lines #L24 - L29 were not covered by tests
},
})
}
}

/// Errors that can occur while verifying the Transparent bundle of a PCZT.
#[derive(Debug)]
pub enum TransparentError<E> {
Parser(transparent::pczt::ParseError),
Verifier(transparent::pczt::VerifyError),
Custom(E),
}

impl<E> From<transparent::pczt::VerifyError> for TransparentError<E> {
fn from(e: transparent::pczt::VerifyError) -> Self {
TransparentError::Verifier(e)

Check warning on line 45 in pczt/src/roles/verifier/transparent.rs

View check run for this annotation

Codecov / codecov/patch

pczt/src/roles/verifier/transparent.rs#L44-L45

Added lines #L44 - L45 were not covered by tests
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down
Original file line number Diff line number Diff line change
@@ -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() {

Check warning on line 11 in zcash_primitives/src/transaction/components/transparent/pczt/verify.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/components/transparent/pczt/verify.rs#L10-L11

Added lines #L10 - L11 were not covered by tests
Some(TransparentAddress::PublicKeyHash(_)) => {
if self.redeem_script().is_some() {
return Err(VerifyError::NotP2sh);

Check warning on line 14 in zcash_primitives/src/transaction/components/transparent/pczt/verify.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/components/transparent/pczt/verify.rs#L13-L14

Added lines #L13 - L14 were not covered by tests
}
}
Some(TransparentAddress::ScriptHash(hash)) => {
if let Some(redeem_script) = self.redeem_script() {

Check warning on line 18 in zcash_primitives/src/transaction/components/transparent/pczt/verify.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/components/transparent/pczt/verify.rs#L17-L18

Added lines #L17 - L18 were not covered by tests
if hash[..] != Ripemd160::digest(Sha256::digest(&redeem_script.0))[..] {
return Err(VerifyError::WrongRedeemScript);

Check warning on line 20 in zcash_primitives/src/transaction/components/transparent/pczt/verify.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/components/transparent/pczt/verify.rs#L20

Added line #L20 was not covered by tests
}
}
}
None => return Err(VerifyError::UnsupportedScriptPubkey),

Check warning on line 24 in zcash_primitives/src/transaction/components/transparent/pczt/verify.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/components/transparent/pczt/verify.rs#L24

Added line #L24 was not covered by tests
}

Ok(())

Check warning on line 27 in zcash_primitives/src/transaction/components/transparent/pczt/verify.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/components/transparent/pczt/verify.rs#L27

Added line #L27 was not covered by tests
}
}

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

Check warning on line 36 in zcash_primitives/src/transaction/components/transparent/pczt/verify.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/components/transparent/pczt/verify.rs#L35-L36

Added lines #L35 - L36 were not covered by tests
Some(TransparentAddress::PublicKeyHash(_)) => {
if self.redeem_script().is_some() {
return Err(VerifyError::NotP2sh);

Check warning on line 39 in zcash_primitives/src/transaction/components/transparent/pczt/verify.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/components/transparent/pczt/verify.rs#L38-L39

Added lines #L38 - L39 were not covered by tests
}
}
Some(TransparentAddress::ScriptHash(hash)) => {
if let Some(redeem_script) = self.redeem_script() {

Check warning on line 43 in zcash_primitives/src/transaction/components/transparent/pczt/verify.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/components/transparent/pczt/verify.rs#L42-L43

Added lines #L42 - L43 were not covered by tests
if hash[..] != Ripemd160::digest(Sha256::digest(&redeem_script.0))[..] {
return Err(VerifyError::WrongRedeemScript);

Check warning on line 45 in zcash_primitives/src/transaction/components/transparent/pczt/verify.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/components/transparent/pczt/verify.rs#L45

Added line #L45 was not covered by tests
}
}
}
None => return Err(VerifyError::UnsupportedScriptPubkey),

Check warning on line 49 in zcash_primitives/src/transaction/components/transparent/pczt/verify.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/components/transparent/pczt/verify.rs#L49

Added line #L49 was not covered by tests
}

Ok(())

Check warning on line 52 in zcash_primitives/src/transaction/components/transparent/pczt/verify.rs

View check run for this annotation

Codecov / codecov/patch

zcash_primitives/src/transaction/components/transparent/pczt/verify.rs#L52

Added line #L52 was not covered by tests
}
}

/// 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,
}

0 comments on commit f97f6fa

Please sign in to comment.