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..0e5fdb8f5ec --- /dev/null +++ b/crates/nargo_cli/tests/test_data/eddsa/src/main.nr @@ -0,0 +1,69 @@ +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_a_x = 5958787406588418500595239545974275039455545059833263445973445578199987122248; + let pub_key_a_y = 6291453822075498887551694851992571215511219854100590306020486222643399599966; + // let pub_key_b = bjj.curve.mul(priv_key_b, bjj.curve.gen); + // let pub_key_b_x = 19522885864221102065733517599414632594441624981666469001809339503157461222552; + // let pub_key_b_y = 17652333933129211461240983013989988903130120700742832943186712581416334147739; + + // 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 = 161490627844314967434218537112044327531827991364156693670922373984031106453; + // let r_b = hash::pedersen([priv_key_b, msg])[0] % bjj.suborder; // modulus computed manually + // let r_b = 2365791308032549643506220161686150043336029213998559307390193647484103923348; + + // let r8_a = bjj.curve.mul(r_a, bjj.base8); + let r8_a_x = 12701037151626467441858303530846040756656970729849074442899237454651294526343; + let r8_a_y = 167159699056112791619442377873552194056131231335147805875057144366514712633; + // let r8_b = bjj.curve.mul(r_b, bjj.base8); + let r8_b_x = 1673844937671191161222451342456048355167330081367086809349241319413342408558; + let r8_b_y = 6202873177260205770199785557298715091502981852957429177245493218486919053107; + + // let perm_a: [Field; 6] = hash::poseidon::bn254::perm::x5_6([ + // 0, + // r8_a.x, + // r8_a.y, + // pub_key_a.x, + // pub_key_a.y, + // msg, + // ]); + // let h_a = perm_a[0]; + // let h_a = 21371027913064755721489810138902574499031056361794273463577587174578008711057; + + // let perm_b: [Field; 6] = hash::poseidon::bn254::perm::x5_6([ + // 0, + // r8_b.x, + // r8_b.y, + // pub_key_b.x, + // pub_key_b.y, + // msg, + // ]); + // let h_b = perm_b[0]; + // let h_b = 15015879520414293281817167314697737785772315344690416596928480331452621620489; + + // let s_a = (r_a + priv_key_a * h_a) % bjj.suborder; // modulus computed manually + let s_a = 960; + // let s_b = (r_b + priv_key_b * h_b) % bjj.suborder; // modulus computed manually + let s_b = 3562; + + // User A verifies their signature over the message + constrain 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 + constrain !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 + constrain !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..170c82d12a2 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 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..0aa90a1f5fa --- /dev/null +++ b/noir_stdlib/src/ec/consts/te.nr @@ -0,0 +1,32 @@ +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 { + constrain compat::is_bn254(); + + BabyJubjub { + // Baby Jubjub (ERC-2494) parameters in affine representation + curve: TECurve::new( + 168700, + 168696, + TEPoint::new( + 5299619240641551281634865583518297030282874472190772894086521144482721001553, + 16950150798460657717958625567821834550301663161624707787222815936182638968203, + ), + ), + // [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/eddsa.nr b/noir_stdlib/src/eddsa.nr new file mode 100644 index 00000000000..3a1ae71ea06 --- /dev/null +++ b/noir_stdlib/src/eddsa.nr @@ -0,0 +1,56 @@ +use crate::hash::poseidon; +use crate::ec::consts::te::baby_jubjub; +use crate::ec::tecurve::affine::Point as TEPoint; +use crate::field; + +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); + constrain bjj.curve.contains(pub_key); + + let signature_r8 = TEPoint::new(signature_r8_x, signature_r8_y); + constrain bjj.curve.contains(signature_r8); + + // Ensure S < Subgroup Order + constrain field::lt_bytes32(signature_s, bjj.suborder); + + // Calculate the h = H(R,A, msg) + let perm: [Field; 6] = poseidon::bn254::perm::x5_6([ + 0, + signature_r8_x, + signature_r8_y, + pub_key_x, + pub_key_y, + message, + ]); + let hash = perm[0]; + + // 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. + constrain !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/field.nr b/noir_stdlib/src/field.nr index b7773182d66..f2eb2b4ab26 100644 --- a/noir_stdlib/src/field.nr +++ b/noir_stdlib/src/field.nr @@ -53,3 +53,24 @@ fn modulus_be_bytes() -> [u8] {} #[builtin(modulus_le_bytes)] fn modulus_le_bytes() -> [u8] {} + +// Returns true if x is less than y +// TODO: consider sftey of final few bits +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 +} 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) {}