|
| 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(¬e.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