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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]
### Added
- [\#237](https://github.com/Manta-Network/manta-rs/pull/237) Public input fuzzing tests for transfer protocol.
- [\#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.
- [\#172](https://github.com/Manta-Network/manta-rs/pull/172) Add abstract Phase 2 for Groth16 trusted setup
Expand Down
4 changes: 2 additions & 2 deletions manta-accounting/src/fs/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1309,14 +1309,14 @@ pub mod test {
&mut F::options()
.create_new(true)
.write(true)
.open(&path, password)
.open(path, password)
.expect("Unable to create file for writing."),
))
.expect("Unable to serialize and encrypt the data.");
let decrypted_data = T::deserialize(&mut Deserializer::new(
&mut F::options()
.read(true)
.open(&path, password)
.open(path, password)
.expect("Unable to open file for reading."),
))
.expect("Unable to decrypt and deserialize the data.");
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."
);
}
}
2 changes: 1 addition & 1 deletion manta-crypto/src/merkle_tree/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ where
InnerDigest<C>: 'i,
I: IntoIterator<Item = &'i InnerDigest<C>>,
{
let mut iter = iter.into_iter().peekable();
let mut iter = iter.into_iter();
(depth..path_length::<C, _>()).fold(base, move |acc, _| {
Self::fold_fn(parameters, index.into_parent(), &acc, default, || {
iter.next().unwrap()
Expand Down
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.")
}
}
}
2 changes: 1 addition & 1 deletion manta-parameters/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ pub mod github {
P: AsRef<Path>,
{
let path = path.as_ref();
download_unchecked(branch, data_path, &path)?;
download_unchecked(branch, data_path, path)?;
anyhow::ensure!(
verify_file(path, checksum)?,
"Checksum did not match. Expected: {:?}",
Expand Down
2 changes: 1 addition & 1 deletion manta-pay/src/crypto/constraint/arkworks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,7 @@ mod tests {
R: RngCore + ?Sized,
F: PrimeField,
{
let bound = Fp(F::from(2u64).pow(&[BITS as u64]));
let bound = Fp(F::from(2u64).pow([BITS as u64]));
check_assert_within_range::<_, BITS>(Fp(F::zero()), true);
check_assert_within_range::<_, BITS>(Fp(bound.0 - F::one()), true);
check_assert_within_range::<_, BITS>(bound, false);
Expand Down
4 changes: 2 additions & 2 deletions manta-pay/src/crypto/poseidon/arkworks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ where

#[inline]
fn apply_sbox(point: &mut Self::Field, _: &mut ()) {
point.0 = point.0.pow(&[Self::SBOX_EXPONENT, 0, 0, 0]);
point.0 = point.0.pow([Self::SBOX_EXPONENT, 0, 0, 0]);
}

#[inline]
Expand Down Expand Up @@ -206,7 +206,7 @@ where
#[inline]
fn apply_sbox(point: &mut Self::Field, _: &mut Compiler<S>) {
*point = point
.pow_by_constant(&[Self::SBOX_EXPONENT])
.pow_by_constant([Self::SBOX_EXPONENT])
.expect("Exponentiation is not allowed to fail.");
}

Expand Down
4 changes: 2 additions & 2 deletions manta-pay/src/crypto/poseidon/compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ pub mod arkworks {

#[inline]
fn apply_sbox(point: &mut Self::Field, _: &mut ()) {
point.0 = point.0.pow(&[Self::SBOX_EXPONENT, 0, 0, 0]);
point.0 = point.0.pow([Self::SBOX_EXPONENT, 0, 0, 0]);
}
}

Expand Down Expand Up @@ -429,7 +429,7 @@ pub mod arkworks {
fn apply_sbox(point: &mut Self::Field, compiler: &mut Compiler<S>) {
let _ = compiler;
*point = point
.pow_by_constant(&[Self::SBOX_EXPONENT])
.pow_by_constant([Self::SBOX_EXPONENT])
.expect("Exponentiation is not allowed to fail.");
}
}
Expand Down
2 changes: 1 addition & 1 deletion manta-pay/src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ where
#[inline]
fn derive(&self, account: AccountIndex, kind: Kind, index: KeyIndex) -> Self::SecretKey {
SecretKey::derive_from_path(
&self.seed,
self.seed,
&path_string::<C>(account, kind, index)
.parse()
.expect("Path string is valid by construction."),
Expand Down
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
2 changes: 1 addition & 1 deletion manta-trusted-setup/src/groth16/ppot/hashing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ where
#[inline]
fn hash(&self, challenge: &[u8; N], pair: (&G1Affine, &G1Affine)) -> G2Affine {
let mut hasher = BlakeHasher::default();
hasher.0.update(&[self.domain_tag]);
hasher.0.update([self.domain_tag]);
hasher.0.update(challenge);
<PerpetualPowersOfTauCeremony<S, POWERS> as Serializer<G1Affine, G1>>::serialize_uncompressed(pair.0, &mut hasher)
.unwrap();
Expand Down
2 changes: 1 addition & 1 deletion manta-trusted-setup/src/groth16/ppot/kzg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ where
item.serialize_uncompressed(&mut hasher).unwrap();
}
state.beta_g2.serialize_uncompressed(&mut hasher).unwrap();
hasher.0.update(&challenge);
hasher.0.update(challenge);
proof
.tau
.serialize(&mut hasher)
Expand Down
2 changes: 1 addition & 1 deletion manta-trusted-setup/src/groth16/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ impl kzg::Configuration for Test {
item.serialize_uncompressed(&mut hasher).unwrap();
}
state.beta_g2.serialize_uncompressed(&mut hasher).unwrap();
hasher.0.update(&challenge);
hasher.0.update(challenge);
proof
.tau
.serialize(&mut hasher)
Expand Down
2 changes: 1 addition & 1 deletion manta-trusted-setup/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ where
#[inline]
fn hash(&self, challenge: &[u8; N], ratio: (&P::G1, &P::G1)) -> P::G2 {
let mut hasher = BlakeHasher::default();
hasher.0.update(&[self.domain_tag]);
hasher.0.update([self.domain_tag]);
hasher.0.update(challenge);
ratio.0.serialize(&mut hasher).unwrap();
ratio.1.serialize(&mut hasher).unwrap();
Expand Down