Skip to content

Commit

Permalink
feat(stdlib): EdDSA sig verification (#1313)
Browse files Browse the repository at this point in the history
* feat(stdlib): eddsa sig verification

* fix(stdlib): use correct poseidon hash in EdDSA
- lift baby jubjub into const
- EdDSA integration test
- lt_bytes32

* fix: use builtin to_bits for mul

* fix: simplify hash function call

* fix: move lt_bytes32 to std::eddsa

* fix: reuse bit_mul code for ec mul

* fix: remove field dependent global from std::ec

* chore: whitespace

* Update noir_stdlib/src/ec/swcurve.nr

* Update noir_stdlib/src/ec/tecurve.nr

---------

Co-authored-by: Joss <joss@aztecprotocol.com>
Co-authored-by: kevaundray <kevtheappdev@gmail.com>
  • Loading branch information
3 people authored May 18, 2023
1 parent ba15d6d commit 04a15e0
Show file tree
Hide file tree
Showing 11 changed files with 200 additions and 23 deletions.
5 changes: 5 additions & 0 deletions crates/nargo_cli/tests/test_data/eddsa/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
authors = [""]
compiler_version = "0.3.2"

[dependencies]
3 changes: 3 additions & 0 deletions crates/nargo_cli/tests/test_data/eddsa/Prover.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
_priv_key_a = 123
_priv_key_b = 456
msg = 789
55 changes: 55 additions & 0 deletions crates/nargo_cli/tests/test_data/eddsa/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use dep::std::compat;
use dep::std::ec::consts::te::baby_jubjub;
use dep::std::hash;
use dep::std::eddsa::eddsa_poseidon_verify;
use dep::std;

fn main(msg: pub Field, _priv_key_a: Field, _priv_key_b: Field) {
// Skip this test for non-bn254 backends
if compat::is_bn254() {
let bjj = baby_jubjub();

let pub_key_a = bjj.curve.mul(_priv_key_a, bjj.curve.gen);
// let pub_key_b = bjj.curve.mul(_priv_key_b, bjj.curve.gen);

// Manually computed as fields can't use modulo. Importantantly the commitment is within
// the subgroup order. Note that choice of hash is flexible for this step.
// let r_a = hash::pedersen([_priv_key_a, msg])[0] % bjj.suborder; // modulus computed manually
let r_a = 1414770703199880747815475415092878800081323795074043628810774576767372531818;
// let r_b = hash::pedersen([_priv_key_b, msg])[0] % bjj.suborder; // modulus computed manually
let r_b = 571799555715456644614141527517766533395606396271089506978608487688924659618;

let r8_a = bjj.curve.mul(r_a, bjj.base8);
let r8_b = bjj.curve.mul(r_b, bjj.base8);

// let h_a: [Field; 6] = hash::poseidon::bn254::hash_5([
// r8_a.x,
// r8_a.y,
// pub_key_a.x,
// pub_key_a.y,
// msg,
// ]);

// let h_b: [Field; 6] = hash::poseidon::bn254::hash_5([
// r8_b.x,
// r8_b.y,
// pub_key_b.x,
// pub_key_b.y,
// msg,
// ]);

// let s_a = (r_a + _priv_key_a * h_a) % bjj.suborder; // modulus computed manually
let s_a = 30333430637424319196043722294837632681219980330991241982145549329256671548;
// let s_b = (r_b + _priv_key_b * h_b) % bjj.suborder; // modulus computed manually
let s_b = 1646085314320208098241070054368798527940102577261034947654839408482102287019;

// User A verifies their signature over the message
assert(eddsa_poseidon_verify(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg));

// User B's signature over the message can't be used with user A's pub key
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));
}
}
4 changes: 4 additions & 0 deletions noir_stdlib/src/compat.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fn is_bn254() -> bool {
// bn254 truncates its curve order to 0
21888242871839275222246405745257275088548364400416034343698204186575808495617 == 0
}
22 changes: 4 additions & 18 deletions noir_stdlib/src/ec.nr
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
mod tecurve; // Twisted Edwards curves
mod swcurve; // Elliptic curves in Short Weierstraß form
mod montcurve; // Montgomery curves
mod consts; // Commonly used curve presets
//
// Note that Twisted Edwards and Montgomery curves are (birationally) equivalent, so that
// they may be freely converted between one another, whereas Short Weierstraß curves are
Expand Down Expand Up @@ -120,9 +121,6 @@ mod montcurve; // Montgomery curves
// **TODO: Support arrays of structs to make this work.


// TODO: Replace with built-in backend-dependent constant.
global N_BITS = 254; // Maximum number of bits in field element

// Field-dependent constant ZETA = a non-square element of Field
// Required for Elligator 2 map
// TODO: Replace with built-in constant.
Expand All @@ -149,20 +147,6 @@ global C5 = 19103219067921713944291392827692070036145651957329286315305642004821
// out
//}

// Converts Field element to little-endian bit array of length N_BITS
// TODO: Fix built-in to_le_bits(., N_BITS), which yields a 128-periodic bit array
fn to_bits(x: Field) -> [u1; N_BITS] {
let mut x = x;
let mut out = [0; N_BITS];
for i in 0..N_BITS {
if x != 0 {
out[i] = x as u1;
x = (x - out[i] as Field)/2;
}
}
out
}

// TODO: Make this built-in.
fn safe_inverse(x: Field) -> Field {
if x == 0 {
Expand All @@ -182,8 +166,10 @@ fn is_square(x: Field) -> bool {
// Power function of two Field arguments of arbitrary size.
// Adapted from std::field::pow_32.
fn pow(x: Field, y: Field) -> Field { // As in tests with minor modifications
let N_BITS = crate::field::modulus_num_bits();

let mut r = 1 as Field;
let b = to_bits(y);
let b = y.to_le_bits(N_BITS as u32);

for i in 0..N_BITS {
r *= r;
Expand Down
1 change: 1 addition & 0 deletions noir_stdlib/src/ec/consts.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod te;
33 changes: 33 additions & 0 deletions noir_stdlib/src/ec/consts/te.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use crate::compat;
use crate::ec::tecurve::affine::Point as TEPoint;
use crate::ec::tecurve::affine::Curve as TECurve;

struct BabyJubjub {
curve: TECurve,
base8: TEPoint,
suborder: Field,
}

fn baby_jubjub() -> BabyJubjub {
assert(compat::is_bn254());

BabyJubjub {
// Baby Jubjub (ERC-2494) parameters in affine representation
curve: TECurve::new(
168700,
168696,
// G
TEPoint::new(
995203441582195749578291179787384436505546430278305826713579947235728471134,
5472060717959818805561601436314318772137091100104008585924551046643952123905,
),
),
// [8]G precalculated
base8: TEPoint::new(
5299619240641551281634865583518297030282874472190772894086521144482721001553,
16950150798460657717958625567821834550301663161624707787222815936182638968203,
),
// The size of the group formed from multiplying the base field by 8.
suborder: 2736030358979909402780800718157159386076813972158567259200215660948447373041,
}
}
11 changes: 9 additions & 2 deletions noir_stdlib/src/ec/swcurve.nr
Original file line number Diff line number Diff line change
Expand Up @@ -344,11 +344,18 @@ mod curvegroup {

// Scalar multiplication (p + ... + p n times)
fn mul(self, n: Field, p: Point) -> Point {
let n_as_bits = crate::ec::to_bits(n); // N_BITS-bit representation
let N_BITS = crate::field::modulus_num_bits();

// TODO: temporary workaround until issue 1354 is solved
let mut n_as_bits: [u1; 254] = [0; 254];
let tmp = n.to_le_bits(N_BITS as u32);
for i in 0..254 {
n_as_bits[i] = tmp[i];
}

self.bit_mul(n_as_bits, p)
}

// Multi-scalar multiplication (n[0]*p[0] + ... + n[N]*p[N], where * denotes scalar multiplication)
fn msm<N>(self, n: [Field; N], p: [Point; N]) -> Point {
let mut out = Point::zero();
Expand Down
13 changes: 10 additions & 3 deletions noir_stdlib/src/ec/tecurve.nr
Original file line number Diff line number Diff line change
Expand Up @@ -364,17 +364,24 @@ mod curvegroup {
self.add(out, out),
if(bits[n - i - 1] == 0) {Point::zero()} else {p});
}

out
}

// Scalar multiplication (p + ... + p n times)
fn mul(self, n: Field, p: Point) -> Point {
let n_as_bits = crate::ec::to_bits(n); // N_BITS-bit representation
let N_BITS = crate::field::modulus_num_bits();

// TODO: temporary workaround until issue 1354 is solved
let mut n_as_bits: [u1; 254] = [0; 254];
let tmp = n.to_le_bits(N_BITS as u32);
for i in 0..254 {
n_as_bits[i] = tmp[i];
}

self.bit_mul(n_as_bits, p)
}

// Multi-scalar multiplication (n[0]*p[0] + ... + n[N]*p[N], where * denotes scalar multiplication)
fn msm<N>(self, n: [Field; N], p: [Point; N]) -> Point {
let mut out = Point::zero();
Expand Down
74 changes: 74 additions & 0 deletions noir_stdlib/src/eddsa.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use crate::hash::poseidon;
use crate::ec::consts::te::baby_jubjub;
use crate::ec::tecurve::affine::Point as TEPoint;

// Returns true if x is less than y
fn lt_bytes32(x: Field, y: Field) -> bool {
let x_bytes = x.to_le_bytes(32);
let y_bytes = y.to_le_bytes(32);
let mut x_is_lt = false;
let mut done = false;
for i in 0..32 {
if (!done) {
let x_byte = x_bytes[31 - i];
let y_byte = y_bytes[31 - i];
let bytes_match = x_byte == y_byte;
if !bytes_match {
x_is_lt = x_byte < y_byte;
done = true;
}
}
}
x_is_lt
}

// Returns true if signature is valid
fn eddsa_poseidon_verify(
pub_key_x: Field,
pub_key_y: Field,
signature_s: Field,
signature_r8_x: Field,
signature_r8_y: Field,
message: Field,
) -> bool {
// Verifies by testing:
// S * B8 = R8 + H(R8, A, m) * A8

let bjj = baby_jubjub();

let pub_key = TEPoint::new(pub_key_x, pub_key_y);
assert(bjj.curve.contains(pub_key));

let signature_r8 = TEPoint::new(signature_r8_x, signature_r8_y);
assert(bjj.curve.contains(signature_r8));

// Ensure S < Subgroup Order
assert(lt_bytes32(signature_s, 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,
]);

// 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);
let pub_key_mul_4 = bjj.curve.add(pub_key_mul_2, pub_key_mul_2);
let pub_key_mul_8 = bjj.curve.add(pub_key_mul_4, pub_key_mul_4);

// We check that A8 is not zero.
assert(!pub_key_mul_8.is_zero());

// Compute the right side: R8 + h * A8
let right = bjj.curve.add(signature_r8, bjj.curve.mul(hash, pub_key_mul_8));

// Calculate left side of equation left = S * B8
let left = bjj.curve.mul(signature_s, bjj.base8);

left.eq(right)
}
2 changes: 2 additions & 0 deletions noir_stdlib/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ mod array;
mod merkle;
mod schnorr;
mod ecdsa_secp256k1;
mod eddsa;
mod scalar_mul;
mod sha256;
mod sha512;
mod field;
mod ec;
mod unsafe;
mod collections;
mod compat;

#[builtin(println)]
fn println<T>(_input : T) {}

0 comments on commit 04a15e0

Please sign in to comment.