add some quickcheck tests for bigint

This commit is contained in:
Balazs Komuves 2026-01-30 01:33:20 +01:00
parent f6c30ed6e0
commit 0628b17ca1
No known key found for this signature in database
GPG Key ID: F63B7AEF18435562
6 changed files with 121 additions and 19 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "rust-poseidon-bn254-pure"
version = "0.0.1"
version = "0.0.2"
edition = "2021"
license = "MIT OR Apache-2.0"
authors = ["Balazs Komuves"]
@ -11,13 +11,16 @@ default-run = "testmain"
unroll = ">= 0.1.5"
[dev-dependencies]
criterion = ">= 0.8"
criterion = ">= 0.8"
quickcheck = "1"
quickcheck_macros = "1"
[lib]
bench = false
[profile.release]
debug = true
debug = false
# debug = true
[[bin]]
name = "testmain"

View File

@ -39,24 +39,25 @@ There are three main types:
- `BigInt<N>` is an unsigned big integer consisting of `N` words (so `2^(32*N)` or `2^(64*N)` bits);
- `Felt`, short for "Field Element", is a prime field element in the standard representation
(integers modulo `p`);
- `Mont` is a field element in the Montgomery represntation. This is used internally
for calculations, as the multiplications is much faster this way.
- `Mont` is a field element in the Montgomery representation. This is used internally
for calculations, as multiplication (the main bottleneck) is much faster this way.
The core functionality of the Poseidon family of hash functions is the _permutation_,
which takes an array of `t` field elements, and returns the same:
which takes an array of `t >= 2` field elements, and returns the same:
fn permute( [Felt; t] ) -> [Felt; t]
From this one can build all kind of stuff, including a proper hash function (using
From this one can build all kinds of stuff, including a proper hash function (using
the so-called "sponge construction). The latter is not implemented in `circomlib`,
instead, what they have is a compression function parametrized by `t`:
instead, what they have is a _compression function_ parametrized by `t`:
fn compress( [Felt; t-1] ) -> Felt
This takes `t-1` field elements and returns one (which is interpreted as a hash).
This takes `t-1` field elements and returns a single one (which is interpreted as a hash.
Note that a field element contains about 254 bits of information, which is pretty fine for a cryptographic hash output)
This is implemented by extending the input with a 0, applying the permutation, and
taking the first element of the output vector (note: in `circomlib`, the extra 0 is
This is implemented by extending the input with a `0`, applying the permutation, and
taking the first element of the output vector (note: in `circomlib`, the extra `0` is
at the beginning, not at the end, but that doesn't matter at all; just be consistent).
Remark: That extra zero (called the "capacity") is _extremely important_, without

View File

@ -11,7 +11,7 @@
use std::fmt;
use std::cmp::{Ordering,min};
use std::ops::{Add,Sub,RangeFull};
use std::ops::{Neg,Add,Sub,RangeFull};
use std::random::{RandomSource,Distribution};
@ -22,7 +22,7 @@ use crate::bn254::constant::{PRIME_ARRAY};
//------------------------------------------------------------------------------
#[derive(Copy, Clone, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct BigInt<const N: usize>([u32; N]);
pub type BigInt256 = BigInt<8>;
@ -50,6 +50,11 @@ impl<const N: usize> BigInt<N> {
//------------------------------------------------------------------------------
// standard numeric traits
impl<const N: usize> Neg for BigInt<N> {
type Output = Self;
fn neg(self) -> Self { BigInt::neg(self) }
}
impl<const N: usize> Add for BigInt<N> {
type Output = Self;
fn add(self, other: Self) -> Self { BigInt::add(self,other) }
@ -274,6 +279,11 @@ impl<const N: usize> BigInt<N> {
out
}
pub fn neg(big: BigInt<N>) -> BigInt<N> {
BigInt::sub( BigInt::zero() , big )
}
//------------------------------------
// multiplication

View File

@ -22,7 +22,7 @@ use crate::bn254::montgomery::*;
type Big = BigInt<8>;
#[derive(Copy, Clone, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Felt(Big);
//------------------------------------------------------------------------------

View File

@ -3,10 +3,15 @@
#![allow(unused)]
use quickcheck::{Arbitrary, Gen};
use quickcheck_macros::quickcheck;
use crate::bn254::bigint::*;
use crate::bn254::test::properties::*;
type Big = BigInt<8>;
type Big = BigInt<8>;
type Big7 = BigInt<7>;
type Big9 = BigInt<9>;
//------------------------------------------------------------------------------
@ -50,3 +55,82 @@ fn prop_to_from_bytes_be<const N: usize>(bs: [u8; 4*N]) -> bool {
}
//------------------------------------------------------------------------------
// quickcheck property-based testing
impl<const N: usize> Group for BigInt<N> {}
// ...as you apparently cannot have an impl for an array...
#[derive(Debug, Copy, Clone)]
struct ByteArray<const K: usize>([u8; K]);
impl<const K: usize> Arbitrary for ByteArray<K> {
fn arbitrary(g: &mut Gen) -> ByteArray<K> {
let mut bs: [u8; K] = [0; K];
for i in 0..K {
bs[i] = u8::arbitrary(g);
}
ByteArray(bs)
}
}
impl<const N: usize> Arbitrary for BigInt<N> {
fn arbitrary(g: &mut Gen) -> BigInt<N> {
let mut xs: [u32; N] = [0; N];
for i in 0..N {
xs[i] = u32::arbitrary(g);
}
BigInt::make(xs)
}
}
//--------------------------------------
#[quickcheck]
fn from_to_bytes_le(x: Big7) -> bool { prop_from_to_bytes_le::<7>(x) }
#[quickcheck]
fn from_to_bytes_be(x: Big7) -> bool { prop_from_to_bytes_be::<7>(x) }
#[quickcheck]
fn to_fom_bytes_le(bs: ByteArray<{7*4}>) -> bool { prop_to_from_bytes_le::<7>(bs.0) }
#[quickcheck]
fn to_fom_bytes_be (bs: ByteArray<{7*4}>) -> bool { prop_to_from_bytes_be::<7>(bs.0) }
//--------------------------------------
#[quickcheck]
fn left_additive_unit(x: Big) -> bool { prop_left_additive_unit(x) }
#[quickcheck]
fn right_additive_unit(x: Big) -> bool { prop_right_additive_unit(x) }
#[quickcheck]
fn sub_zero(x: Big) -> bool { prop_sub_zero(x) }
#[quickcheck]
fn zero_sub(x: Big) -> bool { prop_zero_sub(x) }
#[quickcheck]
fn add_commutative(x: Big, y: Big) -> bool { prop_add_commutative(x,y) }
#[quickcheck]
fn sub_anticommutative(x: Big, y: Big) -> bool { prop_sub_anticommutative(x,y) }
#[quickcheck]
fn neg_involutive(x: Big) -> bool { prop_neg_involutive(x) }
#[quickcheck]
fn add_sub(x: Big, y: Big) -> bool { prop_add_sub(x,y) }
#[quickcheck]
fn sub_add(x: Big, y: Big) -> bool { prop_sub_add(x,y) }
#[quickcheck]
fn sub_neg_add(x: Big, y: Big) -> bool { prop_sub_neg_add(x,y) }
#[quickcheck]
fn sub_add_neg(x: Big, y: Big) -> bool { prop_sub_add_neg(x,y) }
//------------------------------------------------------------------------------

View File

@ -9,9 +9,13 @@ use std::ops::{Neg,Add,Sub,Mul,Div};
//------------------------------------------------------------------------------
pub trait Group = Copy + Clone + Default + From<u32> + Eq + Neg<Output=Self> + Add<Output=Self> + Sub<Output=Self>;
pub trait Ring = Group + Mul<Output=Self>;
pub trait Field = Ring + Div<Output=Self>;
// pub trait Group = Copy + Clone + Default + From<u32> + Eq + Neg<Output=Self> + Add<Output=Self> + Sub<Output=Self>;
// pub trait Ring = Group + Mul<Output=Self>;
// pub trait Field = Ring + Div<Output=Self>;
pub trait Group : Copy + Clone + Default + From<u32> + Eq + Neg<Output=Self> + Add<Output=Self> + Sub<Output=Self> {}
pub trait Ring : Group + Mul<Output=Self> {}
pub trait Field : Ring + Div<Output=Self> {}
//------------------------------------------------------------------------------
@ -42,7 +46,7 @@ pub fn prop_sub_zero<A: Group>(x: A) -> bool {
x - zero::<A>() == x
}
pub fn prop_zero_subo<A: Group>(x: A) -> bool {
pub fn prop_zero_sub<A: Group>(x: A) -> bool {
zero::<A>() - x == xneg(x)
}