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:
parent
205d4c9303
commit
f97f6fa8b3
|
@ -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",
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -3,6 +3,8 @@ pub mod creator;
|
|||
#[cfg(feature = "io-finalizer")]
|
||||
pub mod io_finalizer;
|
||||
|
||||
pub mod verifier;
|
||||
|
||||
pub mod updater;
|
||||
|
||||
#[cfg(feature = "prover")]
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
Loading…
Reference in New Issue