sapling_crypto/pczt/
verify.rs

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