Skip to content

Commit

Permalink
test: strategy for invalid start position in DelegatorVoteCircuit
Browse files Browse the repository at this point in the history
  • Loading branch information
redshiftzero committed Jan 29, 2024
1 parent dc5e20b commit 27b0df5
Showing 1 changed file with 66 additions and 76 deletions.
142 changes: 66 additions & 76 deletions crates/core/component/governance/src/delegator_vote/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,16 +397,14 @@ impl TryFrom<pb::ZkDelegatorVoteProof> 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<Fr> {
any::<[u8; 32]>()
Expand Down Expand Up @@ -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::<DelegatorVoteCircuit>(&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<SpendAuth> = 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::<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) -> (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<SpendAuth> = 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<SpendAuth> = rsk.into();
let nf = Nullifier::derive(&nk, state_commitment_proof.position(), &note_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<SpendAuth> = rsk.into();
let nullifier = Nullifier::derive(&nk, state_commitment_proof.position(), &note_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());
}
}
}

0 comments on commit 27b0df5

Please sign in to comment.