sapling_crypto/pczt/
verify.rs

1use core::fmt;
2
3use crate::{keys::FullViewingKey, value::ValueCommitment, Note, ViewingKey};
4
5impl super::Spend {
6    /// Verifies that the `cv` field is consistent with the note fields.
7    ///
8    /// Requires that the following optional fields are set:
9    /// - `value`
10    /// - `rcv`
11    pub fn verify_cv(&self) -> Result<(), VerifyError> {
12        let value = self.value.ok_or(VerifyError::MissingValue)?;
13        let rcv = self
14            .rcv
15            .clone()
16            .ok_or(VerifyError::MissingValueCommitTrapdoor)?;
17
18        let cv_net = ValueCommitment::derive(value, rcv);
19        if cv_net.to_bytes() == self.cv.to_bytes() {
20            Ok(())
21        } else {
22            Err(VerifyError::InvalidValueCommitment)
23        }
24    }
25
26    /// Returns the [`ViewingKey`] to use when validating this note.
27    ///
28    /// Handles dummy notes when the `value` field is set.
29    fn vk_for_validation(
30        &self,
31        expected_fvk: Option<&FullViewingKey>,
32    ) -> Result<ViewingKey, VerifyError> {
33        let vk = self
34            .proof_generation_key
35            .as_ref()
36            .map(|proof_generation_key| proof_generation_key.to_viewing_key());
37
38        match (expected_fvk, vk, self.value.as_ref()) {
39            (Some(expected_fvk), Some(vk), _)
40                if vk.ak == expected_fvk.vk.ak && vk.nk == expected_fvk.vk.nk =>
41            {
42                Ok(vk)
43            }
44            // `expected_fvk` is ignored if the spent note is a dummy note.
45            (Some(_), Some(vk), Some(value)) if value.inner() == 0 => Ok(vk),
46            (Some(_), Some(_), _) => Err(VerifyError::MismatchedFullViewingKey),
47            (Some(expected_fvk), None, _) => Ok(expected_fvk.vk.clone()),
48            (None, Some(vk), _) => Ok(vk),
49            (None, None, _) => Err(VerifyError::MissingProofGenerationKey),
50        }
51    }
52
53    /// Verifies that the `nullifier` field is consistent with the note fields.
54    ///
55    /// Requires that the following optional fields are set:
56    /// - `recipient`
57    /// - `value`
58    /// - `rseed`
59    /// - `witness`
60    ///
61    /// In addition, at least one of the `proof_generation_key` field or `expected_fvk`
62    /// must be provided.
63    ///
64    /// The provided [`FullViewingKey`] is ignored if the spent note is a dummy note.
65    /// Otherwise, it will be checked against the `proof_generation_key` field (if both
66    /// are set).
67    pub fn verify_nullifier(
68        &self,
69        expected_fvk: Option<&FullViewingKey>,
70    ) -> Result<(), VerifyError> {
71        let vk = self.vk_for_validation(expected_fvk)?;
72
73        let note = Note::from_parts(
74            self.recipient.ok_or(VerifyError::MissingRecipient)?,
75            self.value.ok_or(VerifyError::MissingValue)?,
76            self.rseed.ok_or(VerifyError::MissingRandomSeed)?,
77        );
78
79        // We need both the note and the VK to verify the nullifier; we have everything
80        // needed to also verify that the correct VK was provided (the nullifier check
81        // itself only constrains `nk` within the VK).
82        if vk.to_payment_address(*note.recipient().diversifier()) != Some(note.recipient()) {
83            return Err(VerifyError::WrongFvkForNote);
84        }
85
86        let merkle_path = self.witness().as_ref().ok_or(VerifyError::MissingWitness)?;
87
88        if note.nf(&vk.nk, merkle_path.position().into()) == self.nullifier {
89            Ok(())
90        } else {
91            Err(VerifyError::InvalidNullifier)
92        }
93    }
94
95    /// Verifies that the `rk` field is consistent with the given FVK.
96    ///
97    /// Requires that the following optional fields are set:
98    /// - `alpha`
99    ///
100    /// The provided [`FullViewingKey`] is ignored if the spent note is a dummy note
101    /// (which can only be determined if the `value` field is set). Otherwise, it will be
102    /// checked against the `proof_generation_key` field (if set).
103    pub fn verify_rk(&self, expected_fvk: Option<&FullViewingKey>) -> Result<(), VerifyError> {
104        let vk = self.vk_for_validation(expected_fvk)?;
105
106        let alpha = self
107            .alpha
108            .as_ref()
109            .ok_or(VerifyError::MissingSpendAuthRandomizer)?;
110
111        if vk.ak.randomize(alpha) == self.rk {
112            Ok(())
113        } else {
114            Err(VerifyError::InvalidRandomizedVerificationKey)
115        }
116    }
117}
118
119impl super::Output {
120    /// Verifies that the `cv` field is consistent with the note fields.
121    ///
122    /// Requires that the following optional fields are set:
123    /// - `value`
124    /// - `rcv`
125    pub fn verify_cv(&self) -> Result<(), VerifyError> {
126        let value = self.value.ok_or(VerifyError::MissingValue)?;
127        let rcv = self
128            .rcv
129            .clone()
130            .ok_or(VerifyError::MissingValueCommitTrapdoor)?;
131
132        let cv_net = ValueCommitment::derive(value, rcv);
133        if cv_net.to_bytes() == self.cv.to_bytes() {
134            Ok(())
135        } else {
136            Err(VerifyError::InvalidValueCommitment)
137        }
138    }
139
140    /// Verifies that the `cmu` field is consistent with the note fields.
141    ///
142    /// Requires that the following optional fields are set:
143    /// - `recipient`
144    /// - `value`
145    /// - `rseed`
146    pub fn verify_note_commitment(&self) -> Result<(), VerifyError> {
147        let note = Note::from_parts(
148            self.recipient.ok_or(VerifyError::MissingRecipient)?,
149            self.value.ok_or(VerifyError::MissingValue)?,
150            crate::Rseed::AfterZip212(self.rseed.ok_or(VerifyError::MissingRandomSeed)?),
151        );
152
153        if note.cmu() == self.cmu {
154            Ok(())
155        } else {
156            Err(VerifyError::InvalidExtractedNoteCommitment)
157        }
158    }
159}
160
161/// Errors that can occur while verifying a PCZT bundle.
162#[derive(Debug)]
163#[non_exhaustive]
164pub enum VerifyError {
165    /// The output note's components do not produce the expected `cmu`.
166    InvalidExtractedNoteCommitment,
167    /// The spent note's components do not produce the expected `nullifier`.
168    InvalidNullifier,
169    /// The Spend's FVK and `alpha` do not produce the expected `rk`.
170    InvalidRandomizedVerificationKey,
171    /// The spend or output's `cv` does not match the note value and `rcv`.
172    InvalidValueCommitment,
173    /// The spend's `proof_generation_key` field does not match the provided FVK.
174    MismatchedFullViewingKey,
175    /// Dummy notes must have their `proof_generation_key` field set in order to be verified.
176    MissingProofGenerationKey,
177    /// `nullifier` verification requires `rseed` to be set.
178    MissingRandomSeed,
179    /// `nullifier` verification requires `recipient` to be set.
180    MissingRecipient,
181    /// `rk` verification requires `alpha` to be set.
182    MissingSpendAuthRandomizer,
183    /// Verification requires all `value` fields to be set.
184    MissingValue,
185    /// `cv` verification requires `rcv` to be set.
186    MissingValueCommitTrapdoor,
187    /// `nullifier` verification requires `witness` to be set.
188    MissingWitness,
189    /// The provided `fvk` does not own the spent note.
190    WrongFvkForNote,
191}
192
193impl fmt::Display for VerifyError {
194    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
195        match self {
196            VerifyError::InvalidExtractedNoteCommitment => {
197                write!(f, "output note doesn't match `cmu`")
198            }
199            VerifyError::InvalidNullifier => write!(f, "spent note doesn't match `nullifier`"),
200            VerifyError::InvalidRandomizedVerificationKey => {
201                write!(f, "spend's `fvk` and `alpha` do not match `rk`")
202            }
203            VerifyError::InvalidValueCommitment => {
204                write!(f, "`cv` doesn't match the note value and `rcv`")
205            }
206            VerifyError::MismatchedFullViewingKey => {
207                write!(
208                    f,
209                    "Provided full viewing key doesn't match the `proof_generation_key` field"
210                )
211            }
212            VerifyError::MissingProofGenerationKey => {
213                write!(f, "`proof_generation_key` missing for dummy note")
214            }
215            VerifyError::MissingRandomSeed => {
216                write!(f, "`rseed` missing for `nullifier` verification")
217            }
218            VerifyError::MissingRecipient => {
219                write!(f, "`recipient` missing for `nullifier` verification")
220            }
221            VerifyError::MissingSpendAuthRandomizer => {
222                write!(f, "`alpha` missing for `rk` verification")
223            }
224            VerifyError::MissingValue => write!(f, "`value` missing"),
225            VerifyError::MissingValueCommitTrapdoor => {
226                write!(f, "`rcv` missing for `cv` verification")
227            }
228            VerifyError::MissingWitness => {
229                write!(f, "`witness` missing for `nullifier` verification")
230            }
231            VerifyError::WrongFvkForNote => write!(f, "`fvk` does not own the spent note"),
232        }
233    }
234}
235
236#[cfg(feature = "std")]
237impl std::error::Error for VerifyError {}