Merge pull request #1650 from zcash/pczt-verifier
pczt: Add a Verifier role
This commit is contained in:
commit
80b3d89108
|
@ -2768,7 +2768,7 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "orchard"
|
name = "orchard"
|
||||||
version = "0.10.0"
|
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 = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"bitvec",
|
"bitvec",
|
||||||
|
@ -3798,7 +3798,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sapling-crypto"
|
name = "sapling-crypto"
|
||||||
version = "0.3.0"
|
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 = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"bellman",
|
"bellman",
|
||||||
|
|
|
@ -190,5 +190,5 @@ debug = true
|
||||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(zcash_unstable, values("zfuture"))'] }
|
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(zcash_unstable, values("zfuture"))'] }
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
orchard = { git = "https://github.com/zcash/orchard.git", rev = "bcd08e1d23e70c42a338f3e3f79d6f4c0c219805" }
|
orchard = { git = "https://github.com/zcash/orchard.git", rev = "3d951b4201a63f3c07cba8b179dd9abde142cf33" }
|
||||||
sapling-crypto = { git = "https://github.com/zcash/sapling-crypto.git", rev = "29cff9683cdf2f0c522ff3224081dfb4fbc80248" }
|
sapling-crypto = { git = "https://github.com/zcash/sapling-crypto.git", rev = "231f81911628499a8877be57e66e60c09e55bdea" }
|
||||||
|
|
|
@ -3,6 +3,8 @@ pub mod creator;
|
||||||
#[cfg(feature = "io-finalizer")]
|
#[cfg(feature = "io-finalizer")]
|
||||||
pub mod io_finalizer;
|
pub mod io_finalizer;
|
||||||
|
|
||||||
|
pub mod verifier;
|
||||||
|
|
||||||
pub mod updater;
|
pub mod updater;
|
||||||
|
|
||||||
#[cfg(feature = "prover")]
|
#[cfg(feature = "prover")]
|
||||||
|
|
|
@ -140,37 +140,16 @@ impl Signer {
|
||||||
.get_mut(index)
|
.get_mut(index)
|
||||||
.ok_or(Error::InvalidIndex)?;
|
.ok_or(Error::InvalidIndex)?;
|
||||||
|
|
||||||
// Check consistency of the input being signed.
|
// Check consistency of the input being signed if we have its note components.
|
||||||
let note_from_fields = spend
|
match spend.verify_nullifier(None) {
|
||||||
.recipient()
|
Err(
|
||||||
.zip(spend.value().as_ref())
|
sapling::pczt::VerifyError::MissingRecipient
|
||||||
.zip(spend.rseed().as_ref())
|
| sapling::pczt::VerifyError::MissingValue
|
||||||
.map(|((recipient, value), rseed)| {
|
| sapling::pczt::VerifyError::MissingRandomSeed,
|
||||||
sapling::Note::from_parts(recipient, *value, *rseed)
|
) => Ok(()),
|
||||||
});
|
r => r,
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.map_err(Error::SaplingVerify)?;
|
||||||
|
|
||||||
spend
|
spend
|
||||||
.sign(self.shielded_sighash, ask, OsRng)
|
.sign(self.shielded_sighash, ask, OsRng)
|
||||||
|
@ -201,39 +180,17 @@ impl Signer {
|
||||||
.get_mut(index)
|
.get_mut(index)
|
||||||
.ok_or(Error::InvalidIndex)?;
|
.ok_or(Error::InvalidIndex)?;
|
||||||
|
|
||||||
// Check consistency of the input being signed.
|
// Check consistency of the input being signed if we have its note components.
|
||||||
let note_from_fields = action
|
match action.spend().verify_nullifier(None) {
|
||||||
.spend()
|
Err(
|
||||||
.recipient()
|
orchard::pczt::VerifyError::MissingRecipient
|
||||||
.zip(action.spend().value().as_ref())
|
| orchard::pczt::VerifyError::MissingValue
|
||||||
.zip(action.spend().rho().as_ref())
|
| orchard::pczt::VerifyError::MissingRho
|
||||||
.zip(action.spend().rseed().as_ref())
|
| orchard::pczt::VerifyError::MissingRandomSeed,
|
||||||
.map(|(((recipient, value), rho), rseed)| {
|
) => Ok(()),
|
||||||
orchard::Note::from_parts(recipient, *value, *rho, *rseed)
|
r => r,
|
||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.map_err(Error::OrchardVerify)?;
|
||||||
|
|
||||||
action
|
action
|
||||||
.sign(self.shielded_sighash, ask, OsRng)
|
.sign(self.shielded_sighash, ask, OsRng)
|
||||||
|
@ -317,17 +274,14 @@ pub enum Error {
|
||||||
Global(GlobalError),
|
Global(GlobalError),
|
||||||
IncompatibleLockTimes,
|
IncompatibleLockTimes,
|
||||||
InvalidIndex,
|
InvalidIndex,
|
||||||
InvalidNote,
|
|
||||||
InvalidNullifier,
|
|
||||||
MissingFullViewingKey,
|
|
||||||
MissingProofGenerationKey,
|
|
||||||
MissingWitness,
|
|
||||||
OrchardExtract(orchard::pczt::TxExtractorError),
|
OrchardExtract(orchard::pczt::TxExtractorError),
|
||||||
OrchardParse(orchard::pczt::ParseError),
|
OrchardParse(orchard::pczt::ParseError),
|
||||||
OrchardSign(orchard::pczt::SignerError),
|
OrchardSign(orchard::pczt::SignerError),
|
||||||
|
OrchardVerify(orchard::pczt::VerifyError),
|
||||||
SaplingExtract(sapling::pczt::TxExtractorError),
|
SaplingExtract(sapling::pczt::TxExtractorError),
|
||||||
SaplingParse(sapling::pczt::ParseError),
|
SaplingParse(sapling::pczt::ParseError),
|
||||||
SaplingSign(sapling::pczt::SignerError),
|
SaplingSign(sapling::pczt::SignerError),
|
||||||
|
SaplingVerify(sapling::pczt::VerifyError),
|
||||||
TransparentExtract(transparent::pczt::TxExtractorError),
|
TransparentExtract(transparent::pczt::TxExtractorError),
|
||||||
TransparentParse(transparent::pczt::ParseError),
|
TransparentParse(transparent::pczt::ParseError),
|
||||||
TransparentSign(transparent::pczt::SignerError),
|
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;
|
mod parse;
|
||||||
pub use parse::ParseError;
|
pub use parse::ParseError;
|
||||||
|
|
||||||
|
mod verify;
|
||||||
|
pub use verify::VerifyError;
|
||||||
|
|
||||||
mod updater;
|
mod updater;
|
||||||
pub use updater::{InputUpdater, OutputUpdater, Updater, UpdaterError};
|
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