mirror of
https://github.com/logos-storage/rust-poseidon-bn254-pure.git
synced 2026-02-10 03:03:10 +00:00
implement circom's Poseidon permutation and compression for t=2,3,4,5
This commit is contained in:
parent
f43a8d55a2
commit
49e10423d0
79
README.md
79
README.md
@ -5,22 +5,91 @@ Self-contained (no external dependencies), pure Rust implementation of Poseidon
|
||||
and Poseidon2 hash functions over the BN254 curve's scalar field, using 32 bit
|
||||
limbs internally.
|
||||
|
||||
It's intended to be used on 32-bit platforms, eg. 32-bit RISC-V (rv32im)
|
||||
(though porting to 64 bits shouldn't be a big effort).
|
||||
It's primarily intended to be used on 32-bit platforms, eg. 32-bit RISC-V (`rv32im`)
|
||||
(though porting to 64 bits shouldn't be a big effort; TODO).
|
||||
|
||||
The algebra implementation is based on [`zikkurat-algebra`](https://github.com/faulhornlabs/zikkurat-algebra/)
|
||||
and [`staging-agda`](https://github.com/faulhornlabs/staging-agda/).
|
||||
|
||||
### Compatibility
|
||||
|
||||
The Poseidon implementation is compatible with [`circomlib`](https://github.com/iden3/circomlib/).
|
||||
|
||||
The Poseidon2 implementation is compatible with [`zkfriendlyhashzoo`](https://extgit.isec.tugraz.at/krypto/zkfriendlyhashzoo).
|
||||
It _used to be_ compatible with the [HorizenLabs implementation](https://github.com/HorizenLabs/poseidon2),
|
||||
until they changed all their constants in [this commit](https://github.com/HorizenLabs/poseidon2/commit/bb476b9ca38198cf5092487283c8b8c5d4317c4e).
|
||||
We don't think it's worth the pain to follow this change.
|
||||
|
||||
### Status
|
||||
|
||||
Currently, only Poseidon2 with `t=3` is implemented.
|
||||
Currently, only the following instances are implemented:
|
||||
|
||||
- Poseidon permutation with `t=2,3,4,5` over BN254's scalar field
|
||||
- Poseidon2 permutation with `t=3` over BN254's scalar field
|
||||
|
||||
I feel that larger states are unneccesary in practice. As a concrete example,
|
||||
[PSE's RLN circuit](https://github.com/Rate-Limiting-Nullifier/circom-rln) uses `t=2,3,4`.
|
||||
|
||||
The proper way to handle larger input is to implement the sponge construction.
|
||||
|
||||
### Usage
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
fn permute( [Felt; t] ) -> [Felt; t]
|
||||
|
||||
From this one can build all kind 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`:
|
||||
|
||||
fn compress( [Felt; t-1] ) -> Felt
|
||||
|
||||
This takes `t-1` field elements and returns one (which is interpreted as a hash).
|
||||
|
||||
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
|
||||
that the whole construction would be totally insecure!
|
||||
|
||||
### Speed
|
||||
|
||||
Some approximate benchmark numbers below.
|
||||
|
||||
#### 32-bit RISC-V
|
||||
|
||||
On RV32IM (the primary target as of now), we have approximately the following cycle counts:
|
||||
|
||||
Poseidon2:
|
||||
|
||||
- 350k cycles for a single `t=3` permutation
|
||||
|
||||
#### Modern CPUs
|
||||
|
||||
On modern 64-bit CPU-s, the 64-bit version is preferred (TODO: implement it).
|
||||
|
||||
32 bit version, running on an M2 macbook pro:
|
||||
|
||||
- 155 msec for 10k `t=3` permutations
|
||||
|
||||
### TODO
|
||||
|
||||
- [ ] optimize squaring to use less multiplications (?)
|
||||
- [ ] benchmark RISC-V cycles
|
||||
- [ ] add more Poseidon2 state widths (not just `t=3`)
|
||||
- [ ] implement `circomlib`-compatible Poseidon
|
||||
- [ ] add a test-suite; in particular, more complete testing of the field operations
|
||||
- [x] implement `circomlib`-compatible Poseidon
|
||||
- [ ] add a proper test-suite; in particular, more complete testing of the field operations
|
||||
- [ ] add a 64 bit version
|
||||
- [ ] further optimizations
|
||||
- [ ] implement the sponge construction
|
||||
|
||||
|
||||
@ -202,9 +202,21 @@ fn main() {
|
||||
println!("in dec = {}", Mont::to_decimal_string(&MONT1));
|
||||
*/
|
||||
|
||||
let in1: Felt = Felt::from_u32(1);
|
||||
let out1 = compress_1(in1);
|
||||
println!("compress(1) = {}", Felt::to_decimal_string(&out1) );
|
||||
|
||||
let in2: [Felt; 2] = [ Felt::from_u32(1) , Felt::from_u32(2) ];
|
||||
let out2 = compress_felt_T3(in2);
|
||||
let out2 = compress_2(in2);
|
||||
println!("compress(2) = {}", Felt::to_decimal_string(&out2) );
|
||||
|
||||
let in3: [Felt; 3] = [ Felt::from_u32(1) , Felt::from_u32(2) , Felt::from_u32(3) ];
|
||||
let out3 = compress_3(in3);
|
||||
println!("compress(3) = {}", Felt::to_decimal_string(&out3) );
|
||||
|
||||
let in4: [Felt; 4] = [ Felt::from_u32(1) , Felt::from_u32(2) , Felt::from_u32(3) , Felt::from_u32(4) ];
|
||||
let out4 = compress_4(in4);
|
||||
println!("compress(4) = {}", Felt::to_decimal_string(&out4) );
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -92,7 +92,7 @@ fn external_round<const T: usize>(rcs: &[Mont], input: [Mont; T], mtx: [Mont; T*
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// T = 3
|
||||
// TODO: can we somehow unify the different T cases????
|
||||
|
||||
/*
|
||||
// debugging
|
||||
@ -104,16 +104,20 @@ fn printRound(text: &str, round: usize, state: &[Mont]) {
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn permute_mont_T3(input: [Mont; 3]) -> [Mont; 3] {
|
||||
const T: usize = 3;
|
||||
//--------------------------------------
|
||||
// T = 2
|
||||
|
||||
pub fn permute_mont_T2(input: [Mont; 2]) -> [Mont; 2] {
|
||||
const T: usize = 2;
|
||||
|
||||
const TT: usize = 2*T-1;
|
||||
const NP: usize = INTERNAL_ROUND_COUNT[T-2];
|
||||
const C: [Mont; 81] = t3::CONST_C;
|
||||
const M: [Mont; 9] = t3::CONST_M;
|
||||
const P: [Mont; 9] = t3::CONST_P;
|
||||
const S: [Mont; 285] = t3::CONST_S;
|
||||
let mut state: [Mont; 3] = input;
|
||||
// printRound("input", 0, &state);
|
||||
const C: [Mont; 72] = t2::CONST_C;
|
||||
const M: [Mont; 4] = t2::CONST_M;
|
||||
const P: [Mont; 4] = t2::CONST_P;
|
||||
const S: [Mont; 168] = t2::CONST_S;
|
||||
|
||||
let mut state: [Mont; T] = input;
|
||||
for j in 0..T {
|
||||
state[j] = Mont::add( &state[j] , &C[j] );
|
||||
}
|
||||
@ -137,7 +141,126 @@ pub fn permute_mont_T3(input: [Mont; 3]) -> [Mont; 3] {
|
||||
state
|
||||
}
|
||||
|
||||
pub fn compress_felt_T3(input: [Felt;2]) -> Felt {
|
||||
//--------------------------------------
|
||||
|
||||
pub fn permute_mont_T3(input: [Mont; 3]) -> [Mont; 3] {
|
||||
const T: usize = 3;
|
||||
|
||||
const TT: usize = 2*T-1;
|
||||
const NP: usize = INTERNAL_ROUND_COUNT[T-2];
|
||||
const C: [Mont; 81] = t3::CONST_C;
|
||||
const M: [Mont; 9] = t3::CONST_M;
|
||||
const P: [Mont; 9] = t3::CONST_P;
|
||||
const S: [Mont; 285] = t3::CONST_S;
|
||||
|
||||
let mut state: [Mont; T] = input;
|
||||
for j in 0..T {
|
||||
state[j] = Mont::add( &state[j] , &C[j] );
|
||||
}
|
||||
for i in 0..4 {
|
||||
let rcs: &[Mont] = &C[ ((i+1)*T) .. ((i+2)*T) ];
|
||||
let mat = if i<3 { M } else { P };
|
||||
state = external_round::<T>( rcs , state , mat );
|
||||
// printRound("initial round", i, &state);
|
||||
}
|
||||
for i in 0..NP {
|
||||
let rc: Mont = C[ i + 5*T ];
|
||||
let scoeffs: &[Mont] = &S[ (i*TT) .. ((i+1)*TT) ];
|
||||
state = internal_round::<T>( rc , scoeffs , state );
|
||||
// printRound("internal round", i, &state);
|
||||
}
|
||||
for i in 4..8 {
|
||||
let rcs: &[Mont] = if i<7 { &C[ (NP + (i+1)*T) .. (NP + (i+2)*T) ] } else { &[Mont::zero(); T] };
|
||||
state = external_round::<T>( rcs , state , M );
|
||||
// printRound("final round", i, &state);
|
||||
}
|
||||
state
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
|
||||
pub fn permute_mont_T4(input: [Mont; 4]) -> [Mont; 4] {
|
||||
const T: usize = 4;
|
||||
|
||||
const TT: usize = 2*T-1;
|
||||
const NP: usize = INTERNAL_ROUND_COUNT[T-2];
|
||||
const C: [Mont; 88] = t4::CONST_C;
|
||||
const M: [Mont; 16] = t4::CONST_M;
|
||||
const P: [Mont; 16] = t4::CONST_P;
|
||||
const S: [Mont; 392] = t4::CONST_S;
|
||||
|
||||
let mut state: [Mont; T] = input;
|
||||
for j in 0..T {
|
||||
state[j] = Mont::add( &state[j] , &C[j] );
|
||||
}
|
||||
for i in 0..4 {
|
||||
let rcs: &[Mont] = &C[ ((i+1)*T) .. ((i+2)*T) ];
|
||||
let mat = if i<3 { M } else { P };
|
||||
state = external_round::<T>( rcs , state , mat );
|
||||
// printRound("initial round", i, &state);
|
||||
}
|
||||
for i in 0..NP {
|
||||
let rc: Mont = C[ i + 5*T ];
|
||||
let scoeffs: &[Mont] = &S[ (i*TT) .. ((i+1)*TT) ];
|
||||
state = internal_round::<T>( rc , scoeffs , state );
|
||||
// printRound("internal round", i, &state);
|
||||
}
|
||||
for i in 4..8 {
|
||||
let rcs: &[Mont] = if i<7 { &C[ (NP + (i+1)*T) .. (NP + (i+2)*T) ] } else { &[Mont::zero(); T] };
|
||||
state = external_round::<T>( rcs , state , M );
|
||||
// printRound("final round", i, &state);
|
||||
}
|
||||
state
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
|
||||
pub fn permute_mont_T5(input: [Mont; 5]) -> [Mont; 5] {
|
||||
const T: usize = 5;
|
||||
|
||||
const TT: usize = 2*T-1;
|
||||
const NP: usize = INTERNAL_ROUND_COUNT[T-2];
|
||||
const C: [Mont; 100] = t5::CONST_C;
|
||||
const M: [Mont; 25] = t5::CONST_M;
|
||||
const P: [Mont; 25] = t5::CONST_P;
|
||||
const S: [Mont; 540] = t5::CONST_S;
|
||||
|
||||
let mut state: [Mont; T] = input;
|
||||
for j in 0..T {
|
||||
state[j] = Mont::add( &state[j] , &C[j] );
|
||||
}
|
||||
for i in 0..4 {
|
||||
let rcs: &[Mont] = &C[ ((i+1)*T) .. ((i+2)*T) ];
|
||||
let mat = if i<3 { M } else { P };
|
||||
state = external_round::<T>( rcs , state , mat );
|
||||
// printRound("initial round", i, &state);
|
||||
}
|
||||
for i in 0..NP {
|
||||
let rc: Mont = C[ i + 5*T ];
|
||||
let scoeffs: &[Mont] = &S[ (i*TT) .. ((i+1)*TT) ];
|
||||
state = internal_round::<T>( rc , scoeffs , state );
|
||||
// printRound("internal round", i, &state);
|
||||
}
|
||||
for i in 4..8 {
|
||||
let rcs: &[Mont] = if i<7 { &C[ (NP + (i+1)*T) .. (NP + (i+2)*T) ] } else { &[Mont::zero(); T] };
|
||||
state = external_round::<T>( rcs , state , M );
|
||||
// printRound("final round", i, &state);
|
||||
}
|
||||
state
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
pub fn compress_1(input: Felt) -> Felt {
|
||||
let mut state: [Mont; 2] =
|
||||
[ Mont::zero()
|
||||
, Felt::to_mont(&input)
|
||||
];
|
||||
state = permute_mont_T2(state);
|
||||
Felt::from_mont(&state[0])
|
||||
}
|
||||
|
||||
pub fn compress_2(input: [Felt;2]) -> Felt {
|
||||
let mut state: [Mont; 3] =
|
||||
[ Mont::zero()
|
||||
, Felt::to_mont(&input[0])
|
||||
@ -147,7 +270,27 @@ pub fn compress_felt_T3(input: [Felt;2]) -> Felt {
|
||||
Felt::from_mont(&state[0])
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
pub fn compress_3(input: [Felt;3]) -> Felt {
|
||||
let mut state: [Mont; 4] =
|
||||
[ Mont::zero()
|
||||
, Felt::to_mont(&input[0])
|
||||
, Felt::to_mont(&input[1])
|
||||
, Felt::to_mont(&input[2])
|
||||
];
|
||||
state = permute_mont_T4(state);
|
||||
Felt::from_mont(&state[0])
|
||||
}
|
||||
|
||||
pub fn compress_4(input: [Felt;4]) -> Felt {
|
||||
let mut state: [Mont; 5] =
|
||||
[ Mont::zero()
|
||||
, Felt::to_mont(&input[0])
|
||||
, Felt::to_mont(&input[1])
|
||||
, Felt::to_mont(&input[2])
|
||||
, Felt::to_mont(&input[3])
|
||||
];
|
||||
state = permute_mont_T5(state);
|
||||
Felt::from_mont(&state[0])
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user