From e2604e2d1d047bd4911887698ea628217d45e83e Mon Sep 17 00:00:00 2001 From: Tom French Date: Thu, 21 Nov 2024 21:28:49 +0000 Subject: [PATCH] feat: add initial implementation Release-as: 0.1.0 --- .github/workflows/benchmark.yml | 4 +- .github/workflows/test.yml | 4 +- Nargo.toml | 2 +- README.md | 22 +- src/consts/mod.nr | 1 + src/consts/te.nr | 33 +++ src/lib.nr | 416 ++++++++++++++++++++++++++++++- src/montcurve.nr | 387 +++++++++++++++++++++++++++++ src/swcurve.nr | 391 +++++++++++++++++++++++++++++ src/tecurve.nr | 419 ++++++++++++++++++++++++++++++++ 10 files changed, 1645 insertions(+), 34 deletions(-) create mode 100644 src/consts/mod.nr create mode 100644 src/consts/te.nr create mode 100644 src/montcurve.nr create mode 100644 src/swcurve.nr create mode 100644 src/tecurve.nr diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 850ac38..b3f405a 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -17,12 +17,12 @@ jobs: - name: Install Nargo uses: noir-lang/noirup@v0.1.3 with: - toolchain: 0.34.0 + toolchain: 0.36.0 - name: Install bb run: | npm install -g bbup - bbup -nv 0.34.0 + bbup -nv 0.36.0 - name: Build Noir benchmark programs run: nargo export diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1dec562..bd564ec 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - toolchain: [nightly, 0.34.0] + toolchain: [nightly, 0.36.0] steps: - name: Checkout sources uses: actions/checkout@v4 @@ -38,7 +38,7 @@ jobs: - name: Install Nargo uses: noir-lang/noirup@v0.1.3 with: - toolchain: 0.34.0 + toolchain: 0.36.0 - name: Run formatter run: nargo fmt --check diff --git a/Nargo.toml b/Nargo.toml index ca37ec6..c54a6a5 100644 --- a/Nargo.toml +++ b/Nargo.toml @@ -2,6 +2,6 @@ name = "noir_library" type = "lib" authors = [""] -compiler_version = ">=0.34.0" +compiler_version = ">=0.36.0" [dependencies] diff --git a/README.md b/README.md index 1d25ec0..2d71b6b 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,6 @@ -# noir-library-starter +# ec -This repository is a template used by the noir-lang org when creating internally maintained libraries. - -This provides out of the box: - -- A simple CI setup to test and format the library -- A canary flagging up compilation failures on nightly releases. -- A [release-please](https://github.com/googleapis/release-please) setup to ease creating releases for the library. - -Feel free to use this template as a starting point to create your own Noir libraries. - ---- - -# LIBRARY_NAME - -Add a brief description of the library +A library which exports the ec module which formerly existed within the Noir stdlib. ## Benchmarks @@ -27,11 +13,11 @@ In your _Nargo.toml_ file, add the version of this library you would like to ins ``` [dependencies] -LIBRARY = { tag = "v0.1.0", git = "https://github.com/noir-lang/LIBRARY_NAME" } +ec = { tag = "v0.1.0", git = "https://github.com/noir-lang/ec" } ``` ## `library` ### Usage -`PLACEHOLDER` \ No newline at end of file +`PLACEHOLDER` diff --git a/src/consts/mod.nr b/src/consts/mod.nr new file mode 100644 index 0000000..73c594c --- /dev/null +++ b/src/consts/mod.nr @@ -0,0 +1 @@ +pub mod te; diff --git a/src/consts/te.nr b/src/consts/te.nr new file mode 100644 index 0000000..32a86fa --- /dev/null +++ b/src/consts/te.nr @@ -0,0 +1,33 @@ +use std::ec::tecurve::affine::Curve as TECurve; +use std::ec::tecurve::affine::Point as TEPoint; + +pub struct BabyJubjub { + pub curve: TECurve, + pub base8: TEPoint, + pub suborder: Field, +} + +#[field(bn254)] +// Uncommenting this results in deprecated warnings in the stdlib +// #[deprecated] +pub fn baby_jubjub() -> BabyJubjub { + 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/src/lib.nr b/src/lib.nr index 80b968c..ef91650 100644 --- a/src/lib.nr +++ b/src/lib.nr @@ -1,15 +1,409 @@ -/// This doesn't really do anything by ensures that there is a test for CI to run. -#[test] -fn smoke_test() { - assert(true); +// Elliptic curve implementation +// Overview +// ======== +// The following three elliptic curve representations are admissible: +pub mod tecurve; // Twisted Edwards curves +pub mod swcurve; // Elliptic curves in Short Weierstrass form +pub mod montcurve; // Montgomery curves +pub 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 Weierstrass curves are +// more general. Diagramatically: +// +// tecurve == montcurve `subset` swcurve +// +// Each module is further divided into two submodules, 'affine' and 'curvegroup', depending +// on the preferred coordinate representation. Affine coordinates are none other than the usual +// two-dimensional Cartesian coordinates used in the definitions of these curves, whereas +// 'CurveGroup' coordinates (terminology borrowed from Arkworks, whose conventions we try +// to follow) are special coordinate systems with respect to which the group operations may be +// implemented more efficiently, usually by means of an appropriate choice of projective coordinates. +// +// In each of these submodules, there is a Point struct and a Curve struct, the former +// representing a point in the coordinate system and the latter a curve configuration. +// +// Points +// ====== +// Points may be instantiated using the associated function `new`, which takes coordinates +// as its arguments. For instance, +// +// `let p = swcurve::Point::new(1,1);` +// +// The additive identity may be constructed by a call to the associated function `zero` of no +// arguments: +// +// `let zero = swcurve::Point::zero();` +// +// Points may be tested for equality by calling the method `eq`: +// +// `let pred = p.eq(zero);` +// +// There is also the method `is_zero` to explicitly check whether a point is the additive identity: +// +// `constrain pred == p.is_zero();` +// +// Points may be negated by calling the `negate` method and converted to CurveGroup (or affine) +// coordinates by calling the `into_group` (resp. `into_affine`) method on them. Finally, +// Points may be freely mapped between their respective Twisted Edwards and Montgomery +// representations by calling the `into_montcurve` or `into_tecurve` methods. For mappings +// between Twisted Edwards/Montgomery curves and Short Weierstrass curves, see the Curve section +// below, as the underlying mappings are those of curves rather than ambient spaces. +// As a rule, Points in affine (or CurveGroup) coordinates are mapped to Points in affine +// (resp. CurveGroup) coordinates. +// +// Curves +// ====== +// A curve configuration (Curve) is completely determined by the Field coefficients of its defining +// equation (a and b in the case of swcurve, a and d in the case of tecurve, and j and k in +// the case of montcurve) together with a generator (`gen`) in the corresponding coordinate system. +// For example, the Baby Jubjub curve configuration as defined in ERC-2494 may be instantiated as a Twisted +// Edwards curve in affine coordinates as follows: +// +// `let bjj_affine = tecurve::Curve::new(168700, 168696, tecurve::Point::new(995203441582195749578291179787384436505546430278305826713579947235728471134,5472060717959818805561601436314318772137091100104008585924551046643952123905));` +// +// The `contains` method may be used to check whether a Point lies on a given curve: +// +// `constrain bjj_affine.contains(tecurve::Point::zero());` +// +// The elliptic curve group's addition operation is exposed as the `add` method, e.g. +// +// `let p = bjj_affine.add(bjj_affine.gen, bjj_affine.gen);` +// +// subtraction as the `subtract` method, e.g. +// +// `constrain tecurve::Point::zero().eq(bjj_affine.subtract(bjj_affine.gen, bjj_affine.gen));` +// +// scalar multiplication as the `mul` method, where the scalar is assumed to be a Field* element, e.g. +// +// `constrain tecurve::Point::zero().eq(bjj_affine.mul(2, tecurve::Point::zero());` +// +// There is a scalar multiplication method (`bit_mul`) provided where the scalar input is expected to be +// an array of bits (little-endian convention), as well as a multi-scalar multiplication method** (`msm`) +// which takes an array of Field elements and an array of elliptic curve points as arguments, both assumed +// to be of the same length. +// +// Curve configurations may be converted between different coordinate representations by calling the `into_group` +// and `into_affine` methods on them, e.g. +// +// `let bjj_curvegroup = bjj_affine.into_group();` +// +// Curve configurations may also be converted between different curve representations by calling the `into_swcurve`, +// `into_montcurve` and `into_tecurve` methods subject to the relation between the curve representations mentioned +// above. Note that it is possible to map Points from a Twisted Edwards/Montgomery curve to the corresponding +// Short Weierstrass representation and back, and the methods to do so are exposed as `map_into_swcurve` and +// `map_from_swcurve`, which each take one argument, the point to be mapped. +// +// Curve maps +// ========== +// There are a few different ways of mapping Field elements to elliptic curves. Here we provide the simplified +// Shallue-van de Woestijne-Ulas and Elligator 2 methods, the former being applicable to all curve types +// provided above subject to the constraint that the coefficients of the corresponding Short Weierstrass curve satisfies +// a*b != 0 and the latter being applicable to Montgomery and Twisted Edwards curves subject to the constraint that +// the coefficients of the corresponding Montgomery curve satisfy j*k != 0 and (j^2 - 4)/k^2 is non-square. +// +// The simplified Shallue-van de Woestijne-Ulas method is exposed as the method `swu_map` on the Curve configuration and +// depends on two parameters, a Field element z != -1 for which g(x) - z is irreducible over Field and g(b/(z*a)) is +// square, where g(x) = x^3 + a*x + b is the right-hand side of the defining equation of the corresponding Short +// Weierstrass curve, and a Field element u to be mapped onto the curve. For example, in the case of bjj_affine above, +// it may be determined using the scripts provided at that z = 5. +// +// The Elligator 2 method is exposed as the method `elligator2_map` on the Curve configurations of Montgomery and +// Twisted Edwards curves. Like the simplified SWU method above, it depends on a certain non-square element of Field, +// but this element need not satisfy any further conditions, so it is included as the (Field-dependent) constant +//`ZETA` below. Thus, the `elligator2_map` method depends only on one parameter, the Field element to be mapped onto +// the curve. +// +// For details on all of the above in the context of hashing to elliptic curves, see . +// +// +// *TODO: Replace Field with Bigint. +// **TODO: Support arrays of structs to make this work. +// Field-dependent constant ZETA = a non-square element of Field +// Required for Elligator 2 map +// TODO: Replace with built-in constant. +global ZETA = 5; +// Field-dependent constants for Tonelli-Shanks algorithm (see sqrt function below) +// TODO: Possibly make this built-in. +global C1 = 28; +global C3 = 40770029410420498293352137776570907027550720424234931066070132305055; +global C5 = 19103219067921713944291392827692070036145651957329286315305642004821462161904; +// Higher-order version of scalar multiplication +// TODO: Make this work so that the submodules' bit_mul may be defined in terms of it. +//fn bit_mul(add: fn(T,T) -> T, e: T, bits: [u1; N], p: T) -> T { +// let mut out = e; +// let n = bits.len(); +// +// for i in 0..n { +// out = add( +// add(out, out), +// if(bits[n - i - 1] == 0) {e} else {p}); +// } +// +// out +//} +// TODO: Make this built-in. +pub fn safe_inverse(x: Field) -> Field { + if x == 0 { + 0 + } else { + 1 / x + } +} +// Boolean indicating whether Field element is a square, i.e. whether there exists a y in Field s.t. x = y*y. +pub fn is_square(x: Field) -> bool { + let v = pow(x, 0 - 1 / 2); + + v * (v - 1) == 0 } +// Power function of two Field arguments of arbitrary size. +// Adapted from std::field::pow_32. +pub fn pow(x: Field, y: Field) -> Field { + let mut r = 1 as Field; + let b: [u1; 254] = y.to_le_bits(); -// This is an example benchmark. -// Changes to the number of constraints generated by this function will show in PRs. -#[export] -fn bench_test(mut x: Field) -> Field { - for _ in 0..100 { - x *= x; + for i in 0..254 { + r *= r; + r *= (b[254 - 1 - i] as Field) * x + (1 - b[254 - 1 - i] as Field); } - x + + r +} +// Tonelli-Shanks algorithm for computing the square root of a Field element. +// Requires C1 = max{c: 2^c divides (p-1)}, where p is the order of Field +// as well as C3 = (C2 - 1)/2, where C2 = (p-1)/(2^c1), +// and C5 = ZETA^C2, where ZETA is a non-square element of Field. +// These are pre-computed above as globals. +pub fn sqrt(x: Field) -> Field { + let mut z = pow(x, C3); + let mut t = z * z * x; + z *= x; + let mut b = t; + let mut c = C5; + + for i in 0..(C1 - 1) { + for _j in 1..(C1 - i - 1) { + b *= b; + } + + z *= if b == 1 { 1 } else { c }; + + c *= c; + + t *= if b == 1 { 1 } else { c }; + + b = t; + } + + z +} + +mod tests { + use crate::montcurve::affine::Point as MGaffine; + use crate::montcurve::curvegroup::Point as MG; + use crate::swcurve::affine::Point as SWGaffine; + use crate::swcurve::curvegroup::Point as SWG; + use crate::tecurve::affine::Curve as AffineCurve; + use crate::tecurve::affine::Point as Gaffine; + use crate::tecurve::curvegroup::Point as G; + + #[test] + fn smoke_test() { + // Tests may be checked against https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/tree/main/poc + + // Define Baby Jubjub (ERC-2494) parameters in affine representation + let bjj_affine = AffineCurve::new( + 168700, + 168696, + Gaffine::new( + 995203441582195749578291179787384436505546430278305826713579947235728471134, + 5472060717959818805561601436314318772137091100104008585924551046643952123905, + ), + ); + // Test addition + let p1_affine = Gaffine::new( + 17777552123799933955779906779655732241715742912184938656739573121738514868268, + 2626589144620713026669568689430873010625803728049924121243784502389097019475, + ); + let p2_affine = Gaffine::new( + 16540640123574156134436876038791482806971768689494387082833631921987005038935, + 20819045374670962167435360035096875258406992893633759881276124905556507972311, + ); + + let p3_affine = bjj_affine.add(p1_affine, p2_affine); + assert(p3_affine.eq(Gaffine::new( + 7916061937171219682591368294088513039687205273691143098332585753343424131937, + 14035240266687799601661095864649209771790948434046947201833777492504781204499, + ))); + // Test scalar multiplication + let p4_affine = bjj_affine.mul(2, p1_affine); + assert(p4_affine.eq(Gaffine::new( + 6890855772600357754907169075114257697580319025794532037257385534741338397365, + 4338620300185947561074059802482547481416142213883829469920100239455078257889, + ))); + assert(p4_affine.eq(bjj_affine.bit_mul([0, 1], p1_affine))); + // Test subtraction + let p5_affine = bjj_affine.subtract(p3_affine, p3_affine); + assert(p5_affine.eq(Gaffine::zero())); + // Check that these points are on the curve + assert( + bjj_affine.contains(bjj_affine.gen) + & bjj_affine.contains(p1_affine) + & bjj_affine.contains(p2_affine) + & bjj_affine.contains(p3_affine) + & bjj_affine.contains(p4_affine) + & bjj_affine.contains(p5_affine), + ); + // Test CurveGroup equivalents + let bjj = bjj_affine.into_group(); // Baby Jubjub + let p1 = p1_affine.into_group(); + let p2 = p2_affine.into_group(); + let p3 = p3_affine.into_group(); + let p4 = p4_affine.into_group(); + let p5 = p5_affine.into_group(); + // Test addition + assert(p3.eq(bjj.add(p1, p2))); + // Test scalar multiplication + assert(p4.eq(bjj.mul(2, p1))); + assert(p4.eq(bjj.bit_mul([0, 1], p1))); + // Test subtraction + assert(G::zero().eq(bjj.subtract(p3, p3))); + assert(p5.eq(G::zero())); + // Check that these points are on the curve + assert( + bjj.contains(bjj.gen) + & bjj.contains(p1) + & bjj.contains(p2) + & bjj.contains(p3) + & bjj.contains(p4) + & bjj.contains(p5), + ); + // Test SWCurve equivalents of the above + // First the affine representation + let bjj_swcurve_affine = bjj_affine.into_swcurve(); + + let p1_swcurve_affine = bjj_affine.map_into_swcurve(p1_affine); + let p2_swcurve_affine = bjj_affine.map_into_swcurve(p2_affine); + let p3_swcurve_affine = bjj_affine.map_into_swcurve(p3_affine); + let p4_swcurve_affine = bjj_affine.map_into_swcurve(p4_affine); + let p5_swcurve_affine = bjj_affine.map_into_swcurve(p5_affine); + // Addition + assert(p3_swcurve_affine.eq(bjj_swcurve_affine.add(p1_swcurve_affine, p2_swcurve_affine))); + // Doubling + assert(p4_swcurve_affine.eq(bjj_swcurve_affine.mul(2, p1_swcurve_affine))); + assert(p4_swcurve_affine.eq(bjj_swcurve_affine.bit_mul([0, 1], p1_swcurve_affine))); + // Subtraction + assert(SWGaffine::zero().eq(bjj_swcurve_affine.subtract( + p3_swcurve_affine, + p3_swcurve_affine, + ))); + assert(p5_swcurve_affine.eq(SWGaffine::zero())); + // Check that these points are on the curve + assert( + bjj_swcurve_affine.contains(bjj_swcurve_affine.gen) + & bjj_swcurve_affine.contains(p1_swcurve_affine) + & bjj_swcurve_affine.contains(p2_swcurve_affine) + & bjj_swcurve_affine.contains(p3_swcurve_affine) + & bjj_swcurve_affine.contains(p4_swcurve_affine) + & bjj_swcurve_affine.contains(p5_swcurve_affine), + ); + // Then the CurveGroup representation + let bjj_swcurve = bjj.into_swcurve(); + + let p1_swcurve = bjj.map_into_swcurve(p1); + let p2_swcurve = bjj.map_into_swcurve(p2); + let p3_swcurve = bjj.map_into_swcurve(p3); + let p4_swcurve = bjj.map_into_swcurve(p4); + let p5_swcurve = bjj.map_into_swcurve(p5); + // Addition + assert(p3_swcurve.eq(bjj_swcurve.add(p1_swcurve, p2_swcurve))); + // Doubling + assert(p4_swcurve.eq(bjj_swcurve.mul(2, p1_swcurve))); + assert(p4_swcurve.eq(bjj_swcurve.bit_mul([0, 1], p1_swcurve))); + // Subtraction + assert(SWG::zero().eq(bjj_swcurve.subtract(p3_swcurve, p3_swcurve))); + assert(p5_swcurve.eq(SWG::zero())); + // Check that these points are on the curve + assert( + bjj_swcurve.contains(bjj_swcurve.gen) + & bjj_swcurve.contains(p1_swcurve) + & bjj_swcurve.contains(p2_swcurve) + & bjj_swcurve.contains(p3_swcurve) + & bjj_swcurve.contains(p4_swcurve) + & bjj_swcurve.contains(p5_swcurve), + ); + // Test MontCurve conversions + // First the affine representation + let bjj_montcurve_affine = bjj_affine.into_montcurve(); + + let p1_montcurve_affine = p1_affine.into_montcurve(); + let p2_montcurve_affine = p2_affine.into_montcurve(); + let p3_montcurve_affine = p3_affine.into_montcurve(); + let p4_montcurve_affine = p4_affine.into_montcurve(); + let p5_montcurve_affine = p5_affine.into_montcurve(); + // Addition + assert(p3_montcurve_affine.eq(bjj_montcurve_affine.add( + p1_montcurve_affine, + p2_montcurve_affine, + ))); + // Doubling + assert(p4_montcurve_affine.eq(bjj_montcurve_affine.mul(2, p1_montcurve_affine))); + assert(p4_montcurve_affine.eq(bjj_montcurve_affine.bit_mul([0, 1], p1_montcurve_affine))); + // Subtraction + assert(MGaffine::zero().eq(bjj_montcurve_affine.subtract( + p3_montcurve_affine, + p3_montcurve_affine, + ))); + assert(p5_montcurve_affine.eq(MGaffine::zero())); + // Check that these points are on the curve + assert( + bjj_montcurve_affine.contains(bjj_montcurve_affine.gen) + & bjj_montcurve_affine.contains(p1_montcurve_affine) + & bjj_montcurve_affine.contains(p2_montcurve_affine) + & bjj_montcurve_affine.contains(p3_montcurve_affine) + & bjj_montcurve_affine.contains(p4_montcurve_affine) + & bjj_montcurve_affine.contains(p5_montcurve_affine), + ); + // Then the CurveGroup representation + let bjj_montcurve = bjj.into_montcurve(); + + let p1_montcurve = p1_montcurve_affine.into_group(); + let p2_montcurve = p2_montcurve_affine.into_group(); + let p3_montcurve = p3_montcurve_affine.into_group(); + let p4_montcurve = p4_montcurve_affine.into_group(); + let p5_montcurve = p5_montcurve_affine.into_group(); + // Addition + assert(p3_montcurve.eq(bjj_montcurve.add(p1_montcurve, p2_montcurve))); + // Doubling + assert(p4_montcurve.eq(bjj_montcurve.mul(2, p1_montcurve))); + assert(p4_montcurve.eq(bjj_montcurve.bit_mul([0, 1], p1_montcurve))); + // Subtraction + assert(MG::zero().eq(bjj_montcurve.subtract(p3_montcurve, p3_montcurve))); + assert(p5_montcurve.eq(MG::zero())); + // Check that these points are on the curve + assert( + bjj_montcurve.contains(bjj_montcurve.gen) + & bjj_montcurve.contains(p1_montcurve) + & bjj_montcurve.contains(p2_montcurve) + & bjj_montcurve.contains(p3_montcurve) + & bjj_montcurve.contains(p4_montcurve) + & bjj_montcurve.contains(p5_montcurve), + ); + // Elligator 2 map-to-curve + let ell2_pt_map = bjj_affine.elligator2_map(27); + + assert(ell2_pt_map.eq(MGaffine::new( + 7972459279704486422145701269802978968072470631857513331988813812334797879121, + 8142420778878030219043334189293412482212146646099536952861607542822144507872, + ) + .into_tecurve())); + // SWU map-to-curve + let swu_pt_map = bjj_affine.swu_map(5, 27); + + assert(swu_pt_map.eq(bjj_affine.map_from_swcurve(SWGaffine::new( + 2162719247815120009132293839392097468339661471129795280520343931405114293888, + 5341392251743377373758788728206293080122949448990104760111875914082289313973, + )))); + } + } diff --git a/src/montcurve.nr b/src/montcurve.nr new file mode 100644 index 0000000..d4da9cb --- /dev/null +++ b/src/montcurve.nr @@ -0,0 +1,387 @@ +pub mod affine { + // Affine representation of Montgomery curves + // Points are represented by two-dimensional Cartesian coordinates. + // All group operations are induced by those of the corresponding Twisted Edwards curve. + // See e.g. for details on the correspondences. + use crate::{ + is_square, + montcurve::curvegroup, + safe_inverse, + sqrt, + swcurve::affine::{Curve as SWCurve, Point as SWPoint}, + tecurve::affine::{Curve as TECurve, Point as TEPoint}, + ZETA, + }; + use std::cmp::Eq; + + // Curve specification + pub struct Curve { // Montgomery Curve configuration (ky^2 = x^3 + j*x^2 + x) + pub j: Field, + pub k: Field, + // Generator as point in Cartesian coordinates + pub gen: Point, + } + // Point in Cartesian coordinates + pub struct Point { + pub x: Field, + pub y: Field, + pub infty: bool, // Indicator for point at infinity + } + + impl Point { + // Point constructor + pub fn new(x: Field, y: Field) -> Self { + Self { x, y, infty: false } + } + + // Check if zero + pub fn is_zero(self) -> bool { + self.infty + } + + // Conversion to CurveGroup coordinates + pub fn into_group(self) -> curvegroup::Point { + if self.is_zero() { + curvegroup::Point::zero() + } else { + let (x, y) = (self.x, self.y); + curvegroup::Point::new(x, y, 1) + } + } + + // Additive identity + pub fn zero() -> Self { + Self { x: 0, y: 0, infty: true } + } + + // Negation + pub fn negate(self) -> Self { + let Self { x, y, infty } = self; + + Self { x, y: 0 - y, infty } + } + + // Map into equivalent Twisted Edwards curve + pub fn into_tecurve(self) -> TEPoint { + let Self { x, y, infty } = self; + + if infty | (y * (x + 1) == 0) { + TEPoint::zero() + } else { + TEPoint::new(x / y, (x - 1) / (x + 1)) + } + } + } + + impl Eq for Point { + fn eq(self, p: Self) -> bool { + (self.infty & p.infty) | (!self.infty & !p.infty & (self.x == p.x) & (self.y == p.y)) + } + } + + impl Curve { + // Curve constructor + pub fn new(j: Field, k: Field, gen: Point) -> Self { + // Check curve coefficients + assert(k != 0); + assert(j * j != 4); + + let curve = Self { j, k, gen }; + + // gen should be on the curve + assert(curve.contains(curve.gen)); + + curve + } + + // Conversion to CurveGroup coordinates + pub fn into_group(self) -> curvegroup::Curve { + curvegroup::Curve::new(self.j, self.k, self.gen.into_group()) + } + + // Membership check + pub fn contains(self, p: Point) -> bool { + let Self { j, k, gen: _gen } = self; + let Point { x, y, infty } = p; + + infty | (k * y * y == x * (x * x + j * x + 1)) + } + + // Point addition + pub fn add(self, p1: Point, p2: Point) -> Point { + self.into_tecurve().add(p1.into_tecurve(), p2.into_tecurve()).into_montcurve() + } + + // Scalar multiplication with scalar represented by a bit array (little-endian convention). + // If k is the natural number represented by `bits`, then this computes p + ... + p k times. + pub fn bit_mul(self, bits: [u1; N], p: Point) -> Point { + self.into_tecurve().bit_mul(bits, p.into_tecurve()).into_montcurve() + } + + // Scalar multiplication (p + ... + p n times) + pub fn mul(self, n: Field, p: Point) -> Point { + self.into_tecurve().mul(n, p.into_tecurve()).into_montcurve() + } + + // Multi-scalar multiplication (n[0]*p[0] + ... + n[N]*p[N], where * denotes scalar multiplication) + pub fn msm(self, n: [Field; N], p: [Point; N]) -> Point { + let mut out = Point::zero(); + + for i in 0..N { + out = self.add(out, self.mul(n[i], p[i])); + } + + out + } + + // Point subtraction + pub fn subtract(self, p1: Point, p2: Point) -> Point { + self.add(p1, p2.negate()) + } + + // Conversion to equivalent Twisted Edwards curve + pub fn into_tecurve(self) -> TECurve { + let Self { j, k, gen } = self; + TECurve::new((j + 2) / k, (j - 2) / k, gen.into_tecurve()) + } + + // Conversion to equivalent Short Weierstrass curve + pub fn into_swcurve(self) -> SWCurve { + let j = self.j; + let k = self.k; + let a0 = (3 - j * j) / (3 * k * k); + let b0 = (2 * j * j * j - 9 * j) / (27 * k * k * k); + + SWCurve::new(a0, b0, self.map_into_swcurve(self.gen)) + } + + // Point mapping into equivalent Short Weierstrass curve + pub fn map_into_swcurve(self, p: Point) -> SWPoint { + if p.is_zero() { + SWPoint::zero() + } else { + SWPoint::new((3 * p.x + self.j) / (3 * self.k), p.y / self.k) + } + } + + // Point mapping from equivalent Short Weierstrass curve + pub fn map_from_swcurve(self, p: SWPoint) -> Point { + let SWPoint { x, y, infty } = p; + let j = self.j; + let k = self.k; + + Point { x: (3 * k * x - j) / 3, y: y * k, infty } + } + + // Elligator 2 map-to-curve method; see . + pub fn elligator2_map(self, u: Field) -> Point { + let j = self.j; + let k = self.k; + let z = ZETA; // Non-square Field element required for map + // Check whether curve is admissible + assert(j != 0); + let l = (j * j - 4) / (k * k); + assert(l != 0); + assert(is_square(l) == false); + + let x1 = safe_inverse(1 + z * u * u) * (0 - (j / k)); + + let gx1 = x1 * x1 * x1 + (j / k) * x1 * x1 + x1 / (k * k); + let x2 = 0 - x1 - (j / k); + let gx2 = x2 * x2 * x2 + (j / k) * x2 * x2 + x2 / (k * k); + + let x = if is_square(gx1) { x1 } else { x2 }; + + let y = if is_square(gx1) { + let y0 = sqrt(gx1); + if y0.sgn0() == 1 { + y0 + } else { + 0 - y0 + } + } else { + let y0 = sqrt(gx2); + if y0.sgn0() == 0 { + y0 + } else { + 0 - y0 + } + }; + + Point::new(x * k, y * k) + } + + // SWU map-to-curve method (via rational map) + pub fn swu_map(self, z: Field, u: Field) -> Point { + self.map_from_swcurve(self.into_swcurve().swu_map(z, u)) + } + } +} +pub mod curvegroup { + // Affine representation of Montgomery curves + // Points are represented by three-dimensional projective (homogeneous) coordinates. + // All group operations are induced by those of the corresponding Twisted Edwards curve. + // See e.g. for details on the correspondences. + use crate::{ + montcurve::affine, + swcurve::curvegroup::{Curve as SWCurve, Point as SWPoint}, + tecurve::curvegroup::{Curve as TECurve, Point as TEPoint}, + }; + use std::cmp::Eq; + + pub struct Curve { // Montgomery Curve configuration (ky^2 z = x*(x^2 + j*x*z + z*z)) + pub j: Field, + pub k: Field, + // Generator as point in projective coordinates + pub gen: Point, + } + // Point in projective coordinates + pub struct Point { + pub x: Field, + pub y: Field, + pub z: Field, + } + + impl Point { + // Point constructor + pub fn new(x: Field, y: Field, z: Field) -> Self { + Self { x, y, z } + } + + // Check if zero + pub fn is_zero(self) -> bool { + self.z == 0 + } + + // Conversion to affine coordinates + pub fn into_affine(self) -> affine::Point { + if self.is_zero() { + affine::Point::zero() + } else { + let (x, y, z) = (self.x, self.y, self.z); + affine::Point::new(x / z, y / z) + } + } + + // Additive identity + pub fn zero() -> Self { + Self { x: 0, y: 1, z: 0 } + } + + // Negation + pub fn negate(self) -> Self { + let Self { x, y, z } = self; + + Point::new(x, 0 - y, z) + } + + // Map into equivalent Twisted Edwards curve + pub fn into_tecurve(self) -> TEPoint { + self.into_affine().into_tecurve().into_group() + } + } + + impl Eq for Point { + fn eq(self, p: Self) -> bool { + (self.z == p.z) + | (((self.x * self.z) == (p.x * p.z)) & ((self.y * self.z) == (p.y * p.z))) + } + } + + impl Curve { + // Curve constructor + pub fn new(j: Field, k: Field, gen: Point) -> Self { + // Check curve coefficients + assert(k != 0); + assert(j * j != 4); + + let curve = Self { j, k, gen }; + + // gen should be on the curve + assert(curve.contains(curve.gen)); + + curve + } + + // Conversion to affine coordinates + pub fn into_affine(self) -> affine::Curve { + affine::Curve::new(self.j, self.k, self.gen.into_affine()) + } + + // Membership check + pub fn contains(self, p: Point) -> bool { + let Self { j, k, gen: _gen } = self; + let Point { x, y, z } = p; + + k * y * y * z == x * (x * x + j * x * z + z * z) + } + + // Point addition + pub fn add(self, p1: Point, p2: Point) -> Point { + self.into_affine().add(p1.into_affine(), p2.into_affine()).into_group() + } + + // Scalar multiplication with scalar represented by a bit array (little-endian convention). + // If k is the natural number represented by `bits`, then this computes p + ... + p k times. + pub fn bit_mul(self, bits: [u1; N], p: Point) -> Point { + self.into_tecurve().bit_mul(bits, p.into_tecurve()).into_montcurve() + } + + // Scalar multiplication (p + ... + p n times) + pub fn mul(self, n: Field, p: Point) -> Point { + self.into_tecurve().mul(n, p.into_tecurve()).into_montcurve() + } + + // Multi-scalar multiplication (n[0]*p[0] + ... + n[N]*p[N], where * denotes scalar multiplication) + pub fn msm(self, n: [Field; N], p: [Point; N]) -> Point { + let mut out = Point::zero(); + + for i in 0..N { + out = self.add(out, self.mul(n[i], p[i])); + } + + out + } + + // Point subtraction + pub fn subtract(self, p1: Point, p2: Point) -> Point { + self.add(p1, p2.negate()) + } + + // Conversion to equivalent Twisted Edwards curve + pub fn into_tecurve(self) -> TECurve { + let Self { j, k, gen } = self; + TECurve::new((j + 2) / k, (j - 2) / k, gen.into_tecurve()) + } + + // Conversion to equivalent Short Weierstrass curve + pub fn into_swcurve(self) -> SWCurve { + let j = self.j; + let k = self.k; + let a0 = (3 - j * j) / (3 * k * k); + let b0 = (2 * j * j * j - 9 * j) / (27 * k * k * k); + + SWCurve::new(a0, b0, self.map_into_swcurve(self.gen)) + } + + // Point mapping into equivalent Short Weierstrass curve + pub fn map_into_swcurve(self, p: Point) -> SWPoint { + self.into_affine().map_into_swcurve(p.into_affine()).into_group() + } + + // Point mapping from equivalent Short Weierstrass curve + pub fn map_from_swcurve(self, p: SWPoint) -> Point { + self.into_affine().map_from_swcurve(p.into_affine()).into_group() + } + + // Elligator 2 map-to-curve method + pub fn elligator2_map(self, u: Field) -> Point { + self.into_affine().elligator2_map(u).into_group() + } + + // SWU map-to-curve method (via rational map) + pub fn swu_map(self, z: Field, u: Field) -> Point { + self.into_affine().swu_map(z, u).into_group() + } + } +} diff --git a/src/swcurve.nr b/src/swcurve.nr new file mode 100644 index 0000000..abe7baa --- /dev/null +++ b/src/swcurve.nr @@ -0,0 +1,391 @@ +pub mod affine { + // Affine representation of Short Weierstrass curves + // Points are represented by two-dimensional Cartesian coordinates. + // Group operations are implemented in terms of those in CurveGroup (in this case, extended Twisted Edwards) coordinates + // for reasons of efficiency, cf. . + use crate::{is_square, safe_inverse, sqrt, swcurve::curvegroup}; + use std::cmp::Eq; + + // Curve specification + pub struct Curve { // Short Weierstrass curve + // Coefficients in defining equation y^2 = x^3 + ax + b + pub a: Field, + pub b: Field, + // Generator as point in Cartesian coordinates + pub gen: Point, + } + // Point in Cartesian coordinates + pub struct Point { + pub x: Field, + pub y: Field, + pub infty: bool, // Indicator for point at infinity + } + + impl Point { + // Point constructor + pub fn new(x: Field, y: Field) -> Self { + Self { x, y, infty: false } + } + + // Check if zero + pub fn is_zero(self) -> bool { + self.eq(Point::zero()) + } + + // Conversion to CurveGroup coordinates + pub fn into_group(self) -> curvegroup::Point { + let Self { x, y, infty } = self; + + if infty { + curvegroup::Point::zero() + } else { + curvegroup::Point::new(x, y, 1) + } + } + + // Additive identity + pub fn zero() -> Self { + Self { x: 0, y: 0, infty: true } + } + + // Negation + pub fn negate(self) -> Self { + let Self { x, y, infty } = self; + Self { x, y: 0 - y, infty } + } + } + + impl Eq for Point { + fn eq(self, p: Self) -> bool { + let Self { x: x1, y: y1, infty: inf1 } = self; + let Self { x: x2, y: y2, infty: inf2 } = p; + + (inf1 & inf2) | (!inf1 & !inf2 & (x1 == x2) & (y1 == y2)) + } + } + + impl Curve { + // Curve constructor + pub fn new(a: Field, b: Field, gen: Point) -> Curve { + // Check curve coefficients + assert(4 * a * a * a + 27 * b * b != 0); + + let curve = Curve { a, b, gen }; + + // gen should be on the curve + assert(curve.contains(curve.gen)); + + curve + } + + // Conversion to CurveGroup coordinates + pub fn into_group(self) -> curvegroup::Curve { + let Curve { a, b, gen } = self; + + curvegroup::Curve { a, b, gen: gen.into_group() } + } + + // Membership check + pub fn contains(self, p: Point) -> bool { + let Point { x, y, infty } = p; + infty | (y * y == x * x * x + self.a * x + self.b) + } + + // Point addition, implemented in terms of mixed addition for reasons of efficiency + pub fn add(self, p1: Point, p2: Point) -> Point { + self.mixed_add(p1, p2.into_group()).into_affine() + } + + // Mixed point addition, i.e. first argument in affine, second in CurveGroup coordinates. + pub fn mixed_add(self, p1: Point, p2: curvegroup::Point) -> curvegroup::Point { + if p1.is_zero() { + p2 + } else if p2.is_zero() { + p1.into_group() + } else { + let Point { x: x1, y: y1, infty: _inf } = p1; + let curvegroup::Point { x: x2, y: y2, z: z2 } = p2; + let you1 = x1 * z2 * z2; + let you2 = x2; + let s1 = y1 * z2 * z2 * z2; + let s2 = y2; + + if you1 == you2 { + if s1 != s2 { + curvegroup::Point::zero() + } else { + self.into_group().double(p2) + } + } else { + let h = you2 - you1; + let r = s2 - s1; + let x3 = r * r - h * h * h - 2 * you1 * h * h; + let y3 = r * (you1 * h * h - x3) - s1 * h * h * h; + let z3 = h * z2; + + curvegroup::Point::new(x3, y3, z3) + } + } + } + + // Scalar multiplication with scalar represented by a bit array (little-endian convention). + // If k is the natural number represented by `bits`, then this computes p + ... + p k times. + pub fn bit_mul(self, bits: [u1; N], p: Point) -> Point { + self.into_group().bit_mul(bits, p.into_group()).into_affine() + } + + // Scalar multiplication (p + ... + p n times) + pub fn mul(self, n: Field, p: Point) -> Point { + self.into_group().mul(n, p.into_group()).into_affine() + } + + // Multi-scalar multiplication (n[0]*p[0] + ... + n[N]*p[N], where * denotes scalar multiplication) + pub fn msm(self, n: [Field; N], p: [Point; N]) -> Point { + let mut out = Point::zero(); + + for i in 0..N { + out = self.add(out, self.mul(n[i], p[i])); + } + + out + } + + // Point subtraction + pub fn subtract(self, p1: Point, p2: Point) -> Point { + self.add(p1, p2.negate()) + } + + // Simplified Shallue-van de Woestijne-Ulas map-to-curve method; see . + // First determine non-square z != -1 in Field s.t. g(x) - z irreducible over Field and g(b/(z*a)) is square, + // where g(x) = x^3 + a*x + b. swu_map(c,z,.) then maps a Field element to a point on curve c. + pub fn swu_map(self, z: Field, u: Field) -> Point { + // Check whether curve is admissible + assert(self.a * self.b != 0); + + let Curve { a, b, gen: _gen } = self; + + let tv1 = safe_inverse(z * z * u * u * u * u + u * u * z); + let x1 = if tv1 == 0 { + b / (z * a) + } else { + (0 - b / a) * (1 + tv1) + }; + let gx1 = x1 * x1 * x1 + a * x1 + b; + let x2 = z * u * u * x1; + let gx2 = x2 * x2 * x2 + a * x2 + b; + let (x, y) = if is_square(gx1) { + (x1, sqrt(gx1)) + } else { + (x2, sqrt(gx2)) + }; + Point::new(x, if u.sgn0() != y.sgn0() { 0 - y } else { y }) + } + } +} + +pub mod curvegroup { + // CurveGroup representation of Weierstrass curves + // Points are represented by three-dimensional Jacobian coordinates. + // See for details. + use crate::swcurve::affine; + use std::cmp::Eq; + + // Curve specification + pub struct Curve { // Short Weierstrass curve + // Coefficients in defining equation y^2 = x^3 + axz^4 + bz^6 + pub a: Field, + pub b: Field, + // Generator as point in Cartesian coordinates + pub gen: Point, + } + // Point in three-dimensional Jacobian coordinates + pub struct Point { + pub x: Field, + pub y: Field, + pub z: Field, // z = 0 corresponds to point at infinity. + } + + impl Point { + // Point constructor + pub fn new(x: Field, y: Field, z: Field) -> Self { + Self { x, y, z } + } + + // Check if zero + pub fn is_zero(self) -> bool { + self.eq(Point::zero()) + } + + // Conversion to affine coordinates + pub fn into_affine(self) -> affine::Point { + let Self { x, y, z } = self; + + if z == 0 { + affine::Point::zero() + } else { + affine::Point::new(x / (z * z), y / (z * z * z)) + } + } + + // Additive identity + pub fn zero() -> Self { + Self { x: 0, y: 0, z: 0 } + } + + // Negation + pub fn negate(self) -> Self { + let Self { x, y, z } = self; + Self { x, y: 0 - y, z } + } + } + + impl Eq for Point { + fn eq(self, p: Self) -> bool { + let Self { x: x1, y: y1, z: z1 } = self; + let Self { x: x2, y: y2, z: z2 } = p; + + ((z1 == 0) & (z2 == 0)) + | ( + (z1 != 0) + & (z2 != 0) + & (x1 * z2 * z2 == x2 * z1 * z1) + & (y1 * z2 * z2 * z2 == y2 * z1 * z1 * z1) + ) + } + } + + impl Curve { + // Curve constructor + pub fn new(a: Field, b: Field, gen: Point) -> Curve { + // Check curve coefficients + assert(4 * a * a * a + 27 * b * b != 0); + + let curve = Curve { a, b, gen }; + + // gen should be on the curve + assert(curve.contains(curve.gen)); + + curve + } + + // Conversion to affine coordinates + pub fn into_affine(self) -> affine::Curve { + let Curve { a, b, gen } = self; + + affine::Curve { a, b, gen: gen.into_affine() } + } + + // Membership check + pub fn contains(self, p: Point) -> bool { + let Point { x, y, z } = p; + if z == 0 { + true + } else { + y * y == x * x * x + self.a * x * z * z * z * z + self.b * z * z * z * z * z * z + } + } + + // Addition + pub fn add(self, p1: Point, p2: Point) -> Point { + if p1.is_zero() { + p2 + } else if p2.is_zero() { + p1 + } else { + let Point { x: x1, y: y1, z: z1 } = p1; + let Point { x: x2, y: y2, z: z2 } = p2; + let you1 = x1 * z2 * z2; + let you2 = x2 * z1 * z1; + let s1 = y1 * z2 * z2 * z2; + let s2 = y2 * z1 * z1 * z1; + + if you1 == you2 { + if s1 != s2 { + Point::zero() + } else { + self.double(p1) + } + } else { + let h = you2 - you1; + let r = s2 - s1; + let x3 = r * r - h * h * h - 2 * you1 * h * h; + let y3 = r * (you1 * h * h - x3) - s1 * h * h * h; + let z3 = h * z1 * z2; + + Point::new(x3, y3, z3) + } + } + } + + // Point doubling + pub fn double(self, p: Point) -> Point { + let Point { x, y, z } = p; + + if p.is_zero() { + p + } else if y == 0 { + Point::zero() + } else { + let s = 4 * x * y * y; + let m = 3 * x * x + self.a * z * z * z * z; + let x0 = m * m - 2 * s; + let y0 = m * (s - x0) - 8 * y * y * y * y; + let z0 = 2 * y * z; + + Point::new(x0, y0, z0) + } + } + + // Scalar multiplication with scalar represented by a bit array (little-endian convention). + // If k is the natural number represented by `bits`, then this computes p + ... + p k times. + pub fn bit_mul(self, bits: [u1; N], p: Point) -> Point { + let mut out = Point::zero(); + + for i in 0..N { + out = self.add( + self.add(out, out), + if (bits[N - i - 1] == 0) { + Point::zero() + } else { + p + }, + ); + } + + out + } + + // Scalar multiplication (p + ... + p n times) + pub fn mul(self, n: Field, p: Point) -> Point { + // TODO: temporary workaround until issue 1354 is solved + let mut n_as_bits: [u1; 254] = [0; 254]; + let tmp: [u1; 254] = n.to_le_bits(); + 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) + pub fn msm(self, n: [Field; N], p: [Point; N]) -> Point { + let mut out = Point::zero(); + + for i in 0..N { + out = self.add(out, self.mul(n[i], p[i])); + } + + out + } + + // Point subtraction + pub fn subtract(self, p1: Point, p2: Point) -> Point { + self.add(p1, p2.negate()) + } + + // Simplified SWU map-to-curve method + pub fn swu_map(self, z: Field, u: Field) -> Point { + self.into_affine().swu_map(z, u).into_group() + } + } +} diff --git a/src/tecurve.nr b/src/tecurve.nr new file mode 100644 index 0000000..b359c5d --- /dev/null +++ b/src/tecurve.nr @@ -0,0 +1,419 @@ +pub mod affine { + // Affine coordinate representation of Twisted Edwards curves + // Points are represented by two-dimensional Cartesian coordinates. + // Group operations are implemented in terms of those in CurveGroup (in this case, extended Twisted Edwards) coordinates + // for reasons of efficiency. + // See for details. + use crate::{ + montcurve::affine::{Curve as MCurve, Point as MPoint}, + swcurve::affine::{Curve as SWCurve, Point as SWPoint}, + tecurve::curvegroup, + }; + use std::cmp::Eq; + + // Curve specification + pub struct Curve { // Twisted Edwards curve + // Coefficients in defining equation ax^2 + y^2 = 1 + dx^2y^2 + pub a: Field, + pub d: Field, + // Generator as point in Cartesian coordinates + pub gen: Point, + } + // Point in Cartesian coordinates + pub struct Point { + pub x: Field, + pub y: Field, + } + + impl Point { + // Point constructor + // #[deprecated("It's recommmended to use the external noir-edwards library (https://github.com/noir-lang/noir-edwards)")] + pub fn new(x: Field, y: Field) -> Self { + Self { x, y } + } + + // Check if zero + pub fn is_zero(self) -> bool { + self.eq(Point::zero()) + } + + // Conversion to CurveGroup coordinates + pub fn into_group(self) -> curvegroup::Point { + let Self { x, y } = self; + + curvegroup::Point::new(x, y, x * y, 1) + } + + // Additive identity + pub fn zero() -> Self { + Point::new(0, 1) + } + + // Negation + pub fn negate(self) -> Self { + let Self { x, y } = self; + Point::new(0 - x, y) + } + + // Map into prime-order subgroup of equivalent Montgomery curve + pub fn into_montcurve(self) -> MPoint { + if self.is_zero() { + MPoint::zero() + } else { + let Self { x, y } = self; + let x0 = (1 + y) / (1 - y); + let y0 = (1 + y) / (x * (1 - y)); + + MPoint::new(x0, y0) + } + } + } + + impl Eq for Point { + fn eq(self, p: Self) -> bool { + let Self { x: x1, y: y1 } = self; + let Self { x: x2, y: y2 } = p; + + (x1 == x2) & (y1 == y2) + } + } + + impl Curve { + // Curve constructor + pub fn new(a: Field, d: Field, gen: Point) -> Curve { + // Check curve coefficients + assert(a * d * (a - d) != 0); + + let curve = Curve { a, d, gen }; + + // gen should be on the curve + assert(curve.contains(curve.gen)); + + curve + } + + // Conversion to CurveGroup coordinates + pub fn into_group(self) -> curvegroup::Curve { + let Curve { a, d, gen } = self; + + curvegroup::Curve { a, d, gen: gen.into_group() } + } + + // Membership check + pub fn contains(self, p: Point) -> bool { + let Point { x, y } = p; + self.a * x * x + y * y == 1 + self.d * x * x * y * y + } + + // Point addition, implemented in terms of mixed addition for reasons of efficiency + pub fn add(self, p1: Point, p2: Point) -> Point { + self.mixed_add(p1, p2.into_group()).into_affine() + } + + // Mixed point addition, i.e. first argument in affine, second in CurveGroup coordinates. + pub fn mixed_add(self, p1: Point, p2: curvegroup::Point) -> curvegroup::Point { + let Point { x: x1, y: y1 } = p1; + let curvegroup::Point { x: x2, y: y2, t: t2, z: z2 } = p2; + + let a = x1 * x2; + let b = y1 * y2; + let c = self.d * x1 * y1 * t2; + let e = (x1 + y1) * (x2 + y2) - a - b; + let f = z2 - c; + let g = z2 + c; + let h = b - self.a * a; + + let x = e * f; + let y = g * h; + let t = e * h; + let z = f * g; + + curvegroup::Point::new(x, y, t, z) + } + + // Scalar multiplication with scalar represented by a bit array (little-endian convention). + // If k is the natural number represented by `bits`, then this computes p + ... + p k times. + pub fn bit_mul(self, bits: [u1; N], p: Point) -> Point { + self.into_group().bit_mul(bits, p.into_group()).into_affine() + } + + // Scalar multiplication (p + ... + p n times) + pub fn mul(self, n: Field, p: Point) -> Point { + self.into_group().mul(n, p.into_group()).into_affine() + } + + // Multi-scalar multiplication (n[0]*p[0] + ... + n[N]*p[N], where * denotes scalar multiplication) + pub fn msm(self, n: [Field; N], p: [Point; N]) -> Point { + let mut out = Point::zero(); + + for i in 0..N { + out = self.add(out, self.mul(n[i], p[i])); + } + + out + } + + // Point subtraction + pub fn subtract(self, p1: Point, p2: Point) -> Point { + self.add(p1, p2.negate()) + } + + // Conversion to equivalent Montgomery curve + pub fn into_montcurve(self) -> MCurve { + let j = 2 * (self.a + self.d) / (self.a - self.d); + let k = 4 / (self.a - self.d); + let gen_montcurve = self.gen.into_montcurve(); + + MCurve::new(j, k, gen_montcurve) + } + + // Conversion to equivalent Short Weierstrass curve + pub fn into_swcurve(self) -> SWCurve { + self.into_montcurve().into_swcurve() + } + + // Point mapping into equivalent Short Weierstrass curve + pub fn map_into_swcurve(self, p: Point) -> SWPoint { + self.into_montcurve().map_into_swcurve(p.into_montcurve()) + } + + // Point mapping from equivalent Short Weierstrass curve + pub fn map_from_swcurve(self, p: SWPoint) -> Point { + self.into_montcurve().map_from_swcurve(p).into_tecurve() + } + + // Elligator 2 map-to-curve method (via rational map) + pub fn elligator2_map(self, u: Field) -> Point { + self.into_montcurve().elligator2_map(u).into_tecurve() + } + + // Simplified SWU map-to-curve method (via rational map) + pub fn swu_map(self, z: Field, u: Field) -> Point { + self.into_montcurve().swu_map(z, u).into_tecurve() + } + } +} +pub mod curvegroup { + // CurveGroup coordinate representation of Twisted Edwards curves + // Points are represented by four-dimensional projective coordinates, viz. extended Twisted Edwards coordinates. + // See section 3 of for details. + use crate::{ + montcurve::curvegroup::{Curve as MCurve, Point as MPoint}, + swcurve::curvegroup::{Curve as SWCurve, Point as SWPoint}, + tecurve::affine, + }; + use std::cmp::Eq; + + // Curve specification + pub struct Curve { // Twisted Edwards curve + // Coefficients in defining equation a(x^2 + y^2)z^2 = z^4 + dx^2y^2 + pub a: Field, + pub d: Field, + // Generator as point in projective coordinates + pub gen: Point, + } + // Point in extended twisted Edwards coordinates + pub struct Point { + pub x: Field, + pub y: Field, + pub t: Field, + pub z: Field, + } + + impl Point { + // Point constructor + pub fn new(x: Field, y: Field, t: Field, z: Field) -> Self { + Self { x, y, t, z } + } + + // Check if zero + pub fn is_zero(self) -> bool { + let Self { x, y, t, z } = self; + (x == 0) & (y == z) & (y != 0) & (t == 0) + } + + // Conversion to affine coordinates + pub fn into_affine(self) -> affine::Point { + let Self { x, y, t: _t, z } = self; + + affine::Point::new(x / z, y / z) + } + + // Additive identity + pub fn zero() -> Self { + Point::new(0, 1, 0, 1) + } + + // Negation + pub fn negate(self) -> Self { + let Self { x, y, t, z } = self; + + Point::new(0 - x, y, 0 - t, z) + } + + // Map into prime-order subgroup of equivalent Montgomery curve + pub fn into_montcurve(self) -> MPoint { + self.into_affine().into_montcurve().into_group() + } + } + + impl Eq for Point { + fn eq(self, p: Self) -> bool { + let Self { x: x1, y: y1, t: _t1, z: z1 } = self; + let Self { x: x2, y: y2, t: _t2, z: z2 } = p; + + (x1 * z2 == x2 * z1) & (y1 * z2 == y2 * z1) + } + } + + impl Curve { + // Curve constructor + pub fn new(a: Field, d: Field, gen: Point) -> Curve { + // Check curve coefficients + assert(a * d * (a - d) != 0); + + let curve = Curve { a, d, gen }; + + // gen should be on the curve + assert(curve.contains(curve.gen)); + + curve + } + + // Conversion to affine coordinates + pub fn into_affine(self) -> affine::Curve { + let Curve { a, d, gen } = self; + + affine::Curve { a, d, gen: gen.into_affine() } + } + + // Membership check + pub fn contains(self, p: Point) -> bool { + let Point { x, y, t, z } = p; + + (z != 0) + & (z * t == x * y) + & (z * z * (self.a * x * x + y * y) == z * z * z * z + self.d * x * x * y * y) + } + + // Point addition + pub fn add(self, p1: Point, p2: Point) -> Point { + let Point { x: x1, y: y1, t: t1, z: z1 } = p1; + let Point { x: x2, y: y2, t: t2, z: z2 } = p2; + + let a = x1 * x2; + let b = y1 * y2; + let c = self.d * t1 * t2; + let d = z1 * z2; + let e = (x1 + y1) * (x2 + y2) - a - b; + let f = d - c; + let g = d + c; + let h = b - self.a * a; + + let x = e * f; + let y = g * h; + let t = e * h; + let z = f * g; + + Point::new(x, y, t, z) + } + + // Point doubling, cf. section 3.3 + pub fn double(self, p: Point) -> Point { + let Point { x, y, t: _t, z } = p; + + let a = x * x; + let b = y * y; + let c = 2 * z * z; + let d = self.a * a; + let e = (x + y) * (x + y) - a - b; + let g = d + b; + let f = g - c; + let h = d - b; + + let x0 = e * f; + let y0 = g * h; + let t0 = e * h; + let z0 = f * g; + + Point::new(x0, y0, t0, z0) + } + + // Scalar multiplication with scalar represented by a bit array (little-endian convention). + // If k is the natural number represented by `bits`, then this computes p + ... + p k times. + pub fn bit_mul(self, bits: [u1; N], p: Point) -> Point { + let mut out = Point::zero(); + + for i in 0..N { + out = self.add( + self.add(out, out), + if (bits[N - i - 1] == 0) { + Point::zero() + } else { + p + }, + ); + } + + out + } + + // Scalar multiplication (p + ... + p n times) + pub fn mul(self, n: Field, p: Point) -> Point { + // TODO: temporary workaround until issue 1354 is solved + let mut n_as_bits: [u1; 254] = [0; 254]; + let tmp: [u1; 254] = n.to_le_bits(); + 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) + pub fn msm(self, n: [Field; N], p: [Point; N]) -> Point { + let mut out = Point::zero(); + + for i in 0..N { + out = self.add(out, self.mul(n[i], p[i])); + } + + out + } + + // Point subtraction + pub fn subtract(self, p1: Point, p2: Point) -> Point { + self.add(p1, p2.negate()) + } + + // Conversion to equivalent Montgomery curve + pub fn into_montcurve(self) -> MCurve { + self.into_affine().into_montcurve().into_group() + } + + // Conversion to equivalent Short Weierstrass curve + pub fn into_swcurve(self) -> SWCurve { + self.into_montcurve().into_swcurve() + } + + // Point mapping into equivalent short Weierstrass curve + pub fn map_into_swcurve(self, p: Point) -> SWPoint { + self.into_montcurve().map_into_swcurve(p.into_montcurve()) + } + + // Point mapping from equivalent short Weierstrass curve + pub fn map_from_swcurve(self, p: SWPoint) -> Point { + self.into_montcurve().map_from_swcurve(p).into_tecurve() + } + + // Elligator 2 map-to-curve method (via rational maps) + pub fn elligator2_map(self, u: Field) -> Point { + self.into_montcurve().elligator2_map(u).into_tecurve() + } + + // Simplified SWU map-to-curve method (via rational map) + pub fn swu_map(self, z: Field, u: Field) -> Point { + self.into_montcurve().swu_map(z, u).into_tecurve() + } + } +}