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

Feat: fuzzing test #237

Merged
merged 17 commits into from
Sep 8, 2022
Merged
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]
### Added
- [\#215](https://github.com/Manta-Network/manta-rs/pull/215) Add windowed multiplication algorithm for groups.
- [\#237](https://github.com/Manta-Network/manta-rs/pull/237) Public input fuzzing tests for transfer protocol
- [\#215](https://github.com/Manta-Network/manta-rs/pull/215) Add windowed multiplication algorithm for groups
- [\#213](https://github.com/Manta-Network/manta-rs/pull/197) Add Ceremony Utilities
- [\#206](https://github.com/Manta-Network/manta-rs/pull/206) Move Poseidon sage script to test the hardcoded round constant values.
- [\#206](https://github.com/Manta-Network/manta-rs/pull/206) Move Poseidon sage script to test the hardcoded round constant values
- [\#172](https://github.com/Manta-Network/manta-rs/pull/172) Add abstract Phase 2 for Groth16 trusted setup
- [\#193](https://github.com/Manta-Network/manta-rs/pull/193) Add Bn254 curve backend for Groth16 trusted setup
- [\#196](https://github.com/Manta-Network/manta-rs/pull/172) Add fixed base scalar multiplication using precomputed bases
Expand Down
25 changes: 25 additions & 0 deletions manta-crypto/src/constraint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,3 +364,28 @@ pub mod measure {
}
}
}
/// Testing Framework
SupremoUGH marked this conversation as resolved.
Show resolved Hide resolved
#[cfg(feature = "test")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "test")))]
pub mod test {
use super::*;
use core::fmt::Debug;

/// Checks that attempting to verify `proof` against fuzzed inputs fails.
#[inline]
pub fn fuzz_public_input<P, F>(
SupremoUGH marked this conversation as resolved.
Show resolved Hide resolved
context: &P::VerifyingContext,
input: &P::Input,
proof: &P::Proof,
mut fuzzer: F,
) where
P: ProofSystem,
P::Error: Debug,
F: FnMut(&P::Input) -> P::Input,
{
assert!(
!P::verify(context, &fuzzer(input), proof).expect("Unable to verify proof."),
"Proof remained valid after fuzzing."
);
}
}
84 changes: 84 additions & 0 deletions manta-crypto/src/rand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,3 +530,87 @@ pub trait Rand: RngCore {
}

impl<R> Rand for R where R: RngCore + ?Sized {}

/// Fuzzing module
pub mod fuzz {
use super::*;

#[cfg(all(feature = "arkworks", feature = "rand"))]
use crate::arkworks::ff::{BigInteger, PrimeField};

/// Fuzz Trait
pub trait Fuzz<M = ()> {
/// Changes one bit of `self` at random.
fn fuzz<R>(&self, rng: &mut R) -> Self
where
R: RngCore + ?Sized;
}

impl Fuzz for bool {
#[inline]
fn fuzz<R>(&self, rng: &mut R) -> Self
where
R: RngCore + ?Sized,
{
let _ = rng;
self ^ true
}
}

#[cfg(feature = "rand")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "rand")))]
impl<M, T> Fuzz<Vec<M>> for Vec<T>
where
T: Clone + Fuzz<M>,
{
#[inline]
fn fuzz<R>(&self, rng: &mut R) -> Self
where
R: RngCore + ?Sized,
{
let position = rng.gen_range(0..self.len());
let mut fuzzed_self = self.clone();
fuzzed_self[position] = self[position].fuzz(rng);
fuzzed_self
}
}

/// BigInteger Marker Type
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct BigIntegerMarker;

#[cfg(all(feature = "arkworks", feature = "rand"))]
#[cfg_attr(doc_cfg, doc(cfg(all(feature = "arkworks", feature = "rand"))))]
impl<B> Fuzz<BigIntegerMarker> for B
where
B: BigInteger,
{
#[inline]
fn fuzz<R>(&self, rng: &mut R) -> Self
where
R: RngCore + ?Sized,
{
B::from_bits_be(&self.to_bits_be().fuzz(rng))
}
}

/// Prime Field Marker Type
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct PrimeFieldMarker;

#[cfg(all(feature = "arkworks", feature = "rand"))]
#[cfg_attr(doc_cfg, doc(cfg(all(feature = "arkworks", feature = "rand"))))]
impl<P> Fuzz<PrimeFieldMarker> for P
where
P: PrimeField,
{
#[inline]
fn fuzz<R>(&self, rng: &mut R) -> Self
where
R: RngCore + ?Sized,
{
P::from_repr(self.into_repr().fuzz(rng))
.expect("Computing the field element from a big integer is not supposed to fail.")
}
}
}
106 changes: 103 additions & 3 deletions manta-pay/src/test/transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@ use crate::{
test::payment::UtxoAccumulator,
util::scale::{assert_valid_codec, assert_valid_io_codec},
};
use ark_std::rand::RngCore;
use core::fmt::Debug;
use manta_accounting::transfer::{
test::assert_valid_proof, Configuration, ProofSystemError, TransferPost, VerifyingContext,
};
use manta_crypto::{
accumulator::Accumulator,
constraint::{measure::Measure, ProofSystem as _},
rand::{OsRng, Rand},
constraint::{self, measure::Measure, test::fuzz_public_input, ProofSystem as _},
rand::{fuzz::Fuzz, OsRng, Rand, Sample},
};
use std::io::Cursor;

Expand Down Expand Up @@ -145,6 +150,101 @@ fn generate_proof_input_is_compatibile() {
);
}

/// Checks that a [`TransferPost`] is valid, and that its proof cannot be verified when tested against a fuzzed
/// or randomized `public_input`.
#[inline]
fn validity_check_with_fuzzing<C, R, A, M>(
verifying_context: &VerifyingContext<C>,
post: &TransferPost<C>,
rng: &mut R,
) where
A: Clone + Sample + Fuzz<M>,
C: Configuration,
C::ProofSystem: constraint::ProofSystem<Input = Vec<A>>,
ProofSystemError<C>: Debug,
R: RngCore + ?Sized,
TransferPost<C>: Debug,
{
let public_input = post.generate_proof_input();
let proof = &post.validity_proof;
assert_valid_proof(verifying_context, post);
fuzz_public_input::<C::ProofSystem, _>(verifying_context, &public_input, proof, |input| {
input.fuzz(rng)
});
fuzz_public_input::<C::ProofSystem, _>(verifying_context, &public_input, proof, |input| {
(0..input.len()).map(|_| rng.gen()).collect()
});
}

/// Tests a [`Mint`] proof is valid verified against the right public input and invalid
/// when the public input has been fuzzed or randomly generated.
#[test]
fn mint_proof_validity() {
SupremoUGH marked this conversation as resolved.
Show resolved Hide resolved
let mut rng = OsRng;
let parameters = rng.gen();
let mut utxo_accumulator = UtxoAccumulator::new(rng.gen());
let (proving_context, verifying_context) = Mint::generate_context(
&(),
FullParameters::new(&parameters, utxo_accumulator.model()),
&mut rng,
)
.expect("Unable to create proving and verifying contexts.");
let post = Mint::sample_post(
&proving_context,
&parameters,
&mut utxo_accumulator,
&mut rng,
)
.expect("Random Mint should have produced a proof.");
validity_check_with_fuzzing(&verifying_context, &post, &mut rng);
}

/// Tests a [`PrivateTransfer`] proof is valid verified against the right public input and invalid
/// when the public input has been fuzzed or randomly generated.
#[test]
fn private_transfer_proof_validity() {
let mut rng = OsRng;
let parameters = rng.gen();
let mut utxo_accumulator = UtxoAccumulator::new(rng.gen());
let (proving_context, verifying_context) = PrivateTransfer::generate_context(
&(),
FullParameters::new(&parameters, utxo_accumulator.model()),
&mut rng,
)
.expect("Unable to create proving and verifying contexts.");
let post = PrivateTransfer::sample_post(
&proving_context,
&parameters,
&mut utxo_accumulator,
&mut rng,
)
.expect("Random Private Transfer should have produced a proof.");
validity_check_with_fuzzing(&verifying_context, &post, &mut rng);
}

/// Tests a [`Reclaim`] proof is valid verified against the right public input and invalid
/// when the public input has been fuzzed or randomly generated.
#[test]
fn reclaim_proof_validity() {
let mut rng = OsRng;
let parameters = rng.gen();
let mut utxo_accumulator = UtxoAccumulator::new(rng.gen());
let (proving_context, verifying_context) = Reclaim::generate_context(
&(),
FullParameters::new(&parameters, utxo_accumulator.model()),
&mut rng,
)
.expect("Unable to create proving and verifying contexts.");
let post = Reclaim::sample_post(
&proving_context,
&parameters,
&mut utxo_accumulator,
&mut rng,
)
.expect("Random Reclaim should have produced a proof.");
validity_check_with_fuzzing(&verifying_context, &post, &mut rng);
}

/// Asserts that `proof` can be SCALE encoded and decoded with at least [`Vec`], [`Cursor`], and
/// [`File`](std::fs::File).
#[inline]
Expand Down Expand Up @@ -201,7 +301,7 @@ fn private_transfer_proof_scale_codec() {
assert_valid_proof_codec(post.assert_valid_proof(&verifying_context));
}

/// Tests the SCALE encoding and decoding of a [`Mint`] proof.
/// Tests the SCALE encoding and decoding of a [`Reclaim`] proof.
#[test]
fn reclaim_proof_scale_codec() {
let mut rng = OsRng;
Expand Down