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(stdlib): Implement Poseidon hash #768

Merged
merged 32 commits into from
Apr 3, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1ff23c4
Implement Poseidon hash
ax0 Feb 8, 2023
98c29ad
Minor changes. Addition of BN254-specific permutation and sponge func…
ax0 Feb 8, 2023
1521aa9
Stylistic changes
ax0 Feb 8, 2023
286c739
Minor changes. Exclude sponge test.
ax0 Feb 8, 2023
458cc12
Merge remote-tracking branch 'refs/remotes/origin/master'
ax0 Feb 9, 2023
80fffb2
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Feb 10, 2023
bac6694
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Feb 13, 2023
8c5881a
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Feb 13, 2023
14e75d8
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Feb 15, 2023
e284216
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Feb 16, 2023
ce48c4a
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Feb 17, 2023
be6f0c7
Replace stdlib functions with methods
ax0 Feb 17, 2023
6f57d3c
Fix typo
ax0 Feb 19, 2023
39f1dd8
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Feb 19, 2023
9f1bd24
Fix typo
ax0 Feb 19, 2023
decbb48
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Feb 21, 2023
15de4ca
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Feb 21, 2023
c1162f4
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Feb 24, 2023
1ba0366
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Feb 26, 2023
13dcd63
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Feb 28, 2023
a7ca0c1
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Mar 1, 2023
1b22936
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Mar 6, 2023
d6a9def
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Mar 7, 2023
08f09fd
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Mar 8, 2023
f9aa50e
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Mar 8, 2023
fe0b59f
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Mar 14, 2023
b3eb85b
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Mar 21, 2023
648b059
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Mar 23, 2023
1677590
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Mar 23, 2023
c0bbc58
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Mar 27, 2023
0ddb014
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Mar 29, 2023
2771af8
Merge branch 'master' of https://github.com/noir-lang/noir
ax0 Apr 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/nargo/tests/test_data/config.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# List of tests to be excluded (i.e not run), as their directory name in test_data
# "1_mul", "2_div","3_add","4_sub","5_over", "6","6_array", "7_function","7","8_integration", "9_conditional", "10_slices", "assign_ex", "bool_not", "bool_or", "pedersen_check", "pred_eq", "schnorr", "sha256", "tuples",
# "1_mul", "2_div","3_add","4_sub","5_over", "6","6_array", "7_function","7","8_integration", "9_conditional", "10_slices", "assign_ex", "bool_not", "bool_or", "pedersen_check", "poseidonperm_x5_254", "poseidonsponge_x5_254", "pred_eq", "schnorr", "sha256", "tuples",
# "array_len", "array_neq", "bit_and", "cast_bool", "comptime_array_access", "generics", "global_comptime", "main_bool_arg", "main_return", "merkle_insert", "modules", "modules_more", "scalar_mul", "simple_shield", "struct", "submodules",
# Exclude "sha2_byte" due to relatively long computation time and "sha2_blocks" due to very long computation time.
exclude = ["comptime_fail", "sha2_blocks", "sha2_byte"]
# Exclude "poseidonsponge_x5_254" and "sha2_byte" due to relatively long computation time and "sha2_blocks" due to very long computation time.
exclude = ["comptime_fail", "poseidonsponge_x5_254", "sha2_blocks", "sha2_byte"]


# List of tests (as their directory name in test_data) expecting to fail: if the test pass, we report an error.
Expand Down
6 changes: 6 additions & 0 deletions crates/nargo/tests/test_data/poseidonperm_x5_254/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "Poseidon 254-bit permutation test on 3 elements with alpha = 5"
authors = [""]
compiler_version = "0.1"

[dependencies]
4 changes: 4 additions & 0 deletions crates/nargo/tests/test_data/poseidonperm_x5_254/Prover.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
x1 = [0,1,2]
y1 = "0x115cc0f5e7d690413df64c6b9662e9cf2a3617f2743245519e19607a4417189a"
x2 = [0,1,2,3,4]
y2 = "0x299c867db6c1fdd79dcefa40e4510b9837e60ebb1ce0663dbaa525df65250465"
10 changes: 10 additions & 0 deletions crates/nargo/tests/test_data/poseidonperm_x5_254/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use dep::std::hash::poseidon;

fn main(x1: [Field; 3], y1: pub Field, x2: [Field; 5], y2: pub Field)
{
let perm1 = poseidon::bn254::perm::x5_3(x1);
constrain perm1[0] == y1;

let perm2 = poseidon::bn254::perm::x5_5(x2);
constrain perm2[0] == y2;
}
6 changes: 6 additions & 0 deletions crates/nargo/tests/test_data/poseidonsponge_x5_254/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "Variable-length Poseidon-128 sponge test on 7 elements with alpha = 5"
authors = [""]
compiler_version = "0.1"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x = [1,2,3,4,5,6,7]
14 changes: 14 additions & 0 deletions crates/nargo/tests/test_data/poseidonsponge_x5_254/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use dep::std::hash::poseidon;

fn main(x: [Field; 7])
{
// Test optimised sponge
let result = poseidon::bn254::sponge(x);

constrain result == 0x080ae1669d62f0197190573d4a325bfb8d8fc201ce3127cbac0c47a7ac81ac48;

// Test unoptimised sponge
let result2 = poseidon::absorb(poseidon::bn254::consts::x5_5_config(), [0;5], 4, 1, x)[1];

constrain result2 == result;
}
guipublic marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions noir_stdlib/src/hash.nr
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod poseidon;

#[foreign(sha256)]
fn sha256(_input : [u8]) -> [u8; 32] {}

Expand Down
117 changes: 117 additions & 0 deletions noir_stdlib/src/hash/poseidon.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
mod bn254; // Instantiations of Poseidon for prime field of the same order as BN254

use crate::array;
use crate::pow_32;
use crate::field::modulus_num_bits;

struct PoseidonConfig<M,N> {
t: comptime Field, // Width, i.e. state size
rf: comptime u8, // Number of full rounds; should be even
rp: comptime u8, // Number of partial rounds
alpha: comptime Field, // S-box power; depends on the underlying field
ark: [Field; M], // Additive round keys
mds: [Field; N] // MDS Matrix in row-major order
}

fn config<M,N>(
t: comptime Field,
rf: comptime u8,
rp: comptime u8,
alpha: comptime Field,
ark: [Field; M],
mds: [Field; N])
-> PoseidonConfig<M,N> {
// Input checks
constrain t as u8 * (rf + rp) == array::len(ark) as u8;
constrain t * t == array::len(mds);
constrain alpha != 0;

PoseidonConfig {t, rf, rp, alpha, ark, mds}
}

// General Poseidon permutation on elements of type Field
fn permute<M,N,O>(
pos_conf: PoseidonConfig<M, N>,
mut state: [Field; O])
-> [Field; O] {
let PoseidonConfig {t, rf, rp, alpha, ark, mds} = pos_conf;

constrain t == array::len(state);

let mut count = 0;

// for r in 0..rf + rp
for r in 0..(array::len(ark)/array::len(state)) {
for i in 0..array::len(state) {
state[i] = state[i] + ark[count + i];
} // Shift by round constants

state[0] = pow_32(state[0], alpha);

// Check whether we are in a full round
if (r as u8 < rf/2) | (r as u8 >= rf/2 + rp) {
for i in 1..array::len(state) {
state[i] = pow_32(state[i], alpha);
}
}

state = apply_matrix(mds, state); // Apply MDS matrix
count = count + t;
}

state
}

// Absorption. Fully absorbs input message.
fn absorb<M,N,O,P>(
pos_conf: PoseidonConfig<M, N>,
mut state: [Field; O], // Initial state; usually [0; N]
rate: comptime Field, // Rate
capacity: comptime Field, // Capacity; usually 1
msg: [Field; P]) // Arbitrary length message
-> [Field; O] {
constrain pos_conf.t == rate + capacity;

let mut i = 0;

for k in 0..array::len(msg) {
// Add current block to state
state[capacity + i] += msg[k];
i = i+1;

// Enough to absorb
if i == rate {
state = permute(pos_conf, state);
i = 0;
}
}

// If we have one more block to permute
if i != 0 {
state = permute(pos_conf, state);
}

state
}


// Check security of sponge instantiation
fn check_security(rate: Field, width: Field, security: Field) -> bool {
let n = modulus_num_bits();

((n-1)*(width-rate)/2) as u8 > security as u8
}

// A*x where A is an n x n matrix in row-major order and x an n-vector
fn apply_matrix<N>(a: [Field], x: [Field; N]) -> [Field; N] {
let mut y = x;

for i in 0..array::len(x) {
y[i] = 0;
for j in 0..array::len(x) {
y[i] = y[i] + a[array::len(x)*i + j]* x[j];
}
}

y
}
105 changes: 105 additions & 0 deletions noir_stdlib/src/hash/poseidon/bn254.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Instantiations of Poseidon constants, permutations and sponge for prime field of the same order as BN254
mod perm;
mod consts;

ax0 marked this conversation as resolved.
Show resolved Hide resolved
use crate::hash::poseidon::PoseidonConfig;
use crate::array;
use crate::pow_32;
use crate::hash::poseidon::apply_matrix;

// Optimised permutation for this particular field; uses hardcoded rf and rp values,
// which should agree with those in pos_conf.
fn permute<M,N,O>(
pos_conf: PoseidonConfig<M, N>,
mut state: [Field; O])
-> [Field; O] {
let PoseidonConfig {t, rf: config_rf, rp: config_rp, alpha, ark, mds} = pos_conf;
let rf = 8;
let rp = [56, 57, 56, 60, 60, 63, 64, 63, 60, 66, 60, 65, 70, 60, 64, 68][array::len(state) - 2];

constrain t == array::len(state);
constrain rf == config_rf as Field;
constrain rp == config_rp as Field;

let mut count = 0;

// First half of full rounds
for _r in 0..rf/2 {
for i in 0..array::len(state) {
state[i] = state[i] + ark[count + i];
} // Shift by round constants

for i in 0..array::len(state) {
state[i] = pow_32(state[i], alpha);
}

state = apply_matrix(mds, state); // Apply MDS matrix
count = count + t;
}

// Partial rounds
for _r in 0..rp {
for i in 0..array::len(state) {
state[i] = state[i] + ark[count + i];
} // Shift by round constants

state[0] = pow_32(state[0], alpha);

state = apply_matrix(mds, state); // Apply MDS matrix
count = count + t;
}

// Second half of full rounds
for _r in 0..rf/2 {
for i in 0..array::len(state) {
state[i] = state[i] + ark[count + i];
} // Shift by round constants

for i in 0..array::len(state) {
state[i] = pow_32(state[i], alpha);
}

state = apply_matrix(mds, state); // Apply MDS matrix
count = count + t;
}

state
}

// Corresponding absorption.
fn absorb<M,N,O,P>(
pos_conf: PoseidonConfig<M, N>,
mut state: [Field; O], // Initial state; usually [0; N]
rate: comptime Field, // Rate
capacity: comptime Field, // Capacity; usually 1
msg: [Field; P] // Arbitrary length message
) -> [Field; O] {

constrain pos_conf.t == rate + capacity;

let mut i = 0;

for k in 0..array::len(msg) {
// Add current block to state
state[capacity + i] += msg[k];
i = i+1;

// Enough to absorb
if i == rate {
state = permute(pos_conf, state);
i = 0;
}
}

// If we have one more block to permute
if i != 0 {
state = permute(pos_conf, state);
}

state
}

// Variable-length Poseidon-128 sponge as suggested in second bullet point of §3 of https://eprint.iacr.org/2019/458.pdf
fn sponge<N>(msg: [Field; N]) -> Field {
absorb(consts::x5_5_config(), [0;5], 4, 1, msg)[1]
}
194 changes: 194 additions & 0 deletions noir_stdlib/src/hash/poseidon/bn254/consts.nr

Large diffs are not rendered by default.

Loading