diff --git a/Cargo.toml b/Cargo.toml index f69ce34..8cf3771 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 371822e..caf39e9 100644 --- a/README.md +++ b/README.md @@ -39,24 +39,25 @@ There are three main types: - `BigInt` 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 diff --git a/src/bn254/bigint.rs b/src/bn254/bigint.rs index 0b1f5c6..b02bc3f 100644 --- a/src/bn254/bigint.rs +++ b/src/bn254/bigint.rs @@ -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([u32; N]); pub type BigInt256 = BigInt<8>; @@ -50,6 +50,11 @@ impl BigInt { //------------------------------------------------------------------------------ // standard numeric traits +impl Neg for BigInt { + type Output = Self; + fn neg(self) -> Self { BigInt::neg(self) } +} + impl Add for BigInt { type Output = Self; fn add(self, other: Self) -> Self { BigInt::add(self,other) } @@ -274,6 +279,11 @@ impl BigInt { out } + pub fn neg(big: BigInt) -> BigInt { + BigInt::sub( BigInt::zero() , big ) + } + + //------------------------------------ // multiplication diff --git a/src/bn254/field.rs b/src/bn254/field.rs index e1f4249..a38a834 100644 --- a/src/bn254/field.rs +++ b/src/bn254/field.rs @@ -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); //------------------------------------------------------------------------------ diff --git a/src/bn254/test/bigint.rs b/src/bn254/test/bigint.rs index d10ef71..f61b66d 100644 --- a/src/bn254/test/bigint.rs +++ b/src/bn254/test/bigint.rs @@ -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(bs: [u8; 4*N]) -> bool { } //------------------------------------------------------------------------------ +// quickcheck property-based testing + +impl Group for BigInt {} + +// ...as you apparently cannot have an impl for an array... +#[derive(Debug, Copy, Clone)] +struct ByteArray([u8; K]); + +impl Arbitrary for ByteArray { + fn arbitrary(g: &mut Gen) -> ByteArray { + let mut bs: [u8; K] = [0; K]; + for i in 0..K { + bs[i] = u8::arbitrary(g); + } + ByteArray(bs) + } +} + +impl Arbitrary for BigInt { + fn arbitrary(g: &mut Gen) -> BigInt { + 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) } + +//------------------------------------------------------------------------------ + diff --git a/src/bn254/test/properties.rs b/src/bn254/test/properties.rs index f6c067d..fe4dc76 100644 --- a/src/bn254/test/properties.rs +++ b/src/bn254/test/properties.rs @@ -9,9 +9,13 @@ use std::ops::{Neg,Add,Sub,Mul,Div}; //------------------------------------------------------------------------------ -pub trait Group = Copy + Clone + Default + From + Eq + Neg + Add + Sub; -pub trait Ring = Group + Mul; -pub trait Field = Ring + Div; +// pub trait Group = Copy + Clone + Default + From + Eq + Neg + Add + Sub; +// pub trait Ring = Group + Mul; +// pub trait Field = Ring + Div; + +pub trait Group : Copy + Clone + Default + From + Eq + Neg + Add + Sub {} +pub trait Ring : Group + Mul {} +pub trait Field : Ring + Div {} //------------------------------------------------------------------------------ @@ -42,7 +46,7 @@ pub fn prop_sub_zero(x: A) -> bool { x - zero::() == x } -pub fn prop_zero_subo(x: A) -> bool { +pub fn prop_zero_sub(x: A) -> bool { zero::() - x == xneg(x) }