Skip to main content

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