diff --git a/.github/workflows/mirror-external_libs.yml b/.github/workflows/mirror-external_libs.yml index e577ac0ed92..d1ce0c7aaad 100644 --- a/.github/workflows/mirror-external_libs.yml +++ b/.github/workflows/mirror-external_libs.yml @@ -1,8 +1,38 @@ +# We push using git subrepo (https://github.com/ingydotnet/git-subrepo) +# with some logic to recover from squashed parent commits +# We first identify ourselves, needed to commit. +# Then push to subrepo, commit to master. The commit is needed +# to continue to replay. If we still hit issues such as this +# action failing due to upstream changes, a manual resolution +# PR with ./scripts/git_subrepo.sh pull will be needed. name: Mirror Repositories on: + schedule: + # Run the workflow every night at 2:00 AM UTC. + - cron: "0 2 * * *" workflow_dispatch: {} + jobs: - lint: + mirror-to-noir-edwards-lib: runs-on: ubuntu-latest steps: - - run: echo Dummy workflow TODO \ No newline at end of file + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + token: ${{ secrets.NOIR_RELEASES_TOKEN }} + - name: Push to external libs repo + run: | + SUBREPO_PATH=external_libs/noir-edwards + git config --global user.name noirwhal + git config --global user.email tomfrench@aztecprotocol.com + + git clone https://github.com/ingydotnet/git-subrepo + if ./git-subrepo/lib/git-subrepo push $SUBREPO_PATH --branch=master; then + git fetch # in case a commit came after this + git rebase origin/master + git commit --amend -m "$(git log -1 --pretty=%B) [skip ci]" + git push + fi + + diff --git a/external_libs/noir-edwards/.gitignore b/external_libs/noir-edwards/.gitignore new file mode 100644 index 00000000000..1de565933b0 --- /dev/null +++ b/external_libs/noir-edwards/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/external_libs/noir-edwards/.gitrepo b/external_libs/noir-edwards/.gitrepo new file mode 100644 index 00000000000..14d55a04c5b --- /dev/null +++ b/external_libs/noir-edwards/.gitrepo @@ -0,0 +1,12 @@ +; DO NOT EDIT (unless you know what you are doing) +; +; This subdirectory is a git "subrepo", and this file is maintained by the +; git-subrepo command. See https://github.com/ingydotnet/git-subrepo#readme +; +[subrepo] + remote = https://github.com/zac-williamson/noir-edwards.git + branch = main + commit = 0016ce82cd58b6ebb0c43c271725590bcff4e755 + parent = e1bcb73f8c2e2c6786faeb18b8ce070a2400635d + method = merge + cmdver = 0.4.6 diff --git a/external_libs/noir-edwards/Nargo.toml b/external_libs/noir-edwards/Nargo.toml new file mode 100644 index 00000000000..449f439fcc6 --- /dev/null +++ b/external_libs/noir-edwards/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "edwards" +type = "lib" +authors = [""] +compiler_version = ">=0.30.0" + +[dependencies] \ No newline at end of file diff --git a/external_libs/noir-edwards/README.md b/external_libs/noir-edwards/README.md new file mode 100644 index 00000000000..ed277761635 --- /dev/null +++ b/external_libs/noir-edwards/README.md @@ -0,0 +1,20 @@ +# noir-edwards + +Optimized implementation of Twisted Edwards curves. + +Uses lookup tables and maximally efficient representations of group operations (for width-4 noir) to efficiently implement scalar multiplication and multiscalar multiplication. + +Cost of 1 scalar mul = 2232 gates. Marginal cost of additional muls in an msm = 972 gates. + +For example usage see `test.nr` + +List of potential optimizations to improve performance: + +1. update barretenberg backend to identify when memory lookups always come in pairs. e.g. two MEM operations, different ids, read index is the same. backend can convert into 1 memory table with 2 values instead of 2 memory tables with 1 value + +``` +MEM (id: 1, read at: x1, value: x125) +MEM (id: 2, read at: x1, value: x126) +``` + +2. fix barretenberg bug where range checks for values <2^{14} create an unneccessary addition gate. diff --git a/external_libs/noir-edwards/info.sh b/external_libs/noir-edwards/info.sh new file mode 100755 index 00000000000..92e41a91d44 --- /dev/null +++ b/external_libs/noir-edwards/info.sh @@ -0,0 +1 @@ +nargo compile --force && bb gates -b ./target/edwards.json diff --git a/external_libs/noir-edwards/src/bjj.nr b/external_libs/noir-edwards/src/bjj.nr new file mode 100644 index 00000000000..12a41717378 --- /dev/null +++ b/external_libs/noir-edwards/src/bjj.nr @@ -0,0 +1,10 @@ +use crate::TECurveParameterTrait; +use crate::Curve; + +struct BabyJubJubParams {} +impl TECurveParameterTrait for BabyJubJubParams { + fn a() -> Field { 168700 } + fn d() -> Field { 168696 } + fn gen() -> (Field, Field) { (0x0bb77a6ad63e739b4eacb2e09d6277c12ab8d8010534e0b62893f3f6bb957051, 0x25797203f7a0b24925572e1cd16bf9edfce0051fb9e133774b3c257a872d7d8b)} +} +type BabyJubJub = Curve; \ No newline at end of file diff --git a/external_libs/noir-edwards/src/lib.nr b/external_libs/noir-edwards/src/lib.nr new file mode 100644 index 00000000000..d7523115ae6 --- /dev/null +++ b/external_libs/noir-edwards/src/lib.nr @@ -0,0 +1,443 @@ +mod scalar_field; +mod test; +mod bjj; + +use dep::std; +use crate::scalar_field::ScalarField; + +struct Curve { + x: Field, + y: Field, +} + +// #################################################################################################################### +// #################################################################################################################### +// ### T R A I T S +// #################################################################################################################### +// #################################################################################################################### +/** + * @brief parametrises a Twisted Edwards curve + **/ +trait TECurveParameterTrait { + fn a() -> Field; // twisted edward curve parameter a + fn d() -> Field; // twisted edward curve parameter d + fn gen() -> (Field, Field); // generator point x/y coordinates +} + +/** + * @brief defines methods that a valid Curve implementation must satisfy + **/ +trait CurveTrait where CurveTrait: std::ops::Add + std::ops::Sub + std::ops::Eq + std::ops::Neg { + fn default() -> Self { std::default::Default::default () } + fn new(x: Field, y: Field) -> Self; + fn zero() -> Self; + fn one() -> Self; + + fn add(self, x: Self) -> Self { self + x } + fn sub(self, x: Self) -> Self { self - x } + fn neg(self) -> Self { std::ops::Neg::neg(self) } + fn dbl(self) -> Self; + fn mul(self, x: ScalarField) -> Self; + fn msm (points: [Self; N], scalars: [ScalarField; N]) -> Self; + + fn eq(self, x: Self) -> bool { self == x } + fn is_zero(self) -> bool { self == Self::zero() } + + fn is_on_curve(self) -> bool; + fn assert_is_on_curve(self); + fn assert_equal(self, other: Self); +} + +// #################################################################################################################### +// #################################################################################################################### +// ### C O N S T R A I N E D F U N C T I O N S +// #################################################################################################################### +// #################################################################################################################### +impl std::default::Default for Curve where Params: TECurveParameterTrait { + /** + * @brief return point at infinity + * + * Cost: 0 gates + **/ + fn default() -> Self { + Curve::zero() + } +} + +impl std::ops::Add for Curve where Params: TECurveParameterTrait { + /** + * @brief compute `self + other` + * + * Cost: 7 gates + **/ + fn add(self, other:Self) -> Self { + Curve::add_internal(self, other, Params::a(), Params::d()) + } +} + +impl std::ops::Neg for Curve where Params: TECurveParameterTrait { + /** + * @brief negate a point + * + * Cost: usually 0, will cost 1 gate if the `x` coordinate needs to be converted into a witness + **/ + fn neg(self) -> Self { + Curve{ x: -self.x, y: self.y } + } +} + +impl std::ops::Sub for Curve where Params: TECurveParameterTrait { + /** + * @brief compute `self - other` + * + * Cost: 7 gates + **/ + fn sub(self, other:Self) -> Self { + Curve::add_internal(self, other.neg(), Params::a(), Params::d()) + } +} + +impl std::cmp::Eq for Curve where Params: TECurveParameterTrait { + /** + * @brief compute `self == other` + * + * Cost: 6 gates + **/ + fn eq(self, other:Self) -> bool { + (self.x == other.x) & (self.y == other.y) + } +} + +impl std::convert::From<(Field, Field)> for Curve where Params: TECurveParameterTrait { + /** + * @brief construct from tuple of field elements + * @details use this method instead of `new` if you know x/y is on the curve + * + * Cost: 0 gates + **/ + fn from((x, y): (Field, Field)) -> Self { + Curve{ x, y } + } +} + +impl CurveTrait for Curve where Params: TECurveParameterTrait { + + /** + * @brief Construct a new point + * @details If you know the x/y coords form a valid point DO NOT USE THIS METHOD + * This method calls `assert_is_on_curve` which costs 3 gates. + * Instead, directly construct via Curve{x, y} + * Or use from((x, y)) + * + * Cost: 3 gates + **/ + fn new(x: Field, y: Field) -> Self { + let result = Curve{ x, y }; + result.assert_is_on_curve(); + result + } + + /** + * @brief return the Identity element (point at infinity) + * + * Cost: 0 gates + **/ + fn zero() -> Self { Curve{ x: 0, y: 1 } } + + /** + * @brief return the Generator of the group + * + * Cost: 0 gates (assuming Params trait returns values known at compile time!) + **/ + fn one() -> Self { + let (x, y) = Params::gen(); + Curve{ x, y } + } + + /** + * @brief validate a point is on the curve + * @details cheaper than `is_on_curve` (assert is cheaper than returning a bool) + * + * Cost: 3 gates + **/ + fn assert_is_on_curve(self) { + let t0 = self.x * self.x; + let t1 = self.y * self.y; + std::as_witness(t0); + std::as_witness(t1); + let t2 = Params::a() * t0 + t1; + let t3 = 1 + Params::d() * t0 * t1; + assert(t2 == t3); + } + + /** + * @brief Constrain two points to equal each other + * @details Cheaper than `assert(self == other)` because no need to return a bool + * + * Cost: 0-2 gates (can do these asserts with just copy constraints) + **/ + fn assert_equal(self, other: Self) { + assert(self.x == other.x); + assert(self.y == other.y); + } + + /** + * @brief return a bool that describes whether the point is on the curve + * @details if you don't need to handle the failure case, it is cheaper to call `assert_is_on_curve` + * + * Cost: 5 gates + **/ + fn is_on_curve(self) -> bool { + let t0 = self.x * self.x; + let t1 = self.y * self.y; + std::as_witness(t0); + std::as_witness(t1); + let t2 = Params::a() * t0 + t1; + let t3 = 1 + Params::d() * t0 * t1; + (t2 == t3) + } + + /** + * @brief compute `self + self` + * + * Cost: 5 gates + **/ + fn dbl(self) -> Self { + Curve::dbl_internal(self, Params::a(), Params::d()) + } + + /** + * @brief compute `self * scalar` + * @details uses the Straus method via lookup tables. + * Assumes backend has an efficient implementation of a memory table abstraction + * i.e. `let x = table[y]` is efficient even if `y` is not known at compile time + * + * Key cost components are as follows: + * 1: computing the Straus point lookup table (169 gates) + * 2: 252 point doublings (1260 gates) + * 3: 63 point additions (441 gates) + * 4: 126 table reads with runtime index (252 gates) + * + * Cost: 2122 gates + cost of creating ScalarField (110 gates) + * + * TODO: use windowed non-adjacent form to remove 7 point additions when creating lookup table + **/ + fn mul(self: Self, scalar: ScalarField) -> Self { + // define a, d params locally to make code more readable (shouldn't affect performance) + let a = Params::a(); + let d = Params::d(); + + // Construct tables of precomputed point coordinates. + let (table_x, table_y): ([Field; 16], [Field; 16]) = self.compute_straus_point_table(a, d); + + // Initialize the accumulator with the point that maps to the first (most significant) scalar slice + let idx = scalar.base4_slices[0]; + let mut accumulator: Self = Curve { x: table_x[idx], y: table_y[idx] }; + + // Execute a double-and-add subroutine + // 1. Compute `accumulator = accumulator * 16` + // 2. Extract 4-bits from the scalar multiplier and + // use them to retrieve the corresponding point from our point table + // Note: this is similar to the "double and add" scalar multiplication method, except we use base16 instead of base2! + for i in 1..NScalarSlices { + accumulator = accumulator.dbl_internal(a, d); + accumulator = accumulator.dbl_internal(a, d); + accumulator = accumulator.dbl_internal(a, d); + accumulator = accumulator.dbl_internal(a, d); + let idx: u8 = scalar.base4_slices[i]; + let x = table_x[idx]; + let y = table_y[idx]; + accumulator = accumulator.add_internal(Curve{ x, y }, a, d); + } + + // todo fix + if (scalar.skew) { + accumulator = accumulator - self; + } + accumulator + } + + /** + * @brief compute `points[0] * scalar[0] + ... + points[N-1] * scalar[N-1]` + * @details Is cheaper than `mul` when processing >1 point due to reduced number of point doublings + * uses the Straus MSM method via lookup tables. + * Assumes backend has an efficient implementation of a memory table abstraction + * i.e. `let x = table[y]` is efficient even if `y` is not known at compile time + * + * Key cost components are as follows + * PER POINT costs: + * 1: computing the Straus point lookup table (169N gates) + * 2: 63 point additions (441N gates) + * 3: 126 table reads with runtime index (252N gates) + * + * Additional costs: + * 1. 252 point doublings 1260 gates + * + * Cost: 1260 + 862N + cost of creating ScalarField (110N gates) + * + * TODO: use windowed non-adjacent form to remove 7 point additions per point when creating lookup table + **/ + fn msm (points: [Self; N], scalars: [ScalarField; N]) -> Self + { + let a = Params::a(); + let d = Params::d(); + + // Generalized version of `mul` for multiple points. + let mut point_tables: [([Field; 16], [Field; 16]); N] = [([0; 16], [0; 16]); N]; + for j in 0..N { + point_tables[j] = points[j].compute_straus_point_table(a, d); + } + + let idx = scalars[0].base4_slices[0]; + let mut accumulator: Self = Curve{ x: point_tables[0].0[idx], y: point_tables[0].1[idx] }; + for j in 1..N { + let idx = scalars[j].base4_slices[0]; + let P = Curve{ x: point_tables[j].0[idx], y: point_tables[j].1[idx] }; + accumulator = accumulator.add_internal(P, a, d); + } + for i in 1..NScalarSlices { + accumulator = accumulator.dbl_internal(a, d); + accumulator = accumulator.dbl_internal(a, d); + accumulator = accumulator.dbl_internal(a, d); + accumulator = accumulator.dbl_internal(a, d); + for j in 0..N { + let idx: u8 = scalars[j].base4_slices[i]; + let x = point_tables[j].0[idx]; + let y = point_tables[j].1[idx]; + accumulator = accumulator.add_internal(Curve{ x, y }, a, d); + } + } + + for j in 0..N { + if (scalars[j].skew == true) + { + accumulator = accumulator - points[j]; + } + } + accumulator + } +} + +// #################################################################################################################### +// #################################################################################################################### +// ### H E L P E R F U N C T I O N S +// #################################################################################################################### +// #################################################################################################################### +impl Curve { + /** + * @brief add points together, return output + lambda term + **/ + unconstrained pub fn __add_unconstrained(x1: Field, x2: Field, y1: Field, y2: Field, a: Field, d: Field) -> (Field, Field, Field) { + let lambda = y1 * y2 * x1 * x2; + let y = (x1 * x2 * a - y1 * y2) / (lambda * d - 1); + let x = (x1 * y2 + y1 * x2) / (lambda * d + 1); + (x, y, lambda) + } + + /** + * @brief add two points together + * @details This method exists because of a Noir bug where `Params` cannot be accessed by an internal function + * called from internal function. + * e.g. compiler error if `mul` impl tries to call `add` :( + **/ + fn add_internal(self, other: Self, a: Field, d: Field) -> Self { + let x1 = self.x; + let x2 = other.x; + let y1 = self.y; + let y2 = other.y; + let (x, y, lambda) = Curve::__add_unconstrained(x1, x2, y1, y2, a, d); + let x1x2 = x1 * x2; + let x1y2 = x1 * y2; + std::as_witness(x1x2); + std::as_witness(x1y2); + let x_lhs = x * lambda * d + x - x1y2; // equals y1x2 + let y_lhs = y * lambda * -d + y + x1x2 * a; // equals y1y2 + let y1x2 = y1 * x2; + let y1y2 = y1 * y2; + std::as_witness(y1x2); + std::as_witness(y1y2); + let y1y2x1x2 = y1y2 * x1x2; + assert(x_lhs == y1x2); + assert(y_lhs == y1y2); + assert(y1y2x1x2 == lambda); + Self { x, y } + } + + /** + * @brief add a point to itself + * @details This method exists because of a Noir bug where `Params` cannot be accessed by an internal function + * called from internal function. + * e.g. compiler error if `mul` impl tries to call `dbl` :( + **/ + fn dbl_internal(self, a: Field, d: Field) -> Self { + let x1 = self.x; + let y1 = self.y; + let (x3, y3, _) = Curve::__add_unconstrained(x1, x1, y1, y1, a, d); + let x1x1a = x1 * x1 * a; + std::as_witness(x1x1a); + // t1 = a*x_1^2 + y_1^2 + let t1 = y1 * y1 + x1x1a; + std::as_witness(t1); + // t3 = y_3 * (2 - a*x_1^2 + y_1^2) + 2*a*x_1^2 + let t3 = y3 + y3 - t1 * y3 + x1x1a * 2; + // t3 == t1 implies y_3 * (2 - a*x_1^2 - y_1^2) + 2*a*x_1^2 == a*x_1^2 + y_1^2 + // i.e. y_3 = y_1^2 - a*x_1^2 / (2 - a*x_1^2 - y_1^2) + assert(t3 == t1); + let t4 = x1 * y1; + std::as_witness(t4); + // x3 * t1 - t4 == t4 implies x_3 * (y_1^2 + a * x_1^2) = 2 * x_1 * y_1 + // i.e. x_3 = 2 * x_1 * y_1 / (y_1^2 + a * x_1^2) + let t2 = x3 * t1 - t4; + assert(t2 == t4); + Self { x: x3, y: y3 } + } + + /** + * @brief Compute a 4-bit lookup table of point multiples for the Straus windowed scalar multiplication algorithm. + * @details Table contains [0, P, 2P, ..., 15P], which is used in the scalar mul algorithm to minimize the total number of required point additions + * + * @note It is cheaper to use ([Field; 16], [Field; 16]) than it is ([Curve; 16]). + * This is because the compiler will represent [Curve; 16] in 1 ROM array (vs 2 for [Field; 16], [Field; 16]). + * This means that any index into the ROM array for [Curve; 16] requires an additional arithmetic gate to process. + * + * For example consider `let P: Curve = table[idx]` + * `table` will be a ROM array with 32 elements in it. + * The x-coordinates will be located at `2 * idx` + * The y-coordinates will be located at `2 * idx + 1` + * If `idx` is not known at compile time (for Straus it isnt), 2 arithmetic gates are required to evaluate `2 * idx`, `2 * idx + 1` + * before they can be used as arguments in a memory lookup protocol + * + * Now consider `let P_x = table_x[idx]; let P_y = table_y[idx]` + * In this example, `idx` can be directly used as the argument into a memory lookup protocol for both tables. + * + * For the Barretenberg backend, the cost of a Read-Only memory lookup is 2 gates, + * so splitting the x/y coordinates into separate tables means that the cost to lookup a point is 4 gates + * 2 extra arithmetic gates would increase the cost by 50%, which we avoid by returning `([Field; 16], [Field; 16])` instead of `([Curve; 16])` + * + * Key cost components are as follows: + * 1: Defining two size-16 lookup tables (2 gates per element, 32 elements = 64 gates) + * 2: 15 point additions (7 * 5 = 105) + * + * Total Cost: 169 gates + * + * TODO: use windowed non-adjacent form to remove 8 point additions + **/ + fn compute_straus_point_table(self, a: Field, d: Field) -> ([Field; 16], [Field; 16]) { + let mut table_x: [Field; 16] = [0; 16]; + let mut table_y: [Field; 16] = [0; 16]; + table_x[8] = self.x; + table_y[8] = self.y; + let D = self.dbl_internal(a, d); + for i in 1..8 { + let Q = Curve { x: table_x[7 + i], y: table_y[7 + i] }; + let V = D.add_internal(Q, a, d); + table_x[8 + i] = V.x; + table_y[8 + i] = V.y; + } + for i in 0..8 { + table_x[i] = -table_x[15 - i]; + table_y[i] = table_y[15 - i]; + } + (table_x, table_y) + } +} diff --git a/external_libs/noir-edwards/src/scalar_field.nr b/external_libs/noir-edwards/src/scalar_field.nr new file mode 100644 index 00000000000..86f6f56c9dc --- /dev/null +++ b/external_libs/noir-edwards/src/scalar_field.nr @@ -0,0 +1,163 @@ +/** + * @brief ScalarField represents a scalar multiplier as a sequence of 4-bit slices + * @details There is nuance to ScalarField, because twisted edwards curves generally have prime group orders that easily fit into a Field + * We can therefore obtain cheap conversions by simply summing up the bit slices and validate they equal the input scalar + * However...when converting arbitrary field elements (i.e. scalars that are multiples of a TE curve group order), + * we must perform additional checks when converting into 4-bit slices, as we must validate that the sum of the slices is smaller than the Field modulus (when evaluated over the integers) + * This is expensive and we would rather not do it! therefore ScalarField is flexible. + * ScalarField<63> enables cheap bitslice converions for scalar multipliers that must be <2^{252} + * ScalarField<64> enables bitslice conversions for arbitrary field elements + * + * N.B. ScalarField bit values are not constrained to be smaller than the TE curve group order. + * ScalarField is used when performing scalar multiplications, where all operations wrap modulo the curve order + **/ +struct ScalarField { + base4_slices: [u8; N], + skew: bool +} + +unconstrained fn get_wnaf_slices(x: Field) -> ([u8; N], bool) { + + let mut result: [u8; N] = [0; N]; + let mut nibbles = x.to_le_radix(16, N); + + let skew: bool = nibbles[0] & 1 == 0; + nibbles[0] += skew as u8; + result[N-1] = (nibbles[0] + 15) / 2; + for i in 1..N { + let mut nibble: u8 = nibbles[i]; + result[N-1 - i] = (nibble + 15) / 2; + if (nibble & 1 == 0) { + result[N-1 - i] += 1; + result[N - i] -= 8; + } + } + (result, skew) +} + +unconstrained fn from_wnaf_slices(x: [u8; 64], skew: bool) -> Field { + let mut result: Field = 0; + + for i in 0..64 { + result *= 16; + result += (x[i] as Field) * 2 - 15; + } + result -= skew as Field; + result +} + +#[test] +fn test_wnaf() { + + let result: Field = 0x123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0; + let (t0, t1) = get_wnaf_slices(result); + let expected = from_wnaf_slices(t0, t1); + assert(result == expected); +} + +unconstrained fn get_modulus_slices() -> (Field, Field) { + let bytes = std::field::modulus_be_bytes(); + let num_bytes = std::field::modulus_num_bits() / 8; + let mut lo: Field = 0; + let mut hi: Field = 0; + for i in 0..(num_bytes / 2) { + hi *= 256; + hi += bytes[i] as Field; + lo *= 256; + lo += bytes[i + (num_bytes/2)] as Field; + } + if (num_bytes & 1 == 1) { + lo *= 256; + lo += bytes[num_bytes - 1] as Field; + } + (lo, hi) +} + +unconstrained fn get_borrow_flag(lhs_lo: Field, rhs_lo: Field) -> bool { + lhs_lo.lt(rhs_lo + 1) +} +impl std::convert::From for ScalarField { + + + + /** + * @brief construct from a field element + * @details if N >= 64 we perform extra checks to ensure the slice decomposition represents the same integral value as the input + * (e.g. sum of slices != x + modulus) + **/ + fn from(x: Field) -> Self { + let mut result: Self = ScalarField { base4_slices: [0; N], skew: false }; + let (slices, skew): ([u8; N], bool) = get_wnaf_slices(x); + result.base4_slices = slices; + result.skew = skew; + if (N < 64) { + let mut acc: Field = (slices[0] as Field) * 2 - 15; + for i in 1..N { + acc *= 16; + acc += (slices[i] as Field) * 2 - 15; + } + assert(acc - skew as Field == x); + } + else + { + // TODO: if num bits = 64, validate in sum of the bits is smaller than the Field modulus + let mut lo: Field = slices[(N/2)] as Field; + let mut hi: Field = slices[0] as Field; + let mut borrow_shift = 1; + for i in 1..(N/2) { + borrow_shift *= 16; + lo *= 16; + lo += (slices[(N/2) + i] as Field) * 2 - 15; + hi *= 16; + hi += (slices[i] as Field) * 2 - 15; + } + if ((N & 1) == 1) + { + borrow_shift *= 16; + lo *= 16; + lo += (slices[N-1] as Field) * 2 - 15; + } + lo -= skew as Field; + // Validate that the integer represented by (lo, hi) is smaller than the integer represented by (plo, phi) + let (plo, phi) = get_modulus_slices(); + let borrow = get_borrow_flag(plo, lo) as Field; + let rlo = plo - lo + borrow * borrow_shift - 1; // -1 because we are checking a strict <, not <= + let rhi = phi - hi - borrow; + let offset = (N & 1 == 1) as u8; + let hibits = (N / 2) * 4; + let lobits = hibits + offset * 4; + rlo.assert_max_bit_size(lobits as u32); + rhi.assert_max_bit_size(hibits as u32); + } + for i in 0..N { + (result.base4_slices[i] as Field).assert_max_bit_size(4); + } + result + } +} + +impl std::convert::Into for ScalarField { + /** + * @brief construct from tuple of field elements + * @details use this method instead of `new` if you know x/y is on the curve + **/ + fn into(self: Self) -> Field { + let mut acc: Field = 0; + for i in 0..N { + acc = acc * 16; + acc = acc + (self.base4_slices[i] as Field) * 2 - 15; + } + acc -= self.skew as Field; + acc + } +} + +impl ScalarField { + + fn new() -> Self { + Self { base4_slices: [0; N], skew: false } + } + fn get(self, idx: u64) -> u8 { + self.base4_slices[idx] + } +} diff --git a/external_libs/noir-edwards/src/test.nr b/external_libs/noir-edwards/src/test.nr new file mode 100644 index 00000000000..f151d80072d --- /dev/null +++ b/external_libs/noir-edwards/src/test.nr @@ -0,0 +1,79 @@ +use dep::std; +use dep::std::ec; +use crate::scalar_field::ScalarField; +use crate::Curve; +use crate::TECurveParameterTrait; +use crate::bjj::BabyJubJubParams; +use std::ec::consts::te::baby_jubjub; +use std::ec::tecurve::affine::Point as TEPoint; + +type BabyJubJub = Curve; + +#[test] +fn test_sub() { + let bjj = baby_jubjub(); + let bjj_point = bjj.base8; + let point: Curve = Curve { x: bjj_point.x, y: bjj_point.y }; + + let expected = point + (point + (point)); + let result = point.dbl().dbl().sub(point); + assert(result.x == expected.x); + assert(result.y == expected.y); +} + +#[test] +fn test_mul() { + let scalar = 13439285682734681346581734618; + let bjj = baby_jubjub(); + let bjj_point = bjj.base8; + + let expected = bjj.curve.mul(scalar, bjj_point); + let scalar_f: ScalarField<63> = ScalarField::from(scalar); + let point: BabyJubJub = Curve { x: bjj_point.x, y: bjj_point.y }; + let result = point.mul(scalar_f); + let scalar_converted: Field = ScalarField::into(scalar_f); + assert(scalar_converted == scalar); + assert(result.x == expected.x); + assert(result.y == expected.y); +} + +#[test] +fn test_msm() { + let scalar = 13439285682734681346581734618; + let bjj = baby_jubjub(); + let bjj_point = bjj.base8; + + let point: BabyJubJub = Curve { x: bjj_point.x, y: bjj_point.y }; + let mut scalar_values: [Field; 1] = [0; 1]; + let mut points: [BabyJubJub; 1] = [Curve { x: 0, y: 0 }; 1]; + let mut scalars: [ScalarField<63>; 1] = [ScalarField::new(); 1]; + let mut bjj_points: [TEPoint; 1] = [TEPoint::new(0, 0); 1]; + + points[0] = point; + scalar_values[0] = scalar; + scalars[0] = ScalarField::from(scalar_values[0]); + bjj_points[0] = TEPoint::new(points[0].x, points[0].y); + + for i in 1..1 { + points[i] = points[i-1].dbl(); + scalar_values[i] = scalar_values[i-1] + scalar; + } + + for i in 0..1 { + scalars[i] = ScalarField::from(scalar_values[i]); + bjj_points[i] = TEPoint::new(points[i].x, points[i].y); + let scalar_expected = scalar_values[i]; + let scalar_result: Field = ScalarField::into(scalars[i]); + assert(scalar_result == scalar_expected); + + let result_point = points[i].mul(scalars[i]); + let expected_point = bjj.curve.mul(scalar_values[i], bjj_points[i]); + assert(result_point.x == expected_point.x); + assert(result_point.y == expected_point.y); + } + let expected = bjj.curve.msm(scalar_values, bjj_points); + let result = Curve::msm(points, scalars); + + assert(result.x == expected.x); + assert(result.y == expected.y); +} diff --git a/scripts/git_subrepo.sh b/scripts/git_subrepo.sh new file mode 100755 index 00000000000..969789cdcc8 --- /dev/null +++ b/scripts/git_subrepo.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set -eu + +SCRIPT_DIR=$(dirname "$(realpath "$0")") + +# Check for unstaged changes +if ! git diff-index --quiet HEAD --; then + echo "Error: You have unstaged changes. Please commit or stash them before running git_subrepo.sh." + exit 1 +fi + +# git subrepo is quite nice, but has one flaw in our workflow: +# We frequently squash commits in PRs, and we might update the .gitrepo file +# with a parent commit that later does not exist. +# A backup heuristic is used to later find the squashed commit's parent +# using the .gitrepo file's git history. This might be brittle +# in the face of e.g. a .gitrepo whitespace change, but it's a fallback, +# we only have this issue in master, and the file should only be edited +# generally by subrepo commands. +SUBREPO_PATH="${2:-}" +if [ -d "$SUBREPO_PATH" ] ; then + # Read parent commit from .gitrepo file + parent_commit=$(awk -F'= ' '/parent =/{print $2}' $SUBREPO_PATH/.gitrepo) + # Check if the parent commit exists in this branch + if ! git branch --contains $parent_commit | grep -q '\*'; then + "$SCRIPT_DIR"/fix_subrepo_edge_case.sh "$SUBREPO_PATH" + fi +fi + +# Function to handle subrepo actions and possible error +run_subrepo() { + "$SCRIPT_DIR/git-subrepo/lib/git-subrepo" "$@" 2>&1 | tee /tmp/subrepo_output.log + local exit_code="${PIPESTATUS[0]}" # Capture the exit status of git-subrepo command + + if [ $exit_code -ne 0 ]; then + # Check for the specific error message and maybe recover + if grep -q "doesn't contain upstream HEAD" /tmp/subrepo_output.log; then + "$SCRIPT_DIR/fix_subrepo_edge_case.sh" "$SUBREPO_PATH" + echo "Rerunning..." + "$SCRIPT_DIR/git-subrepo/lib/git-subrepo" "$@" + else + exit $exit_code + fi + fi +} + + +run_subrepo "$@" diff --git a/test_programs/execution_success/external_lib/Nargo.toml b/test_programs/execution_success/external_lib/Nargo.toml new file mode 100644 index 00000000000..3e4dba04907 --- /dev/null +++ b/test_programs/execution_success/external_lib/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "external_lib" +type = "bin" +authors = [""] + +[dependencies] +edwards = { path = "../../../external_libs/noir-edwards" } + diff --git a/test_programs/execution_success/external_lib/Prover.toml b/test_programs/execution_success/external_lib/Prover.toml new file mode 100644 index 00000000000..1248f1e0797 --- /dev/null +++ b/test_programs/execution_success/external_lib/Prover.toml @@ -0,0 +1,2 @@ +x = 1 +y = 1 diff --git a/test_programs/execution_success/external_lib/src/main.nr b/test_programs/execution_success/external_lib/src/main.nr new file mode 100644 index 00000000000..2e78ec3c8ab --- /dev/null +++ b/test_programs/execution_success/external_lib/src/main.nr @@ -0,0 +1,11 @@ +use edwards::Curve; +use edwards::bjj::BabyJubJubParams; +use std::ec::consts::te::baby_jubjub; +use std::ec::tecurve::affine::Point as TEPoint; + +fn main(x: Field, y: pub Field) -> pub Field { + let bjj = baby_jubjub(); + let bjj_point = bjj.base8; + let point: Curve = Curve { x: bjj_point.x, y: bjj_point.y }; + (point.x == x) as Field +}