Skip to content

Commit 1c42667

Browse files
authored
Merge pull request #443 from zcash/pczt-verifier
Add methods for validating aspects of PCZT bundles
2 parents 5c451be + 9b99350 commit 1c42667

File tree

2 files changed

+178
-0
lines changed

2 files changed

+178
-0
lines changed

src/pczt.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ use crate::{
2121
mod parse;
2222
pub use parse::ParseError;
2323

24+
mod verify;
25+
pub use verify::VerifyError;
26+
2427
mod io_finalizer;
2528
pub use io_finalizer::IoFinalizerError;
2629

src/pczt/verify.rs

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
use crate::{
2+
keys::{FullViewingKey, SpendValidatingKey},
3+
note::{ExtractedNoteCommitment, Rho},
4+
value::ValueCommitment,
5+
Note,
6+
};
7+
8+
impl super::Action {
9+
/// Verifies that the `cv_net` field is consistent with the note fields.
10+
///
11+
/// Requires that the following optional fields are set:
12+
/// - `spend.value`
13+
/// - `output.value`
14+
/// - `rcv`
15+
pub fn verify_cv_net(&self) -> Result<(), VerifyError> {
16+
let spend_value = self.spend().value.ok_or(VerifyError::MissingValue)?;
17+
let output_value = self.output().value.ok_or(VerifyError::MissingValue)?;
18+
let rcv = self
19+
.rcv
20+
.clone()
21+
.ok_or(VerifyError::MissingValueCommitTrapdoor)?;
22+
23+
let cv_net = ValueCommitment::derive(spend_value - output_value, rcv);
24+
if cv_net.to_bytes() == self.cv_net.to_bytes() {
25+
Ok(())
26+
} else {
27+
Err(VerifyError::InvalidValueCommitment)
28+
}
29+
}
30+
}
31+
32+
impl super::Spend {
33+
/// Returns the [`FullViewingKey`] to use when validating this note.
34+
///
35+
/// Handles dummy notes when the `value` field is set.
36+
fn fvk_for_validation<'a>(
37+
&'a self,
38+
expected_fvk: Option<&'a FullViewingKey>,
39+
) -> Result<&'a FullViewingKey, VerifyError> {
40+
match (expected_fvk, self.fvk.as_ref(), self.value.as_ref()) {
41+
(Some(expected_fvk), Some(fvk), _) if fvk == expected_fvk => Ok(fvk),
42+
// `expected_fvk` is ignored if the spent note is a dummy note.
43+
(Some(_), Some(fvk), Some(value)) if value.inner() == 0 => Ok(fvk),
44+
(Some(_), Some(_), _) => Err(VerifyError::MismatchedFullViewingKey),
45+
(Some(expected_fvk), None, _) => Ok(expected_fvk),
46+
(None, Some(fvk), _) => Ok(fvk),
47+
(None, None, _) => Err(VerifyError::MissingFullViewingKey),
48+
}
49+
}
50+
51+
/// Verifies that the `nullifier` field is consistent with the note fields.
52+
///
53+
/// Requires that the following optional fields are set:
54+
/// - `recipient`
55+
/// - `value`
56+
/// - `rho`
57+
/// - `rseed`
58+
///
59+
/// The provided [`FullViewingKey`] is ignored if the spent note is a dummy note.
60+
/// Otherwise, it will be checked against the `fvk` field (if set).
61+
pub fn verify_nullifier(
62+
&self,
63+
expected_fvk: Option<&FullViewingKey>,
64+
) -> Result<(), VerifyError> {
65+
let fvk = self.fvk_for_validation(expected_fvk)?;
66+
67+
let note = Note::from_parts(
68+
self.recipient.ok_or(VerifyError::MissingRecipient)?,
69+
self.value.ok_or(VerifyError::MissingValue)?,
70+
self.rho.ok_or(VerifyError::MissingRho)?,
71+
self.rseed.ok_or(VerifyError::MissingRandomSeed)?,
72+
)
73+
.into_option()
74+
.ok_or(VerifyError::InvalidSpendNote)?;
75+
76+
// We need both the note and the FVK to verify the nullifier; we have everything
77+
// needed to also verify that the correct FVK was provided (the nullifier check
78+
// itself only constrains `nk` within the FVK).
79+
fvk.scope_for_address(&note.recipient())
80+
.ok_or(VerifyError::WrongFvkForNote)?;
81+
82+
if note.nullifier(fvk) == self.nullifier {
83+
Ok(())
84+
} else {
85+
Err(VerifyError::InvalidNullifier)
86+
}
87+
}
88+
89+
/// Verifies that the `rk` field is consistent with the given FVK.
90+
///
91+
/// Requires that the following optional fields are set:
92+
/// - `alpha`
93+
///
94+
/// The provided [`FullViewingKey`] is ignored if the spent note is a dummy note
95+
/// (which can only be determined if the `value` field is set). Otherwise, it will be
96+
/// checked against the `fvk` field (if set).
97+
pub fn verify_rk(&self, expected_fvk: Option<&FullViewingKey>) -> Result<(), VerifyError> {
98+
let fvk = self.fvk_for_validation(expected_fvk)?;
99+
100+
let ak = SpendValidatingKey::from(fvk.clone());
101+
102+
let alpha = self
103+
.alpha
104+
.as_ref()
105+
.ok_or(VerifyError::MissingSpendAuthRandomizer)?;
106+
107+
if ak.randomize(alpha) == self.rk {
108+
Ok(())
109+
} else {
110+
Err(VerifyError::InvalidRandomizedVerificationKey)
111+
}
112+
}
113+
}
114+
115+
impl super::Output {
116+
/// Verifies that the `cmx` field is consistent with the note fields.
117+
///
118+
/// Requires that the following optional fields are set:
119+
/// - `recipient`
120+
/// - `value`
121+
/// - `rseed`
122+
///
123+
/// `spend` must be the Spend from the same Orchard action.
124+
pub fn verify_note_commitment(&self, spend: &super::Spend) -> Result<(), VerifyError> {
125+
let note = Note::from_parts(
126+
self.recipient.ok_or(VerifyError::MissingRecipient)?,
127+
self.value.ok_or(VerifyError::MissingValue)?,
128+
Rho::from_nf_old(spend.nullifier),
129+
self.rseed.ok_or(VerifyError::MissingRandomSeed)?,
130+
)
131+
.into_option()
132+
.ok_or(VerifyError::InvalidOutputNote)?;
133+
134+
if ExtractedNoteCommitment::from(note.commitment()) == self.cmx {
135+
Ok(())
136+
} else {
137+
Err(VerifyError::InvalidExtractedNoteCommitment)
138+
}
139+
}
140+
}
141+
142+
/// Errors that can occur while verifying a PCZT bundle.
143+
#[derive(Debug)]
144+
pub enum VerifyError {
145+
/// The output note's components do not produce the expected `cmx`.
146+
InvalidExtractedNoteCommitment,
147+
/// The spent note's components do not produce the expected `nullifier`.
148+
InvalidNullifier,
149+
/// The output note's components do not produce a valid note commitment.
150+
InvalidOutputNote,
151+
/// The Spend's FVK and `alpha` do not produce the expected `rk`.
152+
InvalidRandomizedVerificationKey,
153+
/// The spent note's components do not produce a valid note commitment.
154+
InvalidSpendNote,
155+
/// The action's `cv_net` does not match the provided note values and `rcv`.
156+
InvalidValueCommitment,
157+
/// The spend or output's `fvk` field does not match the provided FVK.
158+
MismatchedFullViewingKey,
159+
/// Dummy notes must have their `fvk` field set in order to be verified.
160+
MissingFullViewingKey,
161+
/// `nullifier` verification requires `rseed` to be set.
162+
MissingRandomSeed,
163+
/// `nullifier` verification requires `recipient` to be set.
164+
MissingRecipient,
165+
/// `nullifier` verification requires `rho` to be set.
166+
MissingRho,
167+
/// `rk` verification requires `alpha` to be set.
168+
MissingSpendAuthRandomizer,
169+
/// Verification requires all `value` fields to be set.
170+
MissingValue,
171+
/// `cv_net` verification requires `rcv` to be set.
172+
MissingValueCommitTrapdoor,
173+
/// The provided `fvk` does not own the spent note.
174+
WrongFvkForNote,
175+
}

0 commit comments

Comments
 (0)