pczt: Add a Verifier role

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.
This commit is contained in:
Jack Grigg 2024-12-12 13:34:31 +00:00
parent 205d4c9303
commit f97f6fa8b3
10 changed files with 260 additions and 71 deletions

4
Cargo.lock generated
View File

@ -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",

View File

@ -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" }

View File

@ -3,6 +3,8 @@ pub mod creator;
#[cfg(feature = "io-finalizer")]
pub mod io_finalizer;
pub mod verifier;
pub mod updater;
#[cfg(feature = "prover")]

View File

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

View File

@ -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
}
}

View File

@ -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;
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<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)
}
}

View File

@ -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;
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<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)
}
}

View File

@ -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;
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<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)
}
}

View File

@ -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};

View File

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