diff --git a/crates/core/component/governance/src/delegator_vote/proof.rs b/crates/core/component/governance/src/delegator_vote/proof.rs index 6cfad88762..00bfa0158a 100644 --- a/crates/core/component/governance/src/delegator_vote/proof.rs +++ b/crates/core/component/governance/src/delegator_vote/proof.rs @@ -397,16 +397,14 @@ impl TryFrom for DelegatorVoteProof { mod tests { use super::*; - use ark_ff::{PrimeField, UniformRand}; + use ark_ff::PrimeField; use decaf377::{Fq, Fr}; use penumbra_asset::{asset, Value}; use penumbra_keys::keys::{SeedPhrase, SpendKey}; use penumbra_num::Amount; - use penumbra_proof_params::generate_prepared_test_parameters; use penumbra_sct::Nullifier; use proptest::prelude::*; - use rand_core::OsRng; fn fr_strategy() -> BoxedStrategy { any::<[u8; 32]>() @@ -488,85 +486,77 @@ mod tests { } } - proptest! { - #![proptest_config(ProptestConfig::with_cases(1))] - #[test] - #[should_panic] - fn delegator_vote_invalid_start_position(seed_phrase_randomness in any::<[u8; 32]>(), spend_auth_randomizer in fr_strategy(), value_amount in 1..2000000000u64, num_commitments in 1000..2000u64) { - let mut rng = OsRng; - let (pk, vk) = generate_prepared_test_parameters::(&mut rng); - - let seed_phrase = SeedPhrase::from_randomness(&seed_phrase_randomness); - let sk_sender = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0)); - let fvk_sender = sk_sender.full_viewing_key(); - let ivk_sender = fvk_sender.incoming(); - let (sender, _dtk_d) = ivk_sender.payment_address(0u32.into()); - - let value_to_send = Value { - amount: value_amount.into(), - asset_id: asset::Cache::with_known_assets().get_unit("upenumbra").unwrap().id(), - }; - - let note = Note::generate(&mut rng, &sender, value_to_send); - let note_commitment = note.commit(); - let rsk = sk_sender.spend_auth_key().randomize(&spend_auth_randomizer); - let nk = *sk_sender.nullifier_key(); - let ak: VerificationKey = sk_sender.spend_auth_key().into(); - let mut sct = tct::Tree::new(); - - // Next, we simulate the case where the SCT is not empty by adding `num_commitments` - // unrelated items in the SCT. - for _ in 0..num_commitments { - let random_note_commitment = Note::generate(&mut rng, &sender, value_to_send).commit(); - sct.insert(tct::Witness::Keep, random_note_commitment).unwrap(); - } + prop_compose! { + fn arb_invalid_delegator_vote_statement_nonzero_start()(v_blinding in fr_strategy(), spend_auth_randomizer in fr_strategy(), asset_id64 in any::(), address_index in any::(), amount in any::(), seed_phrase_randomness in any::<[u8; 32]>(), rseed_randomness in any::<[u8; 32]>(), num_commitments in 0..100) -> (DelegatorVoteProofPublic, DelegatorVoteProofPrivate) { + let seed_phrase = SeedPhrase::from_randomness(&seed_phrase_randomness); + let sk_sender = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0)); + let fvk_sender = sk_sender.full_viewing_key(); + let ivk_sender = fvk_sender.incoming(); + let (sender, _dtk_d) = ivk_sender.payment_address(address_index.into()); + let value_to_send = Value { + amount: Amount::from(amount), + asset_id: asset::Id(Fq::from(asset_id64)), + }; + let note = Note::from_parts( + sender, + value_to_send, + Rseed(rseed_randomness), + ).expect("should be able to create note"); + let note_commitment = note.commit(); + let rsk = sk_sender.spend_auth_key().randomize(&spend_auth_randomizer); + let nk = *sk_sender.nullifier_key(); + let ak: VerificationKey = sk_sender.spend_auth_key().into(); - sct.insert(tct::Witness::Keep, note_commitment).unwrap(); - let anchor = sct.root(); - let state_commitment_proof = sct.witness(note_commitment).unwrap(); + let mut sct = tct::Tree::new(); - let not_first_note_commitment = Note::generate(&mut rng, &sender, value_to_send).commit(); - sct.insert(tct::Witness::Keep, not_first_note_commitment).unwrap(); - let start_position = sct.witness(not_first_note_commitment).unwrap().position(); + // Next, we simulate the case where the SCT is not empty by adding `num_commitments` + // unrelated items in the SCT. + for i in 0..num_commitments { + // To avoid duplicate note commitments, we use the `i` counter as the Rseed randomness + let rseed = Rseed([i as u8; 32]); + let dummy_note_commitment = Note::from_parts(sender, value_to_send, rseed).expect("can create note").commit(); + sct.insert(tct::Witness::Keep, dummy_note_commitment).expect("can insert note commitment into SCT"); + } - let balance_commitment = value_to_send.commit(Fr::from(0u64)); - let rk: VerificationKey = rsk.into(); - let nf = Nullifier::derive(&nk, state_commitment_proof.position(), ¬e_commitment); + sct.insert(tct::Witness::Keep, note_commitment).expect("can insert note commitment into SCT"); + let anchor = sct.root(); + let state_commitment_proof = sct.witness(note_commitment).expect("can witness note commitment"); - let blinding_r = Fq::rand(&mut OsRng); - let blinding_s = Fq::rand(&mut OsRng); - let public = DelegatorVoteProofPublic { - anchor, - balance_commitment, - nullifier: nf, - rk, - start_position, - }; - let private = DelegatorVoteProofPrivate { - state_commitment_proof, - note, - v_blinding: Fr::from(0u64), - spend_auth_randomizer, - ak, - nk, - }; + let rseed = Rseed([num_commitments as u8; 32]); + let not_first_note_commitment = Note::from_parts(sender, value_to_send, rseed).expect("can create note").commit(); + sct.insert(tct::Witness::Keep, not_first_note_commitment).expect("can insert note commitment into SCT"); + // All proposals should have a position commitment index of zero, but this one will not, so + // expect the proof to fail. + let start_position = sct.witness(not_first_note_commitment).expect("can witness note commitment").position(); + let balance_commitment = value_to_send.commit(v_blinding); + let rk: VerificationKey = rsk.into(); + let nullifier = Nullifier::derive(&nk, state_commitment_proof.position(), ¬e_commitment); - let proof = DelegatorVoteProof::prove( - blinding_r, - blinding_s, - &pk, - public.clone(), private - ).expect("can form proof in release mode, but it should not verify"); - - // In debug mode, we won't be able to construct a valid proof if the start position - // commitment index is non-zero. However, in release mode, the proof will be constructed - // but not verify. This is due to the fact there is an assertion during constraint - // generation (upstream) where we panic in debug mode if the circuit is not satisifiable, - // but not in release mode. To ensure the same behavior in this test for both modes, - // we panic if we get here and the proof does not verify (expected). - let proof_result = proof.verify(&vk, public); - proof_result.expect("we expect this proof _not_ to verify, so this will cause a panic"); + let public = DelegatorVoteProofPublic { + anchor, + balance_commitment, + nullifier, + rk, + start_position, + }; + let private = DelegatorVoteProofPrivate { + state_commitment_proof, + note, + v_blinding, + spend_auth_randomizer, + ak, + nk, + }; + (public, private) + } } + + proptest! { + #[test] + fn delegator_vote_invalid_start_position((public, private) in arb_invalid_delegator_vote_statement_nonzero_start()) { + assert!(check_satisfaction(&public, &private).is_err()); + assert!(check_circuit_satisfaction(public, private).is_err()); + } } }