penumbra_sdk_shielded_pool/spend/
proof.rs

1use base64::prelude::*;
2use std::str::FromStr;
3use tct::Root;
4
5use anyhow::Result;
6use ark_r1cs_std::{
7    prelude::{EqGadget, FieldVar},
8    uint8::UInt8,
9    ToBitsGadget,
10};
11use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
12use decaf377::{r1cs::FqVar, Bls12_377, Fq, Fr};
13
14use ark_ff::ToConstraintField;
15use ark_groth16::{
16    r1cs_to_qap::LibsnarkReduction, Groth16, PreparedVerifyingKey, Proof, ProvingKey,
17};
18use ark_r1cs_std::prelude::AllocVar;
19use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef};
20use ark_snark::SNARK;
21use decaf377_rdsa::{SpendAuth, VerificationKey};
22use penumbra_sdk_proto::{penumbra::core::component::shielded_pool::v1 as pb, DomainType};
23use penumbra_sdk_tct as tct;
24use penumbra_sdk_tct::r1cs::StateCommitmentVar;
25
26use crate::{note, Note, Rseed};
27use penumbra_sdk_asset::{
28    balance::commitment::BalanceCommitmentVar,
29    balance::{self, Commitment},
30    Value,
31};
32use penumbra_sdk_keys::keys::{
33    AuthorizationKeyVar, Bip44Path, IncomingViewingKeyVar, NullifierKey, NullifierKeyVar,
34    RandomizedVerificationKey, SeedPhrase, SpendAuthRandomizerVar, SpendKey,
35};
36use penumbra_sdk_proof_params::{DummyWitness, VerifyingKeyExt, GROTH16_PROOF_LENGTH_BYTES};
37use penumbra_sdk_sct::{Nullifier, NullifierVar};
38use tap::Tap;
39
40/// The public input for a [`SpendProof`].
41#[derive(Clone, Debug)]
42pub struct SpendProofPublic {
43    /// the merkle root of the state commitment tree.
44    pub anchor: tct::Root,
45    /// balance commitment of the note to be spent.
46    pub balance_commitment: balance::Commitment,
47    /// nullifier of the note to be spent.
48    pub nullifier: Nullifier,
49    /// the randomized verification spend key.
50    pub rk: VerificationKey<SpendAuth>,
51}
52
53/// The private input for a [`SpendProof`].
54#[derive(Clone, Debug)]
55pub struct SpendProofPrivate {
56    /// Inclusion proof for the note commitment.
57    pub state_commitment_proof: tct::Proof,
58    /// The note being spent.
59    pub note: Note,
60    /// The blinding factor used for generating the balance commitment.
61    pub v_blinding: Fr,
62    /// The randomizer used for generating the randomized spend auth key.
63    pub spend_auth_randomizer: Fr,
64    /// The spend authorization key.
65    pub ak: VerificationKey<SpendAuth>,
66    /// The nullifier deriving key.
67    pub nk: NullifierKey,
68}
69
70#[cfg(test)]
71fn check_satisfaction(public: &SpendProofPublic, private: &SpendProofPrivate) -> Result<()> {
72    use penumbra_sdk_keys::keys::FullViewingKey;
73
74    let note_commitment = private.note.commit();
75    if note_commitment != private.state_commitment_proof.commitment() {
76        anyhow::bail!("note commitment did not match state commitment proof");
77    }
78
79    let nullifier = Nullifier::derive(
80        &private.nk,
81        private.state_commitment_proof.position(),
82        &note_commitment,
83    );
84    if nullifier != public.nullifier {
85        anyhow::bail!("nullifier did not match public input");
86    }
87
88    let amount_u128: u128 = private.note.value().amount.into();
89    if amount_u128 != 0u128 {
90        private.state_commitment_proof.verify(public.anchor)?;
91    }
92
93    let rk = private.ak.randomize(&private.spend_auth_randomizer);
94    if rk != public.rk {
95        anyhow::bail!("randomized spend auth key did not match public input");
96    }
97
98    let fvk = FullViewingKey::from_components(private.ak, private.nk);
99    let ivk = fvk.incoming();
100    let transmission_key = ivk.diversified_public(&private.note.diversified_generator());
101    if transmission_key != *private.note.transmission_key() {
102        anyhow::bail!("transmission key did not match note");
103    }
104
105    let balance_commitment = private.note.value().commit(private.v_blinding);
106    if balance_commitment != public.balance_commitment {
107        anyhow::bail!("balance commitment did not match public input");
108    }
109
110    if private.note.diversified_generator() == decaf377::Element::default() {
111        anyhow::bail!("diversified generator is identity");
112    }
113    if private.ak.is_identity() {
114        anyhow::bail!("ak is identity");
115    }
116
117    Ok(())
118}
119
120#[cfg(test)]
121fn check_circuit_satisfaction(public: SpendProofPublic, private: SpendProofPrivate) -> Result<()> {
122    use ark_relations::r1cs::{self, ConstraintSystem};
123
124    let cs = ConstraintSystem::new_ref();
125    let circuit = SpendCircuit { public, private };
126    cs.set_optimization_goal(r1cs::OptimizationGoal::Constraints);
127    circuit
128        .generate_constraints(cs.clone())
129        .expect("can generate constraints from circuit");
130    cs.finalize();
131    if !cs.is_satisfied()? {
132        anyhow::bail!("constraints are not satisfied");
133    }
134    Ok(())
135}
136
137/// Groth16 proof for spending existing notes.
138#[derive(Clone, Debug)]
139pub struct SpendCircuit {
140    public: SpendProofPublic,
141    private: SpendProofPrivate,
142}
143
144impl ConstraintSynthesizer<Fq> for SpendCircuit {
145    fn generate_constraints(self, cs: ConstraintSystemRef<Fq>) -> ark_relations::r1cs::Result<()> {
146        // Witnesses
147        // Note: in the allocation of the address on `NoteVar` we check the diversified base is not identity.
148        let note_var = note::NoteVar::new_witness(cs.clone(), || Ok(self.private.note.clone()))?;
149        let claimed_note_commitment = StateCommitmentVar::new_witness(cs.clone(), || {
150            Ok(self.private.state_commitment_proof.commitment())
151        })?;
152
153        let position_var = tct::r1cs::PositionVar::new_witness(cs.clone(), || {
154            Ok(self.private.state_commitment_proof.position())
155        })?;
156        let position_bits = position_var.to_bits_le()?;
157        let merkle_path_var = tct::r1cs::MerkleAuthPathVar::new_witness(cs.clone(), || {
158            Ok(self.private.state_commitment_proof)
159        })?;
160
161        let v_blinding_arr: [u8; 32] = self.private.v_blinding.to_bytes();
162        let v_blinding_vars = UInt8::new_witness_vec(cs.clone(), &v_blinding_arr)?;
163
164        let spend_auth_randomizer_var = SpendAuthRandomizerVar::new_witness(cs.clone(), || {
165            Ok(self.private.spend_auth_randomizer)
166        })?;
167        // Note: in the allocation of `AuthorizationKeyVar` we check it is not identity.
168        let ak_element_var: AuthorizationKeyVar =
169            AuthorizationKeyVar::new_witness(cs.clone(), || Ok(self.private.ak))?;
170        let nk_var = NullifierKeyVar::new_witness(cs.clone(), || Ok(self.private.nk))?;
171
172        // Public inputs
173        let anchor_var = FqVar::new_input(cs.clone(), || Ok(Fq::from(self.public.anchor)))?;
174        let claimed_balance_commitment_var =
175            BalanceCommitmentVar::new_input(cs.clone(), || Ok(self.public.balance_commitment))?;
176        let claimed_nullifier_var =
177            NullifierVar::new_input(cs.clone(), || Ok(self.public.nullifier))?;
178        let rk_var = RandomizedVerificationKey::new_input(cs.clone(), || Ok(self.public.rk))?;
179
180        // Note commitment integrity.
181        let note_commitment_var = note_var.commit()?;
182        note_commitment_var.enforce_equal(&claimed_note_commitment)?;
183
184        // Nullifier integrity.
185        let nullifier_var = NullifierVar::derive(&nk_var, &position_var, &claimed_note_commitment)?;
186        nullifier_var.enforce_equal(&claimed_nullifier_var)?;
187
188        // Merkle auth path verification against the provided anchor.
189        //
190        // We short circuit the merkle path verification if the note is a _dummy_ spend (a spend
191        // with zero value), since these are never committed to the state commitment tree.
192        let is_not_dummy = note_var.amount().is_eq(&FqVar::zero())?.not();
193        merkle_path_var.verify(
194            cs.clone(),
195            &is_not_dummy,
196            &position_bits,
197            anchor_var,
198            claimed_note_commitment.inner(),
199        )?;
200
201        // Check integrity of randomized verification key.
202        let computed_rk_var = ak_element_var.randomize(&spend_auth_randomizer_var)?;
203        computed_rk_var.enforce_equal(&rk_var)?;
204
205        // Check integrity of diversified address.
206        let ivk = IncomingViewingKeyVar::derive(&nk_var, &ak_element_var)?;
207        let computed_transmission_key =
208            ivk.diversified_public(&note_var.diversified_generator())?;
209        computed_transmission_key.enforce_equal(&note_var.transmission_key())?;
210
211        // Check integrity of balance commitment.
212        let balance_commitment = note_var.value().commit(v_blinding_vars)?;
213        balance_commitment.enforce_equal(&claimed_balance_commitment_var)?;
214
215        Ok(())
216    }
217}
218
219impl DummyWitness for SpendCircuit {
220    fn with_dummy_witness() -> Self {
221        let seed_phrase = SeedPhrase::from_randomness(&[b'f'; 32]);
222        let sk_sender = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
223        let fvk_sender = sk_sender.full_viewing_key();
224        let ivk_sender = fvk_sender.incoming();
225        let (address, _dtk_d) = ivk_sender.payment_address(0u32.into());
226
227        let spend_auth_randomizer = Fr::from(1u64);
228        let rsk = sk_sender.spend_auth_key().randomize(&spend_auth_randomizer);
229        let nk = *sk_sender.nullifier_key();
230        let ak = sk_sender.spend_auth_key().into();
231        let note = Note::from_parts(
232            address,
233            Value::from_str("1upenumbra").expect("valid value"),
234            Rseed([1u8; 32]),
235        )
236        .expect("can make a note");
237        let v_blinding = Fr::from(1u64);
238        let rk: VerificationKey<SpendAuth> = rsk.into();
239        let nullifier = Nullifier(Fq::from(1u64));
240        let mut sct = tct::Tree::new();
241        let anchor: tct::Root = sct.root();
242        let note_commitment = note.commit();
243        sct.insert(tct::Witness::Keep, note_commitment)
244            .expect("able to insert note commitment into SCT");
245        let state_commitment_proof = sct
246            .witness(note_commitment)
247            .expect("able to witness just-inserted note commitment");
248
249        let public = SpendProofPublic {
250            anchor,
251            balance_commitment: balance::Commitment(decaf377::Element::GENERATOR),
252            nullifier,
253            rk,
254        };
255        let private = SpendProofPrivate {
256            state_commitment_proof,
257            note,
258            v_blinding,
259            spend_auth_randomizer,
260            ak,
261            nk,
262        };
263
264        Self { public, private }
265    }
266}
267
268#[derive(Clone, Debug)]
269pub struct SpendProof([u8; GROTH16_PROOF_LENGTH_BYTES]);
270
271#[derive(Debug, thiserror::Error)]
272pub enum VerificationError {
273    #[error("error deserializing compressed proof: {0:?}")]
274    ProofDeserialize(ark_serialize::SerializationError),
275    #[error("Fq types are Bls12-377 field members")]
276    Anchor,
277    #[error("balance commitment is a Bls12-377 field member")]
278    BalanceCommitment,
279    #[error("nullifier is a Bls12-377 field member")]
280    Nullifier,
281    #[error("could not decompress element points: {0:?}")]
282    DecompressRk(decaf377::EncodingError),
283    #[error("randomized spend key is a Bls12-377 field member")]
284    Rk,
285    #[error("start position is a Bls12-377 field member")]
286    StartPosition,
287    #[error("error verifying proof: {0:?}")]
288    SynthesisError(ark_relations::r1cs::SynthesisError),
289    #[error("spend proof did not verify")]
290    InvalidProof,
291}
292
293impl SpendProof {
294    /// Generate a `SpendProof` given the proving key, public inputs,
295    /// witness data, and two random elements `blinding_r` and `blinding_s`.
296    pub fn prove(
297        blinding_r: Fq,
298        blinding_s: Fq,
299        pk: &ProvingKey<Bls12_377>,
300        public: SpendProofPublic,
301        private: SpendProofPrivate,
302    ) -> anyhow::Result<Self> {
303        let circuit = SpendCircuit { public, private };
304        let proof = Groth16::<Bls12_377, LibsnarkReduction>::create_proof_with_reduction(
305            circuit, pk, blinding_r, blinding_s,
306        )
307        .map_err(|err| anyhow::anyhow!(err))?;
308        let mut proof_bytes = [0u8; GROTH16_PROOF_LENGTH_BYTES];
309        Proof::serialize_compressed(&proof, &mut proof_bytes[..]).expect("can serialize Proof");
310        Ok(Self(proof_bytes))
311    }
312
313    /// Called to verify the proof using the provided public inputs.
314    // For debugging proof verification failures,
315    // to check that the proof data and verification keys are consistent.
316    #[tracing::instrument(level="debug", skip(self, vk), fields(self = ?BASE64_STANDARD.encode(self.clone().encode_to_vec()), vk = ?vk.debug_id()))]
317    pub fn verify(
318        &self,
319        vk: &PreparedVerifyingKey<Bls12_377>,
320        SpendProofPublic {
321            anchor: Root(anchor),
322            balance_commitment: Commitment(balance_commitment),
323            nullifier: Nullifier(nullifier),
324            rk,
325        }: SpendProofPublic,
326    ) -> Result<(), VerificationError> {
327        let proof = Proof::deserialize_compressed_unchecked(&self.0[..])
328            .map_err(VerificationError::ProofDeserialize)?;
329        let element_rk = decaf377::Encoding(rk.to_bytes())
330            .vartime_decompress()
331            .map_err(VerificationError::DecompressRk)?;
332
333        /// Shorthand helper, convert expressions into field elements.
334        macro_rules! to_field_elements {
335            ($fe:expr, $err:expr) => {
336                $fe.to_field_elements().ok_or($err)?
337            };
338        }
339
340        use VerificationError::*;
341        let public_inputs = [
342            to_field_elements!(Fq::from(anchor), Anchor),
343            to_field_elements!(balance_commitment, BalanceCommitment),
344            to_field_elements!(nullifier, Nullifier),
345            to_field_elements!(element_rk, Rk),
346        ]
347        .into_iter()
348        .flatten()
349        .collect::<Vec<_>>()
350        .tap(|public_inputs| tracing::trace!(?public_inputs));
351
352        let start = std::time::Instant::now();
353        Groth16::<Bls12_377, LibsnarkReduction>::verify_with_processed_vk(
354            vk,
355            public_inputs.as_slice(),
356            &proof,
357        )
358        .map_err(VerificationError::SynthesisError)?
359        .tap(|proof_result| tracing::debug!(?proof_result, elapsed = ?start.elapsed()))
360        .then_some(())
361        .ok_or(VerificationError::InvalidProof)
362    }
363}
364
365impl DomainType for SpendProof {
366    type Proto = pb::ZkSpendProof;
367}
368
369impl From<SpendProof> for pb::ZkSpendProof {
370    fn from(proof: SpendProof) -> Self {
371        pb::ZkSpendProof {
372            inner: proof.0.to_vec(),
373        }
374    }
375}
376
377impl TryFrom<pb::ZkSpendProof> for SpendProof {
378    type Error = anyhow::Error;
379
380    fn try_from(proto: pb::ZkSpendProof) -> Result<Self, Self::Error> {
381        Ok(SpendProof(proto.inner[..].try_into()?))
382    }
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388    use ark_r1cs_std::prelude::Boolean;
389    use decaf377::{Fq, Fr};
390    use penumbra_sdk_asset::{asset, Value};
391    use penumbra_sdk_keys::{
392        keys::{Bip44Path, SeedPhrase, SpendKey},
393        Address,
394    };
395    use penumbra_sdk_num::Amount;
396    use penumbra_sdk_proof_params::generate_prepared_test_parameters;
397    use penumbra_sdk_sct::Nullifier;
398    use penumbra_sdk_tct::StateCommitment;
399    use proptest::prelude::*;
400
401    use crate::Note;
402    use decaf377_rdsa::{SpendAuth, VerificationKey};
403    use penumbra_sdk_tct as tct;
404    use rand_core::OsRng;
405
406    fn fr_strategy() -> BoxedStrategy<Fr> {
407        any::<[u8; 32]>()
408            .prop_map(|bytes| Fr::from_le_bytes_mod_order(&bytes[..]))
409            .boxed()
410    }
411
412    prop_compose! {
413        fn arb_valid_spend_statement()(v_blinding in fr_strategy(), spend_auth_randomizer in fr_strategy(), asset_id64 in any::<u64>(), address_index in any::<u32>(), amount in any::<u64>(), seed_phrase_randomness in any::<[u8; 32]>(), rseed_randomness in any::<[u8; 32]>(), num_commitments in 0..100) -> (SpendProofPublic, SpendProofPrivate) {
414            let seed_phrase = SeedPhrase::from_randomness(&seed_phrase_randomness);
415            let sk_sender = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
416            let fvk_sender = sk_sender.full_viewing_key();
417            let ivk_sender = fvk_sender.incoming();
418            let (sender, _dtk_d) = ivk_sender.payment_address(address_index.into());
419            let value_to_send = Value {
420                amount: Amount::from(amount),
421                asset_id: asset::Id(Fq::from(asset_id64)),
422            };
423            let note = Note::from_parts(
424                sender.clone(),
425                value_to_send,
426                Rseed(rseed_randomness),
427            ).expect("should be able to create note");
428            let note_commitment = note.commit();
429            let rsk = sk_sender.spend_auth_key().randomize(&spend_auth_randomizer);
430            let nk = *sk_sender.nullifier_key();
431            let ak: VerificationKey<SpendAuth> = sk_sender.spend_auth_key().into();
432
433            let mut sct = tct::Tree::new();
434
435            // Next, we simulate the case where the SCT is not empty by adding `num_commitments`
436            // unrelated items in the SCT.
437            for i in 0..num_commitments {
438                // To avoid duplicate note commitments, we use the `i` counter as the Rseed randomness
439                let rseed = Rseed([i as u8; 32]);
440                let dummy_note_commitment = Note::from_parts(sender.clone(), value_to_send, rseed).expect("can create note").commit();
441                sct.insert(tct::Witness::Keep, dummy_note_commitment).expect("should be able to insert note commitments into the SCT");
442            }
443
444            sct.insert(tct::Witness::Keep, note_commitment).expect("should be able to insert note commitments into the SCT");
445            let anchor = sct.root();
446            let state_commitment_proof = sct.witness(note_commitment).expect("can witness note commitment");
447            let balance_commitment = value_to_send.commit(v_blinding);
448            let rk: VerificationKey<SpendAuth> = rsk.into();
449            let nullifier = Nullifier::derive(&nk, state_commitment_proof.position(), &note_commitment);
450
451            let public = SpendProofPublic {
452                anchor,
453                balance_commitment,
454                nullifier,
455                rk,
456            };
457            let private = SpendProofPrivate {
458                state_commitment_proof,
459                note,
460                v_blinding,
461                spend_auth_randomizer,
462                ak,
463                nk,
464            };
465            (public, private)
466        }
467    }
468
469    proptest! {
470        #[test]
471        fn spend_proof_happy_path((public, private) in arb_valid_spend_statement()) {
472            assert!(check_satisfaction(&public, &private).is_ok());
473            assert!(check_circuit_satisfaction(public, private).is_ok());
474        }
475    }
476
477    prop_compose! {
478        // This strategy generates a spend statement that uses a Merkle root
479        // from prior to the note commitment being added to the SCT. The Merkle
480        // path should not verify using this invalid root, and as such the circuit
481        // should be unsatisfiable.
482        fn arb_invalid_spend_statement_incorrect_anchor()(v_blinding in fr_strategy(), spend_auth_randomizer in fr_strategy(), asset_id64 in any::<u64>(), address_index in any::<u32>(), amount in any::<u64>(), seed_phrase_randomness in any::<[u8; 32]>(), rseed_randomness in any::<[u8; 32]>(), num_commitments in 0..100) -> (SpendProofPublic, SpendProofPrivate) {
483            let seed_phrase = SeedPhrase::from_randomness(&seed_phrase_randomness);
484            let sk_sender = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
485            let fvk_sender = sk_sender.full_viewing_key();
486            let ivk_sender = fvk_sender.incoming();
487            let (sender, _dtk_d) = ivk_sender.payment_address(address_index.into());
488            let value_to_send = Value {
489                amount: Amount::from(amount),
490                asset_id: asset::Id(Fq::from(asset_id64)),
491            };
492            let note = Note::from_parts(
493                sender.clone(),
494                value_to_send,
495                Rseed(rseed_randomness),
496            ).expect("should be able to create note");
497            let note_commitment = note.commit();
498            let rsk = sk_sender.spend_auth_key().randomize(&spend_auth_randomizer);
499            let nk = *sk_sender.nullifier_key();
500            let ak: VerificationKey<SpendAuth> = sk_sender.spend_auth_key().into();
501
502            let mut sct = tct::Tree::new();
503
504            // Next, we simulate the case where the SCT is not empty by adding `num_commitments`
505            // unrelated items in the SCT.
506            for i in 0..num_commitments {
507                // To avoid duplicate note commitments, we use the `i` counter as the Rseed randomness
508                let rseed = Rseed([i as u8; 32]);
509                let dummy_note_commitment = Note::from_parts(sender.clone(), value_to_send, rseed).expect("can create note").commit();
510                sct.insert(tct::Witness::Keep, dummy_note_commitment).expect("should be able to insert note commitments into the SCT");
511            }
512            let incorrect_anchor = sct.root();
513
514            sct.insert(tct::Witness::Keep, note_commitment).expect("should be able to insert note commitments into the SCT");
515            let state_commitment_proof = sct.witness(note_commitment).expect("can witness note commitment");
516            let balance_commitment = value_to_send.commit(v_blinding);
517            let rk: VerificationKey<SpendAuth> = rsk.into();
518            let nullifier = Nullifier::derive(&nk, state_commitment_proof.position(), &note_commitment);
519
520            let public = SpendProofPublic {
521                anchor: incorrect_anchor,
522                balance_commitment,
523                nullifier,
524                rk,
525            };
526            let private = SpendProofPrivate {
527                state_commitment_proof,
528                note,
529                v_blinding,
530                spend_auth_randomizer,
531                ak,
532                nk,
533            };
534            (public, private)
535        }
536    }
537
538    proptest! {
539    #[test]
540    /// Check that the `SpendCircuit` is not satisfied when using an incorrect
541    /// TCT root (`anchor`).
542    fn spend_proof_verification_merkle_path_integrity_failure((public, private) in arb_invalid_spend_statement_incorrect_anchor()) {
543        assert!(check_satisfaction(&public, &private).is_err());
544            assert!(check_circuit_satisfaction(public, private).is_err());
545    }
546    }
547
548    prop_compose! {
549        // Recall: The transmission key `pk_d` is derived as:
550        //
551        // `pk_d ​= [ivk] B_d`
552        //
553        // where `B_d` is the diversified basepoint and `ivk` is the incoming
554        // viewing key.
555        //
556        // This strategy generates a spend statement that is spending a note
557        // that corresponds to a diversified address associated with a different
558        // IVK, i.e. the prover cannot demonstrate the transmission key `pk_d`
559        // was derived as above and the circuit should be unsatisfiable.
560        fn arb_invalid_spend_statement_diversified_address()(v_blinding in fr_strategy(), spend_auth_randomizer in fr_strategy(), asset_id64 in any::<u64>(), address_index in any::<u32>(), amount in any::<u64>(), seed_phrase_randomness in any::<[u8; 32]>(), incorrect_seed_phrase_randomness in any::<[u8; 32]>(), rseed_randomness in any::<[u8; 32]>()) -> (SpendProofPublic, SpendProofPrivate) {
561            let seed_phrase = SeedPhrase::from_randomness(&seed_phrase_randomness);
562            let sk_sender = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
563            let fvk_sender = sk_sender.full_viewing_key();
564            let ivk_sender = fvk_sender.incoming();
565            let (_sender, _dtk_d) = ivk_sender.payment_address(address_index.into());
566            let value_to_send = Value {
567                amount: Amount::from(amount),
568                asset_id: asset::Id(Fq::from(asset_id64)),
569            };
570
571            let wrong_seed_phrase = SeedPhrase::from_randomness(&incorrect_seed_phrase_randomness);
572            let wrong_sk_sender = SpendKey::from_seed_phrase_bip44(wrong_seed_phrase, &Bip44Path::new(0));
573            let wrong_fvk_sender = wrong_sk_sender.full_viewing_key();
574            let wrong_ivk_sender = wrong_fvk_sender.incoming();
575            let (wrong_sender, _dtk_d) = wrong_ivk_sender.payment_address(address_index.into());
576
577            let note = Note::from_parts(
578                wrong_sender,
579                value_to_send,
580                Rseed(rseed_randomness),
581            ).expect("should be able to create note");
582            let note_commitment = note.commit();
583            let rsk = sk_sender.spend_auth_key().randomize(&spend_auth_randomizer);
584            let nk = *sk_sender.nullifier_key();
585            let ak: VerificationKey<SpendAuth> = sk_sender.spend_auth_key().into();
586
587            let mut sct = tct::Tree::new();
588            sct.insert(tct::Witness::Keep, note_commitment).expect("should be able to insert note commitments into the SCT");
589            let anchor = sct.root();
590            let state_commitment_proof = sct.witness(note_commitment).expect("can witness note commitment");
591            let balance_commitment = value_to_send.commit(v_blinding);
592            let rk: VerificationKey<SpendAuth> = rsk.into();
593            let nullifier = Nullifier::derive(&nk, state_commitment_proof.position(), &note_commitment);
594
595            let public = SpendProofPublic {
596                anchor,
597                balance_commitment,
598                nullifier,
599                rk,
600            };
601            let private = SpendProofPrivate {
602                state_commitment_proof,
603                note,
604                v_blinding,
605                spend_auth_randomizer,
606                ak,
607                nk,
608            };
609            (public, private)
610        }
611    }
612
613    proptest! {
614        #[test]
615        /// Check that the `SpendCircuit` is not satisfied when the diversified address is wrong.
616        fn spend_proof_verification_diversified_address_integrity_failure((public, private) in arb_invalid_spend_statement_diversified_address()) {
617            assert!(check_satisfaction(&public, &private).is_err());
618            assert!(check_circuit_satisfaction(public, private).is_err());
619        }
620    }
621
622    prop_compose! {
623        // This strategy generates a spend statement that derives a nullifier
624        // using a different position.
625        fn arb_invalid_spend_statement_nullifier()(v_blinding in fr_strategy(), spend_auth_randomizer in fr_strategy(), asset_id64 in any::<u64>(), address_index in any::<u32>(), amount in any::<u64>(), seed_phrase_randomness in any::<[u8; 32]>(), rseed_randomness in any::<[u8; 32]>(), num_commitments in 0..100) -> (SpendProofPublic, SpendProofPrivate) {
626            let seed_phrase = SeedPhrase::from_randomness(&seed_phrase_randomness);
627            let sk_sender = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
628            let fvk_sender = sk_sender.full_viewing_key();
629            let ivk_sender = fvk_sender.incoming();
630            let (sender, _dtk_d) = ivk_sender.payment_address(address_index.into());
631            let value_to_send = Value {
632                amount: Amount::from(amount),
633                asset_id: asset::Id(Fq::from(asset_id64)),
634            };
635            let note = Note::from_parts(
636                sender.clone(),
637                value_to_send,
638                Rseed(rseed_randomness),
639            ).expect("should be able to create note");
640            let note_commitment = note.commit();
641            let rsk = sk_sender.spend_auth_key().randomize(&spend_auth_randomizer);
642            let nk = *sk_sender.nullifier_key();
643            let ak: VerificationKey<SpendAuth> = sk_sender.spend_auth_key().into();
644
645            let mut sct = tct::Tree::new();
646
647            // Next, we simulate the case where the SCT is not empty by adding `num_commitments`
648            // unrelated items in the SCT.
649            for i in 0..num_commitments {
650                // To avoid duplicate note commitments, we use the `i` counter as the Rseed randomness
651                let rseed = Rseed([i as u8; 32]);
652                let dummy_note_commitment = Note::from_parts(sender.clone(), value_to_send, rseed).expect("can create note").commit();
653                sct.insert(tct::Witness::Keep, dummy_note_commitment).expect("should be able to insert note commitments into the SCT");
654            }
655            // Insert one more note commitment and witness it.
656            let rseed = Rseed([num_commitments as u8; 32]);
657            let dummy_note_commitment = Note::from_parts(sender.clone(), value_to_send, rseed).expect("can create note").commit();
658            sct.insert(tct::Witness::Keep, dummy_note_commitment).expect("should be able to insert note commitments into the SCT");
659            let incorrect_position = sct.witness(dummy_note_commitment).expect("can witness note commitment").position();
660
661            sct.insert(tct::Witness::Keep, note_commitment).expect("should be able to insert note commitments into the SCT");
662            let anchor = sct.root();
663            let state_commitment_proof = sct.witness(note_commitment).expect("can witness note commitment");
664            let balance_commitment = value_to_send.commit(v_blinding);
665            let rk: VerificationKey<SpendAuth> = rsk.into();
666            let incorrect_nf = Nullifier::derive(&nk, incorrect_position, &note_commitment);
667
668            let public = SpendProofPublic {
669                anchor,
670                balance_commitment,
671                nullifier: incorrect_nf,
672                rk,
673            };
674            let private = SpendProofPrivate {
675                state_commitment_proof,
676                note,
677                v_blinding,
678                spend_auth_randomizer,
679                ak,
680                nk,
681            };
682            (public, private)
683        }
684    }
685
686    proptest! {
687        #[test]
688        /// Check that the `SpendCircuit` is not satisfied, when using an
689        /// incorrect nullifier.
690        fn spend_proof_verification_nullifier_integrity_failure((public, private) in arb_invalid_spend_statement_nullifier()) {
691            assert!(check_satisfaction(&public, &private).is_err());
692            assert!(check_circuit_satisfaction(public, private).is_err());
693        }
694    }
695
696    prop_compose! {
697        // This statement uses a randomly generated incorrect value blinding factor for deriving the
698        // balance commitment.
699        fn arb_invalid_spend_statement_v_blinding_factor()(v_blinding in fr_strategy(), incorrect_v_blinding in fr_strategy(), spend_auth_randomizer in fr_strategy(), asset_id64 in any::<u64>(), address_index in any::<u32>(), amount in any::<u64>(), seed_phrase_randomness in any::<[u8; 32]>(), rseed_randomness in any::<[u8; 32]>(), num_commitments in 0..100) -> (SpendProofPublic, SpendProofPrivate) {
700            let seed_phrase = SeedPhrase::from_randomness(&seed_phrase_randomness);
701            let sk_sender = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
702            let fvk_sender = sk_sender.full_viewing_key();
703            let ivk_sender = fvk_sender.incoming();
704            let (sender, _dtk_d) = ivk_sender.payment_address(address_index.into());
705            let value_to_send = Value {
706                amount: Amount::from(amount),
707                asset_id: asset::Id(Fq::from(asset_id64)),
708            };
709            let note = Note::from_parts(
710                sender.clone(),
711                value_to_send,
712                Rseed(rseed_randomness),
713            ).expect("should be able to create note");
714            let note_commitment = note.commit();
715            let rsk = sk_sender.spend_auth_key().randomize(&spend_auth_randomizer);
716            let nk = *sk_sender.nullifier_key();
717            let ak: VerificationKey<SpendAuth> = sk_sender.spend_auth_key().into();
718
719            let mut sct = tct::Tree::new();
720
721            // Next, we simulate the case where the SCT is not empty by adding `num_commitments`
722            // unrelated items in the SCT.
723            for i in 0..num_commitments {
724                // To avoid duplicate note commitments, we use the `i` counter as the Rseed randomness
725                let rseed = Rseed([i as u8; 32]);
726                let dummy_note_commitment = Note::from_parts(sender.clone(), value_to_send, rseed).expect("can create note").commit();
727                sct.insert(tct::Witness::Keep, dummy_note_commitment).expect("should be able to insert note commitments into the SCT");
728            }
729
730            sct.insert(tct::Witness::Keep, note_commitment).expect("should be able to insert note commitments into the SCT");
731            let anchor = sct.root();
732            let state_commitment_proof = sct.witness(note_commitment).expect("can witness note commitment");
733            let balance_commitment = value_to_send.commit(v_blinding);
734            let rk: VerificationKey<SpendAuth> = rsk.into();
735            let nullifier = Nullifier::derive(&nk, state_commitment_proof.position(), &note_commitment);
736
737            let public = SpendProofPublic {
738                anchor,
739                balance_commitment,
740                nullifier,
741                rk,
742            };
743            let private = SpendProofPrivate {
744                state_commitment_proof,
745                note,
746                v_blinding: incorrect_v_blinding,
747                spend_auth_randomizer,
748                ak,
749                nk,
750            };
751            (public, private)
752        }
753    }
754
755    proptest! {
756        #[test]
757        /// Check that the `SpendCircuit` is not satisfied when using balance
758        /// commitments with different blinding factors.
759        fn spend_proof_verification_balance_commitment_integrity_failure((public, private) in arb_invalid_spend_statement_v_blinding_factor()) {
760            assert!(check_satisfaction(&public, &private).is_err());
761            assert!(check_circuit_satisfaction(public, private).is_err());
762        }
763    }
764
765    prop_compose! {
766        // This statement uses a randomly generated incorrect spend auth randomizer for deriving the
767        // randomized verification key.
768        fn arb_invalid_spend_statement_rk_integrity()(v_blinding in fr_strategy(), spend_auth_randomizer in fr_strategy(), asset_id64 in any::<u64>(), address_index in any::<u32>(), amount in any::<u64>(), seed_phrase_randomness in any::<[u8; 32]>(), rseed_randomness in any::<[u8; 32]>(), num_commitments in 0..100, incorrect_spend_auth_randomizer in fr_strategy()) -> (SpendProofPublic, SpendProofPrivate) {
769            let seed_phrase = SeedPhrase::from_randomness(&seed_phrase_randomness);
770            let sk_sender = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
771            let fvk_sender = sk_sender.full_viewing_key();
772            let ivk_sender = fvk_sender.incoming();
773            let (sender, _dtk_d) = ivk_sender.payment_address(address_index.into());
774            let value_to_send = Value {
775                amount: Amount::from(amount),
776                asset_id: asset::Id(Fq::from(asset_id64)),
777            };
778            let note = Note::from_parts(
779                sender.clone(),
780                value_to_send,
781                Rseed(rseed_randomness),
782            ).expect("should be able to create note");
783            let note_commitment = note.commit();
784            let nk = *sk_sender.nullifier_key();
785            let ak: VerificationKey<SpendAuth> = sk_sender.spend_auth_key().into();
786
787            let mut sct = tct::Tree::new();
788
789            // Next, we simulate the case where the SCT is not empty by adding `num_commitments`
790            // unrelated items in the SCT.
791            for i in 0..num_commitments {
792                // To avoid duplicate note commitments, we use the `i` counter as the Rseed randomness
793                let rseed = Rseed([i as u8; 32]);
794                let dummy_note_commitment = Note::from_parts(sender.clone(), value_to_send, rseed).expect("can create note").commit();
795                sct.insert(tct::Witness::Keep, dummy_note_commitment).expect("should be able to insert note commitments into the SCT");
796            }
797
798            sct.insert(tct::Witness::Keep, note_commitment).expect("should be able to insert note commitments into the SCT");
799            let anchor = sct.root();
800            let state_commitment_proof = sct.witness(note_commitment).expect("can witness note commitment");
801            let balance_commitment = value_to_send.commit(v_blinding);
802            let nullifier = Nullifier::derive(&nk, state_commitment_proof.position(), &note_commitment);
803
804            let incorrect_rsk = sk_sender
805                .spend_auth_key()
806                .randomize(&incorrect_spend_auth_randomizer);
807            let incorrect_rk: VerificationKey<SpendAuth> = incorrect_rsk.into();
808
809            let public = SpendProofPublic {
810                anchor,
811                balance_commitment,
812                nullifier,
813                rk: incorrect_rk,
814            };
815            let private = SpendProofPrivate {
816                state_commitment_proof,
817                note,
818                v_blinding,
819                spend_auth_randomizer,
820                ak,
821                nk,
822            };
823            (public, private)
824        }
825    }
826
827    proptest! {
828        #[test]
829        /// Check that the `SpendCircuit` is not satisfied when the incorrect randomizable verification key is used.
830        fn spend_proof_verification_fails_rk_integrity((public, private) in arb_invalid_spend_statement_rk_integrity()) {
831            assert!(check_satisfaction(&public, &private).is_err());
832            assert!(check_circuit_satisfaction(public, private).is_err());
833        }
834    }
835
836    prop_compose! {
837        fn arb_valid_dummy_spend_statement()(v_blinding in fr_strategy(), spend_auth_randomizer in fr_strategy(), asset_id64 in any::<u64>(), address_index in any::<u32>(), seed_phrase_randomness in any::<[u8; 32]>(), rseed_randomness in any::<[u8; 32]>()) -> (SpendProofPublic, SpendProofPrivate) {
838            let seed_phrase = SeedPhrase::from_randomness(&seed_phrase_randomness);
839            let sk_sender = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
840            let fvk_sender = sk_sender.full_viewing_key();
841            let ivk_sender = fvk_sender.incoming();
842            let (sender, _dtk_d) = ivk_sender.payment_address(address_index.into());
843            let value_to_send = Value {
844                amount: Amount::from(0u64),
845                asset_id: asset::Id(Fq::from(asset_id64)),
846            };
847            let note = Note::from_parts(
848                sender.clone(),
849                value_to_send,
850                Rseed(rseed_randomness),
851            ).expect("should be able to create note");
852            let note_commitment = note.commit();
853            let rsk = sk_sender.spend_auth_key().randomize(&spend_auth_randomizer);
854            let nk = *sk_sender.nullifier_key();
855            let ak: VerificationKey<SpendAuth> = sk_sender.spend_auth_key().into();
856
857            let mut sct = tct::Tree::new();
858            sct.insert(tct::Witness::Keep, note_commitment).expect("should be able to insert note commitments into the SCT");
859
860            let state_commitment_proof = sct.witness(note_commitment).expect("can witness note commitment");
861            let balance_commitment = value_to_send.commit(v_blinding);
862            let rk: VerificationKey<SpendAuth> = rsk.into();
863            let nullifier = Nullifier::derive(&nk, state_commitment_proof.position(), &note_commitment);
864
865            // use an invalid anchor to verify that the circuit skips inclusion checks for dummy
866            // spends
867            let invalid_anchor = tct::Tree::new().root();
868
869            let public = SpendProofPublic {
870                anchor: invalid_anchor,
871                balance_commitment,
872                nullifier,
873                rk,
874            };
875            let private = SpendProofPrivate {
876                state_commitment_proof,
877                note,
878                v_blinding,
879                spend_auth_randomizer,
880                ak,
881                nk,
882            };
883            (public, private)
884        }
885    }
886
887    proptest! {
888        #[test]
889        /// Check that the `SpendCircuit` is always satisfied for dummy (zero value) spends.
890        fn spend_proof_dummy_verification_suceeds((public, private) in arb_valid_dummy_spend_statement()) {
891            assert!(check_satisfaction(&public, &private).is_ok());
892            assert!(check_circuit_satisfaction(public, private).is_ok());
893        }
894    }
895
896    struct MerkleProofCircuit {
897        /// Witness: Inclusion proof for the note commitment.
898        state_commitment_proof: tct::Proof,
899        /// Public input: The merkle root of the state commitment tree
900        pub anchor: tct::Root,
901        pub epoch: Fq,
902        pub block: Fq,
903        pub commitment_index: Fq,
904    }
905
906    impl ConstraintSynthesizer<Fq> for MerkleProofCircuit {
907        fn generate_constraints(
908            self,
909            cs: ConstraintSystemRef<Fq>,
910        ) -> ark_relations::r1cs::Result<()> {
911            // public inputs
912            let anchor_var = FqVar::new_input(cs.clone(), || Ok(Fq::from(self.anchor)))?;
913            let epoch_var = FqVar::new_input(cs.clone(), || Ok(self.epoch))?;
914            let block_var = FqVar::new_input(cs.clone(), || Ok(self.block))?;
915            let commitment_index_var = FqVar::new_input(cs.clone(), || Ok(self.commitment_index))?;
916
917            // witnesses
918            let merkle_path_var = tct::r1cs::MerkleAuthPathVar::new_witness(cs.clone(), || {
919                Ok(self.state_commitment_proof.clone())
920            })?;
921            let claimed_note_commitment = StateCommitmentVar::new_witness(cs.clone(), || {
922                Ok(self.state_commitment_proof.commitment())
923            })?;
924            let position_var = tct::r1cs::PositionVar::new_witness(cs.clone(), || {
925                Ok(self.state_commitment_proof.position())
926            })?;
927            let position_bits = position_var.to_bits_le()?;
928            merkle_path_var.verify(
929                cs,
930                &Boolean::TRUE,
931                &position_bits,
932                anchor_var,
933                claimed_note_commitment.inner(),
934            )?;
935
936            // Now also verify the commitment index, block, and epoch numbers are all valid. This is not necessary
937            // for Merkle proofs in general, but is here to ensure this code is exercised in tests.
938            let computed_epoch = position_var.epoch()?;
939            let computed_block = position_var.block()?;
940            let computed_commitment_index = position_var.commitment()?;
941            computed_epoch.enforce_equal(&epoch_var)?;
942            computed_block.enforce_equal(&block_var)?;
943            computed_commitment_index.enforce_equal(&commitment_index_var)?;
944            Ok(())
945        }
946    }
947
948    impl DummyWitness for MerkleProofCircuit {
949        fn with_dummy_witness() -> Self {
950            let seed_phrase = SeedPhrase::from_randomness(&[b'f'; 32]);
951            let sk_sender = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
952            let fvk_sender = sk_sender.full_viewing_key();
953            let ivk_sender = fvk_sender.incoming();
954            let (address, _dtk_d) = ivk_sender.payment_address(0u32.into());
955
956            let note = Note::from_parts(
957                address,
958                Value::from_str("1upenumbra").expect("valid value"),
959                Rseed([1u8; 32]),
960            )
961            .expect("can make a note");
962            let mut sct = tct::Tree::new();
963            let note_commitment = note.commit();
964            sct.insert(tct::Witness::Keep, note_commitment)
965                .expect("able to insert note commitment into SCT");
966            let anchor = sct.root();
967            let state_commitment_proof = sct
968                .witness(note_commitment)
969                .expect("able to witness just-inserted note commitment");
970            let position = state_commitment_proof.position();
971            let epoch = Fq::from(position.epoch());
972            let block = Fq::from(position.block());
973            let commitment_index = Fq::from(position.commitment());
974
975            Self {
976                state_commitment_proof,
977                anchor,
978                epoch,
979                block,
980                commitment_index,
981            }
982        }
983    }
984
985    fn make_random_note_commitment(address: Address) -> StateCommitment {
986        let note = Note::from_parts(
987            address,
988            Value::from_str("1upenumbra").expect("valid value"),
989            Rseed([1u8; 32]),
990        )
991        .expect("can make a note");
992        note.commit()
993    }
994
995    #[test]
996    fn merkle_proof_verification_succeeds() {
997        let mut rng = OsRng;
998        let (pk, vk) = generate_prepared_test_parameters::<MerkleProofCircuit>(&mut rng);
999
1000        let seed_phrase = SeedPhrase::from_randomness(&[b'f'; 32]);
1001        let sk_sender = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
1002        let fvk_sender = sk_sender.full_viewing_key();
1003        let ivk_sender = fvk_sender.incoming();
1004        let (address, _dtk_d) = ivk_sender.payment_address(0u32.into());
1005        // We will incrementally add notes to the state commitment tree, checking the merkle proofs verify
1006        // at each step.
1007        let mut sct = tct::Tree::new();
1008
1009        for _ in 0..5 {
1010            let note_commitment = make_random_note_commitment(address.clone());
1011            sct.insert(tct::Witness::Keep, note_commitment).unwrap();
1012            let anchor = sct.root();
1013            let state_commitment_proof = sct.witness(note_commitment).unwrap();
1014            let position = state_commitment_proof.position();
1015            let epoch = Fq::from(position.epoch());
1016            let block = Fq::from(position.block());
1017            let commitment_index = Fq::from(position.commitment());
1018            let circuit = MerkleProofCircuit {
1019                state_commitment_proof,
1020                anchor,
1021                epoch,
1022                block,
1023                commitment_index,
1024            };
1025            let proof = Groth16::<Bls12_377, LibsnarkReduction>::prove(&pk, circuit, &mut rng)
1026                .expect("should be able to form proof");
1027
1028            let proof_result = Groth16::<Bls12_377, LibsnarkReduction>::verify_with_processed_vk(
1029                &vk,
1030                &[Fq::from(anchor), epoch, block, commitment_index],
1031                &proof,
1032            );
1033            assert!(proof_result.is_ok());
1034        }
1035
1036        sct.end_block().expect("can end block");
1037        for _ in 0..100 {
1038            let note_commitment = make_random_note_commitment(address.clone());
1039            sct.insert(tct::Witness::Forget, note_commitment).unwrap();
1040        }
1041
1042        for _ in 0..5 {
1043            let note_commitment = make_random_note_commitment(address.clone());
1044            sct.insert(tct::Witness::Keep, note_commitment).unwrap();
1045            let anchor = sct.root();
1046            let state_commitment_proof = sct.witness(note_commitment).unwrap();
1047            let position = state_commitment_proof.position();
1048            let epoch = Fq::from(position.epoch());
1049            let block = Fq::from(position.block());
1050            let commitment_index = Fq::from(position.commitment());
1051            let circuit = MerkleProofCircuit {
1052                state_commitment_proof,
1053                anchor,
1054                epoch,
1055                block,
1056                commitment_index,
1057            };
1058            let proof = Groth16::<Bls12_377, LibsnarkReduction>::prove(&pk, circuit, &mut rng)
1059                .expect("should be able to form proof");
1060
1061            let proof_result = Groth16::<Bls12_377, LibsnarkReduction>::verify_with_processed_vk(
1062                &vk,
1063                &[Fq::from(anchor), epoch, block, commitment_index],
1064                &proof,
1065            );
1066            assert!(proof_result.is_ok());
1067        }
1068
1069        sct.end_epoch().expect("can end epoch");
1070        for _ in 0..100 {
1071            let note_commitment = make_random_note_commitment(address.clone());
1072            sct.insert(tct::Witness::Forget, note_commitment).unwrap();
1073        }
1074
1075        for _ in 0..5 {
1076            let note_commitment = make_random_note_commitment(address.clone());
1077            sct.insert(tct::Witness::Keep, note_commitment).unwrap();
1078            let anchor = sct.root();
1079            let state_commitment_proof = sct.witness(note_commitment).unwrap();
1080            let position = state_commitment_proof.position();
1081            let epoch = Fq::from(position.epoch());
1082            let block = Fq::from(position.block());
1083            let commitment_index = Fq::from(position.commitment());
1084            let circuit = MerkleProofCircuit {
1085                state_commitment_proof,
1086                anchor,
1087                epoch,
1088                block,
1089                commitment_index,
1090            };
1091            let proof = Groth16::<Bls12_377, LibsnarkReduction>::prove(&pk, circuit, &mut rng)
1092                .expect("should be able to form proof");
1093
1094            let proof_result = Groth16::<Bls12_377, LibsnarkReduction>::verify_with_processed_vk(
1095                &vk,
1096                &[Fq::from(anchor), epoch, block, commitment_index],
1097                &proof,
1098            );
1099            assert!(proof_result.is_ok());
1100        }
1101    }
1102}