Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add negative proptest strategies for remaining circuits #3671

Merged
merged 6 commits into from
Jan 29, 2024
Merged
63 changes: 63 additions & 0 deletions crates/core/component/dex/src/swap/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,4 +365,67 @@ mod tests {
assert!(check_circuit_satisfaction(public, private).is_ok());
}
}

prop_compose! {
// This strategy generates a swap statement with an invalid fee blinding factor.
fn arb_invalid_swap_statement_fee_commitment()(fee_blinding in fr_strategy(), invalid_fee_blinding in fr_strategy(), address_index in any::<u32>(), value1_amount in any::<u64>(), seed_phrase_randomness in any::<[u8; 32]>(), rseed_randomness in any::<[u8; 32]>()) -> (SwapProofPublic, SwapProofPrivate) {
let seed_phrase = SeedPhrase::from_randomness(&seed_phrase_randomness);
let sk_trader = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
let fvk_trader = sk_trader.full_viewing_key();
let ivk_trader = fvk_trader.incoming();
let (claim_address, _dtk_d) = ivk_trader.payment_address(address_index.into());

let gm = asset::Cache::with_known_assets().get_unit("gm").unwrap();
let gn = asset::Cache::with_known_assets().get_unit("gn").unwrap();
let trading_pair = TradingPair::new(gm.id(), gn.id());

let delta_1_i = Amount::from(value1_amount);
let delta_2_i = Amount::from(0u64);
let fee = Fee::default();

let rseed = Rseed(rseed_randomness);
let swap_plaintext = SwapPlaintext {
trading_pair,
delta_1_i,
delta_2_i,
claim_fee: fee,
claim_address,
rseed,
};
let swap_commitment = swap_plaintext.swap_commitment();

let value_1 = Value {
amount: swap_plaintext.delta_1_i,
asset_id: swap_plaintext.trading_pair.asset_1(),
};
let value_2 = Value {
amount: swap_plaintext.delta_2_i,
asset_id: swap_plaintext.trading_pair.asset_2(),
};
let value_fee = Value {
amount: swap_plaintext.claim_fee.amount(),
asset_id: swap_plaintext.claim_fee.asset_id(),
};
let mut balance = Balance::default();
balance -= value_1;
balance -= value_2;
balance -= value_fee;
let balance_commitment = balance.commit(fee_blinding);

let invalid_fee_commitment = swap_plaintext.claim_fee.commit(invalid_fee_blinding);

let public = SwapProofPublic { balance_commitment, swap_commitment, fee_commitment: invalid_fee_commitment };
let private = SwapProofPrivate { fee_blinding, swap_plaintext };

(public, private)
}
}

proptest! {
#[test]
fn swap_proof_invalid_fee_commitment((public, private) in arb_invalid_swap_statement_fee_commitment()) {
assert!(check_satisfaction(&public, &private).is_err());
assert!(check_circuit_satisfaction(public, private).is_err());
}
}
}
186 changes: 186 additions & 0 deletions crates/core/component/dex/src/swap_claim/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -653,4 +653,190 @@ mod tests {
assert!(check_circuit_satisfaction(public, private).is_ok());
}
}

prop_compose! {
// This strategy is invalid because the fee is not equal to the claim fee.
fn arb_invalid_swapclaim_statement_fee()(seed_phrase_randomness in any::<[u8; 32]>(), rseed_randomness in any::<[u8; 32]>(), value1_amount in 2..200u64, fee_amount in any::<u64>(), test_bsod in unfilled_bsod_strategy()) -> (SwapClaimProofPublic, SwapClaimProofPrivate) {
let seed_phrase = SeedPhrase::from_randomness(&seed_phrase_randomness);
let sk_recipient = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
let fvk_recipient = sk_recipient.full_viewing_key();
let ivk_recipient = fvk_recipient.incoming();
let (claim_address, _dtk_d) = ivk_recipient.payment_address(0u32.into());
let nk = *sk_recipient.nullifier_key();

let gm = asset::Cache::with_known_assets().get_unit("gm").unwrap();
let gn = asset::Cache::with_known_assets().get_unit("gn").unwrap();
let trading_pair = TradingPair::new(gm.id(), gn.id());

let delta_1_i = Amount::from(value1_amount);
let delta_2_i = Amount::from(0u64);
let fee = Fee::default();

let rseed = Rseed(rseed_randomness);
let swap_plaintext = SwapPlaintext {
trading_pair,
delta_1_i,
delta_2_i,
claim_fee: fee,
claim_address,
rseed,
};
let incorrect_fee = Fee::from_staking_token_amount(Amount::from(fee_amount));
let mut sct = tct::Tree::new();
let swap_commitment = swap_plaintext.swap_commitment();
sct.insert(tct::Witness::Keep, swap_commitment).unwrap();
let anchor = sct.root();
let state_commitment_proof = sct.witness(swap_commitment).unwrap();
let position = state_commitment_proof.position();
let nullifier = Nullifier::derive(&nk, position, &swap_commitment);
let epoch_duration = 20;
let height = epoch_duration * position.epoch() + position.block();

let output_data = BatchSwapOutputData {
delta_1: test_bsod.delta_1,
delta_2: test_bsod.delta_2,
lambda_1: test_bsod.lambda_1,
lambda_2: test_bsod.lambda_2,
unfilled_1: test_bsod.unfilled_1,
unfilled_2: test_bsod.unfilled_2,
height: height.into(),
trading_pair: swap_plaintext.trading_pair,
epoch_starting_height: (epoch_duration * position.epoch()).into(),
};
let (lambda_1, lambda_2) = output_data.pro_rata_outputs((delta_1_i, delta_2_i));

let (output_rseed_1, output_rseed_2) = swap_plaintext.output_rseeds();
let note_blinding_1 = output_rseed_1.derive_note_blinding();
let note_blinding_2 = output_rseed_2.derive_note_blinding();
let (output_1_note, output_2_note) = swap_plaintext.output_notes(&output_data);
let note_commitment_1 = output_1_note.commit();
let note_commitment_2 = output_2_note.commit();

let public = SwapClaimProofPublic {
anchor,
nullifier,
claim_fee: incorrect_fee,
output_data,
note_commitment_1,
note_commitment_2,
};
let private = SwapClaimProofPrivate {
swap_plaintext,
state_commitment_proof,
nk,
lambda_1,
lambda_2,
note_blinding_1,
note_blinding_2,
};

(public, private)
}
}

proptest! {
#[test]
fn swap_claim_proof_invalid_fee((public, private) in arb_invalid_swapclaim_statement_fee()) {
assert!(check_satisfaction(&public, &private).is_err());
assert!(check_circuit_satisfaction(public, private).is_err());
}
}

prop_compose! {
// This strategy is invalid because the block height of the swap commitment does not match
// the height of the batch swap output data.
fn arb_invalid_swapclaim_swap_commitment_height()(seed_phrase_randomness in any::<[u8; 32]>(), rseed_randomness in any::<[u8; 32]>(), value1_amount in 2..200u64, fee_amount in any::<u64>(), test_bsod in unfilled_bsod_strategy()) -> (SwapClaimProofPublic, SwapClaimProofPrivate) {
let seed_phrase = SeedPhrase::from_randomness(&seed_phrase_randomness);
let sk_recipient = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
let fvk_recipient = sk_recipient.full_viewing_key();
let ivk_recipient = fvk_recipient.incoming();
let (claim_address, _dtk_d) = ivk_recipient.payment_address(0u32.into());
let nk = *sk_recipient.nullifier_key();

let gm = asset::Cache::with_known_assets().get_unit("gm").unwrap();
let gn = asset::Cache::with_known_assets().get_unit("gn").unwrap();
let trading_pair = TradingPair::new(gm.id(), gn.id());

let delta_1_i = Amount::from(value1_amount);
let delta_2_i = Amount::from(0u64);
let fee = Fee::default();

let rseed = Rseed(rseed_randomness);
let swap_plaintext = SwapPlaintext {
trading_pair,
delta_1_i,
delta_2_i,
claim_fee: fee,
claim_address,
rseed,
};
let incorrect_fee = Fee::from_staking_token_amount(Amount::from(fee_amount));
let mut sct = tct::Tree::new();
let swap_commitment = swap_plaintext.swap_commitment();
sct.insert(tct::Witness::Keep, swap_commitment).unwrap();
let anchor = sct.root();
let state_commitment_proof = sct.witness(swap_commitment).unwrap();
let position = state_commitment_proof.position();
let nullifier = Nullifier::derive(&nk, position, &swap_commitment);

// End the block, and then add a dummy commitment that we'll use
// to compute the position and block height that the BSOD corresponds to.
sct.end_block().expect("can end block");
let dummy_swap_commitment = tct::StateCommitment(Fq::from(1));
sct.insert(tct::Witness::Keep, dummy_swap_commitment).unwrap();
let dummy_state_commitment_proof = sct.witness(swap_commitment).unwrap();
let dummy_position = dummy_state_commitment_proof.position();

let epoch_duration = 20;
let height = epoch_duration * dummy_position.epoch() + dummy_position.block();

let output_data = BatchSwapOutputData {
delta_1: test_bsod.delta_1,
delta_2: test_bsod.delta_2,
lambda_1: test_bsod.lambda_1,
lambda_2: test_bsod.lambda_2,
unfilled_1: test_bsod.unfilled_1,
unfilled_2: test_bsod.unfilled_2,
height: height.into(),
trading_pair: swap_plaintext.trading_pair,
epoch_starting_height: (epoch_duration * dummy_position.epoch()).into(),
};
let (lambda_1, lambda_2) = output_data.pro_rata_outputs((delta_1_i, delta_2_i));

let (output_rseed_1, output_rseed_2) = swap_plaintext.output_rseeds();
let note_blinding_1 = output_rseed_1.derive_note_blinding();
let note_blinding_2 = output_rseed_2.derive_note_blinding();
let (output_1_note, output_2_note) = swap_plaintext.output_notes(&output_data);
let note_commitment_1 = output_1_note.commit();
let note_commitment_2 = output_2_note.commit();

let public = SwapClaimProofPublic {
anchor,
nullifier,
claim_fee: incorrect_fee,
output_data,
note_commitment_1,
note_commitment_2,
};
let private = SwapClaimProofPrivate {
swap_plaintext,
state_commitment_proof,
nk,
lambda_1,
lambda_2,
note_blinding_1,
note_blinding_2,
};

(public, private)
}
}

proptest! {
#[test]
fn swap_claim_proof_invalid_swap_commitment_height((public, private) in arb_invalid_swapclaim_swap_commitment_height()) {
assert!(check_satisfaction(&public, &private).is_err());
assert!(check_circuit_satisfaction(public, private).is_err());
}
}
}
35 changes: 35 additions & 0 deletions crates/core/component/shielded-pool/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,4 +296,39 @@ mod tests {
assert!(check_circuit_satisfaction(public, private).is_ok());
}
}

fn nonzero_u128() -> impl Strategy<Value = u128> {
prop::num::u128::ANY.prop_filter("nonzero", |x| *x != 0)
}

fn nonzero_u64() -> impl Strategy<Value = u64> {
prop::num::u64::ANY.prop_filter("nonzero", |x| *x != 0)
}

prop_compose! {
// The circuit should be unsatisfiable if the rate used by the prover is incorrect.
// We generate a random rate, filtering out non-zero denominators to avoid division by zero.
// This is the "true" rate.
// Next, we add a (u64) random value to the true rate, and the prover generates the balance
// using this incorrect rate.
fn arb_invalid_convert_statement_wrong_rate(balance_blinding: Fr)(amount in any::<u64>(), from_asset_id64 in any::<u64>(), to_asset_id64 in any::<u64>(), rate_num in nonzero_u64(), rate_denom in nonzero_u128(), random_rate_num in nonzero_u64()) -> (ConvertProofPublic, ConvertProofPrivate) {
let rate = U128x128::ratio(u128::from(rate_num), rate_denom).expect("the bounds will make this not overflow");
let incorrect_rate = rate.checked_add(&U128x128::ratio(random_rate_num, 1u64).expect("should not overflow")).expect("should not overflow");
let from = asset::Id(Fq::from(from_asset_id64));
let to = asset::Id(Fq::from(to_asset_id64));
let amount = Amount::from(amount);
let balance = Balance::from(Value { asset_id: to, amount: incorrect_rate.apply_to_amount(&amount).expect("the bounds will make this not overflow")}) - Value {asset_id: from, amount};
let public = ConvertProofPublic { from, to, rate, balance_commitment: balance.commit(balance_blinding) };
let private = ConvertProofPrivate { amount, balance_blinding };
(public, private)
}
}

proptest! {
#[test]
fn convert_proof_invalid_convert_statement_wrong_rate((public, private) in arb_invalid_convert_statement_wrong_rate(Fr::from(1u64))) {
assert!(check_satisfaction(&public, &private).is_err());
assert!(check_circuit_satisfaction(public, private).is_err());
}
}
}
Loading
Loading