Skip to content

Commit

Permalink
chore: custom hash for eddsa (#4440)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

Resolves #3642 

## Summary\*

Eddsa verification takes now a hasher so that it can be used with
anything having the Hasher trait.
I added this trait to the stdlib implementations of mimc, poseidon and
poseidon2.

## Additional Context



## Documentation\*

Check one:
- [ ] No documentation needed.
- [X] Documentation included in this PR.
- [ ] **[Exceptional Case]** Documentation to be submitted in a separate
PR.

# PR Checklist\*

- [X] I have tested the changes locally.
- [X] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.

---------

Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com>
  • Loading branch information
guipublic and TomAFrench authored Mar 8, 2024
1 parent b60279b commit 9cee413
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 14 deletions.
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>(
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 {
// 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

0 comments on commit 9cee413

Please sign in to comment.