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

chore: custom hash for eddsa #4440

Merged
merged 13 commits into from
Mar 8, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ Verifier for EdDSA signatures
fn eddsa_poseidon_verify(public_key_x : Field, public_key_y : Field, signature_s: Field, signature_r8_x: Field, signature_r8_y: Field, message: Field) -> bool
```

It is also possible to specify the hash algorithm used for the signature by using the `eddsa_verify_with_hasher` function with a parameter implementing the Hasher trait. For instance, if you want to use Poseidon2 instead, you can do the following:
```rust
use dep::std::hash::poseidon2::Poseidon2Hasher;

let mut hasher = Poseidon2Hasher::default();
eddsa_verify_with_hasher(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg, &mut hasher);
```

<BlackBoxInfo />

## eddsa::eddsa_to_pub
Expand Down
31 changes: 30 additions & 1 deletion noir_stdlib/src/eddsa.nr
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::hash::poseidon;
use crate::ec::consts::te::baby_jubjub;
use crate::ec::tecurve::affine::Point as TEPoint;
use crate::hash::{Hash, Hasher, BuildHasher, BuildHasherDefault};
use crate::hash::poseidon::PoseidonHasher;

// Returns true if signature is valid
pub fn eddsa_poseidon_verify(
Expand All @@ -11,6 +13,28 @@ pub fn eddsa_poseidon_verify(
signature_r8_y: Field,
message: Field
) -> bool {
let mut hasher = PoseidonHasher::default();
eddsa_verify_with_hasher(
pub_key_x,
pub_key_y,
signature_s,
signature_r8_x,
signature_r8_y,
message,
&mut hasher
)
}

pub fn eddsa_verify_with_hasher<H>(
guipublic marked this conversation as resolved.
Show resolved Hide resolved
pub_key_x: Field,
pub_key_y: Field,
signature_s: Field,
signature_r8_x: Field,
signature_r8_y: Field,
message: Field,
hasher: &mut H
) -> bool
where H: Hasher {
guipublic marked this conversation as resolved.
Show resolved Hide resolved
// Verifies by testing:
// S * B8 = R8 + H(R8, A, m) * A8
let bjj = baby_jubjub();
Expand All @@ -23,7 +47,12 @@ pub fn eddsa_poseidon_verify(
// Ensure S < Subgroup Order
assert(signature_s.lt(bjj.suborder));
// Calculate the h = H(R, A, msg)
let hash: Field = poseidon::bn254::hash_5([signature_r8_x, signature_r8_y, pub_key_x, pub_key_y, message]);
signature_r8_x.hash(hasher);
signature_r8_y.hash(hasher);
pub_key_x.hash(hasher);
pub_key_y.hash(hasher);
message.hash(hasher);
let hash: Field = (*hasher).finish();
// Calculate second part of the right side: right2 = h*8*A
// Multiply by 8 by doubling 3 times. This also ensures that the result is in the subgroup.
let pub_key_mul_2 = bjj.curve.add(pub_key, pub_key);
Expand Down
50 changes: 42 additions & 8 deletions noir_stdlib/src/hash/mimc.nr
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use crate::hash::Hasher;
use crate::default::Default;

// mimc-p/p implementation
// constants are (publicly generated) random numbers, for instance using keccak as a ROM.
// You must use constants generated for the native field
Expand All @@ -16,13 +19,8 @@ fn mimc<N>(x: Field, k: Field, constants: [Field; N], exp: Field) -> Field {
}

global MIMC_BN254_ROUNDS = 91;
//mimc implementation with hardcoded parameters for BN254 curve.
#[field(bn254)]
pub fn mimc_bn254<N>(array: [Field; N]) -> Field {
//mimc parameters
let exponent = 7;
//generated from seed "mimc" using keccak256
let constants: [Field; MIMC_BN254_ROUNDS] = [
//generated from seed "mimc" using keccak256
global MIMC_BN254_CONSTANTS: [Field; MIMC_BN254_ROUNDS] = [
0,
20888961410941983456478427210666206549300505294776164667214940546594746570981,
15265126113435022738560151911929040668591755459209400716467504685752745317193,
Expand Down Expand Up @@ -116,10 +114,46 @@ pub fn mimc_bn254<N>(array: [Field; N]) -> Field {
13602139229813231349386885113156901793661719180900395818909719758150455500533
];

//mimc implementation with hardcoded parameters for BN254 curve.
#[field(bn254)]
pub fn mimc_bn254<N>(array: [Field; N]) -> Field {
let exponent = 7;
let mut r = 0;
for elem in array {
let h = mimc(elem, r, constants, exponent);
let h = mimc(elem, r, MIMC_BN254_CONSTANTS, exponent);
r = r + elem + h;
}
r
}

struct MimcHasher{
_state: [Field],
_len: u64,
}

impl Hasher for MimcHasher {
#[field(bn254)]
fn finish(self) -> Field {
let exponent = 7;
let mut r = 0;
for i in 0..self._len {
let h = mimc(self._state[i], r, MIMC_BN254_CONSTANTS, exponent);
r = r + self._state[i] + h;
}
r
}

fn write(&mut self, input: [Field]){
self._state = self._state.append(input);
self._len += input.len();
}
}

impl Default for MimcHasher{
fn default() -> Self{
MimcHasher{
_state: [],
_len: 0,
}
}
}
76 changes: 76 additions & 0 deletions noir_stdlib/src/hash/poseidon.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod bn254; // Instantiations of Poseidon for prime field of the same order as BN254
use crate::field::modulus_num_bits;
use crate::hash::Hasher;
use crate::default::Default;

struct PoseidonConfig<M,N> {
t: Field, // Width, i.e. state size
Expand Down Expand Up @@ -100,3 +102,77 @@ fn apply_matrix<N, M>(a: [Field; M], x: [Field; N]) -> [Field; N] {

y
}

struct PoseidonHasher{
_state: [Field],
_len: u64,
}

impl Hasher for PoseidonHasher {
#[field(bn254)]
fn finish(self) -> Field {
let mut result = 0;
assert(self._len < 16);
if self._len == 1 {
result = bn254::hash_1([self._state[0]]);
}
if self._len == 2 {
result = bn254::hash_2([self._state[0],self._state[1]]);
}
if self._len == 3 {
result = bn254::hash_3([self._state[0],self._state[1],self._state[2]]);
}
if self._len == 4 {
result = bn254::hash_4([self._state[0],self._state[1],self._state[2],self._state[3]]);
}
if self._len == 5 {
result = bn254::hash_5([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4]]);
}
if self._len == 6 {
result = bn254::hash_6([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5]]);
}
if self._len == 7 {
result = bn254::hash_7([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6]]);
}
if self._len == 8 {
result = bn254::hash_8([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7]]);
}
if self._len == 9 {
result = bn254::hash_9([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8]]);
}
if self._len == 10 {
result = bn254::hash_10([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8], self._state[9]]);
}
if self._len == 11 {
result = bn254::hash_11([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8], self._state[9], self._state[10]]);
}
if self._len == 12 {
result = bn254::hash_12([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8], self._state[9], self._state[10], self._state[11]]);
}
if self._len == 13 {
result = bn254::hash_13([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8], self._state[9], self._state[10], self._state[11], self._state[12]]);
}
if self._len == 14 {
result = bn254::hash_14([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8], self._state[9], self._state[10], self._state[11], self._state[12], self._state[13]]);
}
if self._len == 15 {
result = bn254::hash_15([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8], self._state[9], self._state[10], self._state[11], self._state[12], self._state[13], self._state[14]]);
}

result
}

fn write(&mut self, input: [Field]){
self._state = self._state.append(input);
self._len += input.len();
}
}

impl Default for PoseidonHasher{
fn default() -> Self{
PoseidonHasher{
_state: [],
_len: 0,
}
}
}
39 changes: 36 additions & 3 deletions noir_stdlib/src/hash/poseidon2.nr
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use crate::hash::Hasher;
use crate::default::Default;

global RATE = 3;

struct Poseidon2 {
Expand All @@ -9,7 +12,7 @@ struct Poseidon2 {

impl Poseidon2 {

pub fn hash<N>(input: [Field; N], message_size: u32) -> Field {
pub fn hash<N>(input: [Field; N], message_size: u64) -> Field {
if message_size == N {
Poseidon2::hash_internal(input, N, false)
} else {
Expand Down Expand Up @@ -92,12 +95,12 @@ impl Poseidon2 {
result
}

fn hash_internal<N>(input: [Field; N], in_len: u32, is_variable_length: bool) -> Field {
fn hash_internal<N>(input: [Field; N], in_len: u64, is_variable_length: bool) -> Field {
let two_pow_64 = 18446744073709551616;
let iv : Field = (in_len as Field) * two_pow_64;
let mut sponge = Poseidon2::new(iv);
for i in 0..input.len() {
if i as u32 < in_len {
if i < in_len {
sponge.absorb(input[i]);
}
}
Expand All @@ -111,3 +114,33 @@ impl Poseidon2 {
sponge.squeeze()
}
}

struct Poseidon2Hasher{
_state: [Field],
_len: u64,
}

impl Hasher for Poseidon2Hasher {
fn finish(self) -> Field {
let iv : Field = (self._state.len() as Field)*18446744073709551616; // iv = (self._state.len() << 64)
let mut sponge = Poseidon2::new(iv);
for i in 0..self._len {
sponge.absorb(self._state[i]);
}
sponge.squeeze()
}

fn write(&mut self, input: [Field]){
self._state = self._state.append(input);
self._len += input.len();
}
}

impl Default for Poseidon2Hasher{
fn default() -> Self{
Poseidon2Hasher{
_state: [],
_len: 0,
}
}
}
10 changes: 9 additions & 1 deletion test_programs/execution_success/eddsa/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use dep::std::compat;
use dep::std::ec::consts::te::baby_jubjub;
use dep::std::ec::tecurve::affine::Point as TEPoint;
use dep::std::hash;
use dep::std::eddsa::{eddsa_to_pub, eddsa_poseidon_verify};
use dep::std::eddsa::{eddsa_to_pub, eddsa_poseidon_verify, eddsa_verify_with_hasher};
use dep::std::hash::poseidon2::Poseidon2Hasher;
use dep::std::hash::pedersen::PedersenHasher;

fn main(msg: pub Field, _priv_key_a: Field, _priv_key_b: Field) {
// Skip this test for non-bn254 backends
Expand Down Expand Up @@ -48,5 +50,11 @@ fn main(msg: pub Field, _priv_key_a: Field, _priv_key_b: Field) {
assert(!eddsa_poseidon_verify(pub_key_a.x, pub_key_a.y, s_b, r8_b.x, r8_b.y, msg));
// User A's signature over the message can't be used with another message
assert(!eddsa_poseidon_verify(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg + 1));
// Using a different hash should fail
let mut hasher = Poseidon2Hasher::default();
assert(!eddsa_verify_with_hasher(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg, &mut hasher));
// Using a different hash should fail
let mut hasher = PedersenHasher::default();
assert(!eddsa_verify_with_hasher(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg, &mut hasher));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ fn main(x1: [Field; 2], y1: pub Field, x2: [Field; 4], y2: pub Field, x3: [Field
let hash2 = poseidon::bn254::hash_4(x2);
assert(hash2 == y2);

let hash3 = poseidon2::Poseidon2::hash(x3, x3.len() as u32);
let hash3 = poseidon2::Poseidon2::hash(x3, x3.len());
assert(hash3 == y3);
}
// docs:end:poseidon
Loading