diff --git a/crates/nargo_cli/tests/test_data/eddsa/Nargo.toml b/crates/nargo_cli/tests/test_data/eddsa/Nargo.toml new file mode 100644 index 00000000000..48db376fb19 --- /dev/null +++ b/crates/nargo_cli/tests/test_data/eddsa/Nargo.toml @@ -0,0 +1,5 @@ +[package] +authors = [""] +compiler_version = "0.3.2" + +[dependencies] \ No newline at end of file diff --git a/crates/nargo_cli/tests/test_data/eddsa/Prover.toml b/crates/nargo_cli/tests/test_data/eddsa/Prover.toml new file mode 100644 index 00000000000..53555202ca6 --- /dev/null +++ b/crates/nargo_cli/tests/test_data/eddsa/Prover.toml @@ -0,0 +1,3 @@ +_priv_key_a = 123 +_priv_key_b = 456 +msg = 789 diff --git a/crates/nargo_cli/tests/test_data/eddsa/src/main.nr b/crates/nargo_cli/tests/test_data/eddsa/src/main.nr new file mode 100644 index 00000000000..8de38011aaf --- /dev/null +++ b/crates/nargo_cli/tests/test_data/eddsa/src/main.nr @@ -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)); + } +} \ No newline at end of file diff --git a/noir_stdlib/src/compat.nr b/noir_stdlib/src/compat.nr new file mode 100644 index 00000000000..65ae22c5aba --- /dev/null +++ b/noir_stdlib/src/compat.nr @@ -0,0 +1,4 @@ +fn is_bn254() -> bool { + // bn254 truncates its curve order to 0 + 21888242871839275222246405745257275088548364400416034343698204186575808495617 == 0 +} diff --git a/noir_stdlib/src/ec.nr b/noir_stdlib/src/ec.nr index cc58b714de7..59a0731b9aa 100644 --- a/noir_stdlib/src/ec.nr +++ b/noir_stdlib/src/ec.nr @@ -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 @@ -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. @@ -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 { @@ -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; diff --git a/noir_stdlib/src/ec/consts.nr b/noir_stdlib/src/ec/consts.nr new file mode 100644 index 00000000000..f4d67e7a92c --- /dev/null +++ b/noir_stdlib/src/ec/consts.nr @@ -0,0 +1 @@ +mod te; diff --git a/noir_stdlib/src/ec/consts/te.nr b/noir_stdlib/src/ec/consts/te.nr new file mode 100644 index 00000000000..8a5bdae5127 --- /dev/null +++ b/noir_stdlib/src/ec/consts/te.nr @@ -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, + } +} diff --git a/noir_stdlib/src/ec/swcurve.nr b/noir_stdlib/src/ec/swcurve.nr index eae4f375e43..1f22de5598f 100644 --- a/noir_stdlib/src/ec/swcurve.nr +++ b/noir_stdlib/src/ec/swcurve.nr @@ -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(self, n: [Field; N], p: [Point; N]) -> Point { let mut out = Point::zero(); diff --git a/noir_stdlib/src/ec/tecurve.nr b/noir_stdlib/src/ec/tecurve.nr index 8611e4270c3..ff2c398a8a9 100644 --- a/noir_stdlib/src/ec/tecurve.nr +++ b/noir_stdlib/src/ec/tecurve.nr @@ -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(self, n: [Field; N], p: [Point; N]) -> Point { let mut out = Point::zero(); diff --git a/noir_stdlib/src/eddsa.nr b/noir_stdlib/src/eddsa.nr new file mode 100644 index 00000000000..ff9186e9f42 --- /dev/null +++ b/noir_stdlib/src/eddsa.nr @@ -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) +} diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index f0af06b97ba..f6a60a6dee7 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -3,6 +3,7 @@ mod array; mod merkle; mod schnorr; mod ecdsa_secp256k1; +mod eddsa; mod scalar_mul; mod sha256; mod sha512; @@ -10,6 +11,7 @@ mod field; mod ec; mod unsafe; mod collections; +mod compat; #[builtin(println)] fn println(_input : T) {}