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): EdDSA sig verification #1313

Merged
merged 10 commits into from
May 18, 2023
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([
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
// 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([
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
// 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
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
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)
shuklaayush marked this conversation as resolved.
Show resolved Hide resolved
}

// 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) {}