diff --git a/README.md b/README.md index 2f4b351..27f3f6b 100644 --- a/README.md +++ b/README.md @@ -11,44 +11,65 @@ Multiplication operations for a 2048-bit prime field cost approx. 930 gates. bignum can evaluate large integer arithmetic by defining a modulus() that is a power of 2. -# Types - -bignum operations are evaluated using two structs and a trait: `ParamsTrait`, `BigNum`, `BigNumInstance` - -`ParamsTrait` defines the compile-time properties of a BigNum instance: the number of modulus bits and the Barret reduction parameter `k` (TODO: these two values should be the same?!) - -`BigNumInstance` is a generator type that is used to create `BigNum` objects and evaluate operations on `BigNum` objects. It wraps BigNum parameters that may not be known at compile time (the `modulus` and a reduction parameter required for Barret reductions (`redc_param`)) - -The `BigNum` struct represents individual big numbers. - -BigNumInstance parameters (`modulus`, `redc_param`) can be provided at runtime via witnesses (e.g. RSA verification). The `redc_param` is only used in unconstrained functions and does not need to be derived from `modulus` in-circuit. - # Usage -Example usage: +Example usage ```rust use crate::bignum::fields::bn254Fq::BNParams; use crate::bignum::fields::BN254Instance; use crate::bignum::BigNum; -use crate::bignum::BigNumInstance; +use crate::bignum::runtime_bignum::BigNumInstance; type Fq = BigNum<3, BNParams>; -type FqInst = BigNumInstance<3, BNParams>; -fn example(Fq a, Fq b) -> Fq { - let instance = BN254Instance(); - instance.mul(a, b) +fn example_mul(Fq a, Fq b) -> Fq { + a * b } -``` - -Basic expressions can be evaluated using `BigNumInstance::add, BigNumInstance::sub, BigNumInstance::mul`. However, when evaluating relations (up to degree 2) that are more complex than single operations, the function `BigNumInstance::evaluate_quadratic_expression` is more efficient (due to needing only a single modular reduction). - -Unconstrained functions `__mulmod, __addmod, __submod, __divmod, __powmod` can be used to compute witnesses that can then be fed into `BigNumInstance::evaluate_quadratic_expression`. - -See `bignum_test.nr` for examples. -Note: `__divmod`, `__powmod` and `div` are expensive due to requiring modular exponentiations during witness computation. It is worth modifying witness generation algorithms to minimize the number of modular exponentiations required. (for example, using batch inverses) +fn example_ecc_double(Fq x, Fq y) -> (Fq, Fq) { + // Step 1: construct witnesses + // lambda = 3*x*x / 2y + let mut lambda_numerator = x.__mulmod(x); + lambda_numerator = lambda_numerator.__addmod(lambda_numerator.__addmod(lambda_numerator)); + let lambda_denominator = y.__addmod(y); + let lambda = lambda_numerator / lambda_denominator; + // x3 = lambda * lambda - x - x + let x3 = lambda.__mulmod(lambda).__submod(x.__addmod(x)); + // y3 = lambda * (x - x3) - y + let y3 = lambda.__mulmod(x.__submod(x3)).__submod(y); + + // Step 2: constrain witnesses to be correct using minimal number of modular reductions (3) + // 2y * lambda - 3*x*x = 0 + BigNum::evaluate_quadratic_expression( + [[lambda]], + [[false]], + [[y,y]], + [[false, false]], + [x,x,x], + [true, true, true] + ); + // lambda * lambda - x - x - x3 = 0 + BigNum::evaluate_quadratic_expression( + [[lambda]], + [[false]], + [[lambda]], + [[false]], + [x3,x,x], + [true, true, true] + ); + // lambda * (x - x3) - y = 0 + BigNum::evaluate_quadratic_expression( + [[lambda]], + [[false]], + [[x, x3]], + [[false, true]], + [y], + [true] + ); + (x3, y3) +} +``` ### `evaluate_quadratic_expression` @@ -81,9 +102,58 @@ let rhs_terms = [[c], [f]]; let rhs_flags = [[false], [false]]; let add_terms = [g]; let add_flags = [true]; -instance.evaluate_quadratic_expresson(lhs_terms, lhs_flags, rhs_terms, rhs_flags, linear_terms, linear_flags); +BigNum::evaluate_quadratic_expresson(lhs_terms, lhs_flags, rhs_terms, rhs_flags, linear_terms, linear_flags); ``` +# BigNum and runtime_bignum::BigNum + +BigNum members are represented as arrays of 120-bit limbs. The number of 120-bit limbs required to represent a given BigNum object must be defined at compile-time. + +If your field moduli is _also_ known at compile-time, use the `BigNumTrait` definition in `lib.nr` + +If your field moduli is _not_ known at compile-time (e.g. RSA verification), use the traits and structs defined in `runtime_bignum`: `runtime_bignum::BigNumTrait` and `runtime_bignum::BigNumInstanceTrait` + +A `runtime_bignum::BigNumInstance` wraps the bignum modulus (as well as a derived parameter used internally to perform Barret reductions). A `BigNumInstance` object is required to evaluate most bignum operations. + +# runtime_bignum.nr Types + +bignum operations are evaluated using two structs and a trait: `ParamsTrait`, `BigNum`, `BigNumInstance` + +`ParamsTrait` defines the compile-time properties of a BigNum instance: the number of modulus bits and the Barret reduction parameter `k` (TODO: these two values should be the same?!) + +`BigNumInstance` is a generator type that is used to create `BigNum` objects and evaluate operations on `BigNum` objects. It wraps BigNum parameters that may not be known at compile time (the `modulus` and a reduction parameter required for Barret reductions (`redc_param`)) + +The `BigNum` struct represents individual big numbers. + +BigNumInstance parameters (`modulus`, `redc_param`) can be provided at runtime via witnesses (e.g. RSA verification). The `redc_param` is only used in unconstrained functions and does not need to be derived from `modulus` in-circuit. + +# Usage (runtime_bignum) + +Example usage: + +```rust +use crate::bignum::fields::bn254Fq::BNParams; +use crate::bignum::fields::BN254Instance; +use crate::bignum::BigNum; +use crate::bignum::runtime_bignum::BigNumInstance; + +type Fq = BigNum<3, BNParams>; +type FqInst = BigNumInstance<3, BNParams>; + +fn example(Fq a, Fq b) -> Fq { + let instance = BN254Instance(); + instance.mul(a, b) +} +``` + +Basic expressions can be evaluated using `BigNumInstance::add, BigNumInstance::sub, BigNumInstance::mul`. However, when evaluating relations (up to degree 2) that are more complex than single operations, the function `BigNumInstance::evaluate_quadratic_expression` is more efficient (due to needing only a single modular reduction). + +Unconstrained functions `__mulmod, __addmod, __submod, __divmod, __powmod` can be used to compute witnesses that can then be fed into `BigNumInstance::evaluate_quadratic_expression`. + +See `bignum_test.nr` for examples. + +Note: `__divmod`, `__powmod` and `div` are expensive due to requiring modular exponentiations during witness computation. It is worth modifying witness generation algorithms to minimize the number of modular exponentiations required. (for example, using batch inverses) + ### Computing witness values When computing inputs to `evaluate_quadratic_expresson` , unconstrained functions `__addmod`, `__submod`, `__mulmod`, `__divmod` can be used to compute witness values. @@ -107,4 +177,6 @@ For other moduli (e.g. those used in RSA verification), both `modulus` and `redc `redc_param` is equal to `(1 << (2 * Params::modulus_bits())) / modulus` . This must be computed outside of the circuit and provided either as a private witness or hardcoded constant. (computing it via an unconstrained function would be very expensive until noir witness computation times improve) +`double_modulus` is derived via the method `compute_double_modulus` in `runtime_bignum.nr`. If you want to provide this value as a compile-time constant (see `fields/bn254Fq.nr` for an example), follow the algorithm `compute_double_modulus` as this parameter is _not_ structly 2 \* modulus. Each limb except the most significant limb borrows 2^120 from the next most significant limb. This ensure that when performing limb subtractions `double_modulus.limbs[i] - x.limbs[i]`, we know that the result will not underflow. + For example derivations see `https://github.com/noir-lang/noir_rsa` diff --git a/src/bignum_test.nr b/src/bignum_test.nr index a73f5f7..ed8b78a 100644 --- a/src/bignum_test.nr +++ b/src/bignum_test.nr @@ -1,12 +1,15 @@ use crate::utils::arrayX::ArrayX; use crate::BigNum; -use crate::BigNumInstance; -use crate::BigNumParamsTrait; +use crate::runtime_bignum::BigNumParamsTrait as RuntimeBigNumParamsTrait; +use crate::BigNumParamsTrait as BigNumParamsTrait; + use crate::utils::u60_representation::U60Repr; use crate::fields::bn254Fq::BNParams as BNParams; -use crate::fields::BN254Instance; -struct Test2048Params {} -impl BigNumParamsTrait<18> for Test2048Params { +use crate::runtime_bignum::BigNumInstance; +use crate::BigNumTrait; + +struct Test2048Params{} +impl RuntimeBigNumParamsTrait<18> for Test2048Params { fn k() -> u64 { 2048 } @@ -14,8 +17,8 @@ impl BigNumParamsTrait<18> for Test2048Params { 2048 } } - -fn get_2048_BN_instance() -> BigNumInstance<18, Test2048Params> { +impl BigNumParamsTrait<18> for Test2048Params { + fn get_instance() -> BigNumInstance<18, Self> { let modulus: [Field; 18] = [ 0x0000000000000000000000000000000000c0a197a5ae0fcdceb052c9732614fe, 0x0000000000000000000000000000000000656ae034423283422243918ab83be3, @@ -77,392 +80,460 @@ fn get_2048_BN_instance() -> BigNumInstance<18, Test2048Params> { 0x0000000000000000000000000000000000000000000000000000000000000162 ]; BigNumInstance::new(modulus, redc_param) + } + // fn modulus() -> [Field; 18] {[ + // 0x0000000000000000000000000000000000c0a197a5ae0fcdceb052c9732614fe, + // 0x0000000000000000000000000000000000656ae034423283422243918ab83be3, + // 0x00000000000000000000000000000000006bf590da48a7c1070b7d5aabaac678, + // 0x00000000000000000000000000000000000cce39f530238b606f24b296e2bda9, + // 0x000000000000000000000000000000000001e1fef9bb9c1c3ead98f226f1bfa0, + // 0x0000000000000000000000000000000000ad8c1c816e12e0ed1379055e373abf, + // 0x0000000000000000000000000000000000cebe80e474f753aa9d1461c435123d, + // 0x0000000000000000000000000000000000aee5a18ceedef88d115a8b93c167ad, + // 0x0000000000000000000000000000000000268ba83c4a65c4307427fc495d9e44, + // 0x0000000000000000000000000000000000dd2777926848667b7df79f342639d4, + // 0x0000000000000000000000000000000000f455074c96855ca0068668efe7da3d, + // 0x00000000000000000000000000000000005ddba6b30bbc168bfb3a1225f27d65, + // 0x0000000000000000000000000000000000591fec484f36707524133bcd6f4258, + // 0x000000000000000000000000000000000059641b756766aeebe66781dd01d062, + // 0x000000000000000000000000000000000058bc5eaff4b165e142bf9e2480eebb, + // 0x0000000000000000000000000000000000667a3964f08e06df772ce64b229a72, + // 0x00000000000000000000000000000000009c1fdb18907711bfe3e3c1cf918395, + // 0x00000000000000000000000000000000000000000000000000000000000000b8 + // ]} + // fn double_modulus() -> [Field; 18] {[ + // 0x000000000000000000000000000000000181432f4b5c1f9b9d60a592e64c29fc, + // 0x0000000000000000000000000000000001cad5c06884650684448723157077c6, + // 0x0000000000000000000000000000000001d7eb21b4914f820e16fab557558cef, + // 0x0000000000000000000000000000000001199c73ea604716c0de49652dc57b51, + // 0x000000000000000000000000000000000103c3fdf37738387d5b31e44de37f3f, + // 0x00000000000000000000000000000000015b183902dc25c1da26f20abc6e757d, + // 0x00000000000000000000000000000000019d7d01c8e9eea7553a28c3886a247a, + // 0x00000000000000000000000000000000015dcb4319ddbdf11a22b5172782cf5a, + // 0x00000000000000000000000000000000014d17507894cb8860e84ff892bb3c88, + // 0x0000000000000000000000000000000001ba4eef24d090ccf6fbef3e684c73a7, + // 0x0000000000000000000000000000000001e8aa0e992d0ab9400d0cd1dfcfb47a, + // 0x0000000000000000000000000000000001bbb74d6617782d17f674244be4faca, + // 0x0000000000000000000000000000000001b23fd8909e6ce0ea4826779ade84af, + // 0x0000000000000000000000000000000001b2c836eacecd5dd7cccf03ba03a0c3, + // 0x0000000000000000000000000000000001b178bd5fe962cbc2857f3c4901dd75, + // 0x0000000000000000000000000000000001ccf472c9e11c0dbeee59cc964534e3, + // 0x0000000000000000000000000000000001383fb63120ee237fc7c7839f230729, + // 0x0000000000000000000000000000000000000000000000000000000000000170 + // ]} + // fn redc_param() -> [Field; 18] {[ + // 0x000000000000000000000000000000000091697def7100cd5cf8d890b4ef2ec3, + // 0x00000000000000000000000000000000006765ba8304214dac764d3f4adc3185, + // 0x000000000000000000000000000000000048404bd14d927ea230e60d4bebf940, + // 0x00000000000000000000000000000000007c4d53a23bacc251ecbfc4b7ba5a0b, + // 0x000000000000000000000000000000000093eaf3499474a6f5b2fff83f1259c8, + // 0x00000000000000000000000000000000005bff4c737b97281f1a5f2384a8c16d, + // 0x000000000000000000000000000000000061b4cf2f55358476b5323782999055, + // 0x00000000000000000000000000000000001e7a804e8eacfe3a2a5673bc3885b8, + // 0x0000000000000000000000000000000000eabadeae4282906c817adf70eab4ae, + // 0x0000000000000000000000000000000000166f7df257fe2bf27f0809aceed9b0, + // 0x00000000000000000000000000000000007d90fb7428901b8bed11f6b81e36bf, + // 0x0000000000000000000000000000000000f36e6ba885c60b7024c563605df7e0, + // 0x000000000000000000000000000000000052b7c58d2fb5d2c8478963ae6d4a44, + // 0x000000000000000000000000000000000036ee761de26635f114ccc3f7d74f85, + // 0x0000000000000000000000000000000000e3fb726a10cf2220897513f05243de, + // 0x0000000000000000000000000000000000f43a26bbd732496eb4d828591b8056, + // 0x0000000000000000000000000000000000ff4e42304e60fb3a54fca735499f2c, + // 0x0000000000000000000000000000000000000000000000000000000000000162 + // ]} + // fn k() -> u64 { + // 2048 + // } + // fn modulus_bits() -> u64 { + // 2048 + // } } type Fq = BigNum<3, BNParams>; -type FqInstance = BigNumInstance<3, BNParams>; type Fqq = BigNum<18, Test2048Params>; -type FqqInstance = BigNumInstance<18, Test2048Params>; - -// fn main(x: Field, y: pub Field) { -// let z = x + y; -// let a = z * z; -// let b = a * a; - -// let mut A: Fqq= bn.__derive_from_seed([a as u8]); -// let mut B: Fqq= bn.__derive_from_seed([b as u8]); -// let mut C: Fqq= bn.__derive_from_seed([(b * b) as u8]); -// let mut D: Fqq= bn.__derive_from_seed([(b + a) as u8]); - -// let X = A * B; -// let predicate = x == y; - -// // 1872 gates -// let mut result: Fqq = BigNum::new(); -// if (predicate) { -// result = A * B; -// } else { -// result = C * D; -// } - -// // 1152 gates -// let mut L: [Fqq; 2] = [A, C]; -// let mut R: [Fqq; 2] = [B, D]; -// let idx = predicate as Field; -// let result = L[idx] * R[idx]; - -// // 7429 - 6588 = 841 -// let result = A * C; -// println(f"{result}"); -// // println(f"{R}"); -// // let mut lhs2 = BNExpressionInput::new(lhs, false); -// // let rhs2= BNExpressionInput::new(rhs, false); -// // for _ in 0..1 { -// // let out: Fqq = lhs2.value.__mulmod(rhs2.value); -// // out.validate_in_range(); -// // let rem2 = BNExpressionInput::new(out, true); -// // BigNum::evaluate_quadratic_expression([[lhs2]], [[rhs2]], [rem2]); -// // lhs2.value = out; -// // } -// // 68? + +// // // fn main(x: Field, y: pub Field) { +// // // let z = x + y; +// // // let a = z * z; +// // // let b = a * a; + +// // // let mut A: Fqq= BigNum::__derive_from_seed([a as u8]); +// // // let mut B: Fqq= BigNum::__derive_from_seed([b as u8]); +// // // let mut C: Fqq= BigNum::__derive_from_seed([(b * b) as u8]); +// // // let mut D: Fqq= BigNum::__derive_from_seed([(b + a) as u8]); + +// // // let X = A * B; +// // // let predicate = x == y; + +// // // // 1872 gates +// // // let mut result: Fqq = BigNum::new(); +// // // if (predicate) { +// // // result = A * B; +// // // } else { +// // // result = C * D; +// // // } + +// // // // 1152 gates +// // // let mut L: [Fqq; 2] = [A, C]; +// // // let mut R: [Fqq; 2] = [B, D]; +// // // let idx = predicate as Field; +// // // let result = L[idx] * R[idx]; + +// // // // 7429 - 6588 = 841 +// // // let result = A * C; +// // // println(f"{result}"); +// // // // println(f"{R}"); +// // // // let mut lhs2 = BNExpressionInput::new(lhs, false); +// // // // let rhs2= BNExpressionInput::new(rhs, false); +// // // // for _ in 0..1 { +// // // // let out: Fqq = lhs2.value.__mulmod(rhs2.value); +// // // // out.validate_in_range(); +// // // // let rem2 = BNExpressionInput::new(out, true); +// // // // BigNum::evaluate_quadratic_expression([[lhs2]], [[rhs2]], [rem2]); +// // // // lhs2.value = out; +// // // // } +// // // // 68? +// // // } + +fn test_eq(_: BigNum, __: [Field; N]) where BigNum: BigNumTrait { + let a = BigNum::__derive_from_seed([1, 2, 3, 4]); + let b = BigNum::__derive_from_seed([1, 2, 3, 4]); + let c = BigNum::__derive_from_seed([2, 2, 3, 4]); + + let modulus = BigNum::modulus(); + let t0: U60Repr = (U60Repr::from(modulus.get().as_array())); + let t1: U60Repr = (U60Repr::from(b.get().as_array())); + let b_plus_modulus: BigNum = BigNum::from(U60Repr::into(t0 + t1).as_slice()); + assert(a.eq(b) == true); + assert(a.eq(b_plus_modulus) == true); + assert(c.eq(b) == false); + assert(c.eq(a) == false); +} + +// fn test_eq(_: BigNum) where Params: BigNumParamsTrait + RuntimeBigNumParamsTrait { +// let a = BigNum::__derive_from_seed([1, 2, 3, 4]); +// let b = BigNum::__derive_from_seed([1, 2, 3, 4]); +// let c = BigNum::__derive_from_seed([2, 2, 3, 4]); + +// let modulus: BigNum = Params::get_instance().modulus(); +// let t0: U60Repr = (U60Repr::from(modulus.limbs)); +// let t1: U60Repr = (U60Repr::from(b.limbs)); +// let b_plus_modulus: BigNum = BigNum { limbs: U60Repr::into(t0 + t1) }; + +// assert(a.eq(b) == true); +// assert(a.eq(b_plus_modulus) == true); +// assert(c.eq(b) == false); +// assert(c.eq(a) == false); // } -fn test_eq(BNInstance: BigNumInstance) where Params: BigNumParamsTrait { - let a: BigNum = BNInstance.__derive_from_seed([1, 2, 3, 4]); - let b: BigNum = BNInstance.__derive_from_seed([1, 2, 3, 4]); - let c: BigNum = BNInstance.__derive_from_seed([2, 2, 3, 4]); - - let modulus: BigNum = BNInstance.modulus(); - let t0: U60Repr = (U60Repr::from(modulus.limbs)); - let t1: U60Repr = (U60Repr::from(b.limbs)); - let b_plus_modulus: BigNum = BigNum { limbs: U60Repr::into(t0 + t1) }; - - assert(BNInstance.eq(a, b) == true); - assert(BNInstance.eq(a, b_plus_modulus) == true); - assert(BNInstance.eq(c, b) == false); - assert(BNInstance.eq(c, a) == false); -} - -// 98760 -// 99689 -// 929 gates for a 2048 bit mul -fn test_mul(BNInstance: BigNumInstance) where Params: BigNumParamsTrait { - let a: BigNum = BNInstance.__derive_from_seed([1, 2, 3, 4]); - let b: BigNum = BNInstance.__derive_from_seed([4, 5, 6, 7]); - - let c = BNInstance.mul(BNInstance.add(a, b), BNInstance.add(a, b)); - let d = BNInstance.add( - BNInstance.add( - BNInstance.add(BNInstance.mul(a, a), BNInstance.mul(b, b)), - BNInstance.mul(a, b) - ), - BNInstance.mul(a, b) - ); - assert(BNInstance.eq(c, d)); +// // // 98760 +// // // 99689 +// // // 929 gates for a 2048 bit mul +fn test_mul(_: BigNum) where BigNum: BigNumTrait + std::ops::Mul + std::ops::Add { + let a: BigNum = BigNum::__derive_from_seed([1, 2, 3, 4]); + let b: BigNum = BigNum::__derive_from_seed([4, 5, 6, 7]); + + let c = (a + b) * (a + b); + let d = (a * a) + (b * b) + (a * b) + (a * b); + assert(c.eq(d)); } -fn test_add(bn: BigNumInstance) where Params: BigNumParamsTrait { - let a: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); - let b: BigNum = bn.__derive_from_seed([4, 5, 6, 7]); - let one: BigNum = BigNum::one(); +fn test_add(_: BigNum) where BigNum: BigNumTrait + std::ops::Add + std::ops::Mul + std::cmp::Eq { + let a = BigNum::__derive_from_seed([1, 2, 3, 4]); + let b: BigNum = BigNum::__derive_from_seed([4, 5, 6, 7]); + let one = BigNum::one(); a.validate_in_range(); - bn.validate_in_field(a); + a.validate_in_field(); b.validate_in_range(); - bn.validate_in_field(b); - - let mut c = bn.add(a, b); - c = bn.add(c, c); - let d = bn.mul(bn.add(a, b), bn.add(one, one)); - assert(bn.eq(c, d)); - - let e = bn.add(one, one); - for i in 1..N { - assert(e.limbs[i] == 0); + b.validate_in_field(); + let mut c = (a + b); + c += c; + let d = (a + b) * (one + one); + assert(c == (d)); + let e = one + one; + let limbs = e.get(); + let mut first: bool = true; + for limb in limbs { + if first { + first = false; + assert(limb == 2); + } else { + assert(limb == 0); + } } - assert(e.limbs[0] == 2); } -fn test_div(bn: BigNumInstance) where Params: BigNumParamsTrait { - let a: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); - let b: BigNum = bn.__derive_from_seed([4, 5, 6, 7]); +fn test_div(_: BigNum) where BigNum: BigNumTrait + std::ops::Div + std::ops::Mul + std::ops::Add + std::cmp::Eq { + let a = BigNum::__derive_from_seed([1, 2, 3, 4]); + let b = BigNum::__derive_from_seed([4, 5, 6, 7]); - let c = bn.div(a, b); - assert(bn.eq(bn.mul(b, c), a)); + let c = a / b; + assert((b * c) == (a)); } -fn test_invmod(bn: BigNumInstance) where Params: BigNumParamsTrait { - let u: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); +fn test_invmod(_: BigNum) where BigNum: BigNumTrait + std::cmp::Eq { + let u = BigNum::__derive_from_seed([1, 2, 3, 4]); for _ in 0..1 { - let v = bn.__invmod(u); - let result = bn.__mulmod(u, v); - let expected: BigNum = BigNum::one(); - assert(result.limbs == expected.limbs); + let v = u.__invmod(); + let result = u.__mulmod(v); + let expected = BigNum::one(); + assert(result == expected); } } -fn assert_is_not_equal(bn: BigNumInstance) where Params: BigNumParamsTrait { - let a: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); - let b: BigNum = bn.__derive_from_seed([4, 5, 6, 7]); +fn assert_is_not_equal(_: BigNum) where BigNum: BigNumTrait { + let a = BigNum::__derive_from_seed([1, 2, 3, 4]); + let b = BigNum::__derive_from_seed([4, 5, 6, 7]); - bn.assert_is_not_equal(a, b); + a.assert_is_not_equal(b); } -fn assert_is_not_equal_fail(bn: BigNumInstance) where Params: BigNumParamsTrait { - let a: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); - let b: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); +fn assert_is_not_equal_fail(_: BigNum) where BigNum: BigNumTrait { + let a = BigNum::__derive_from_seed([1, 2, 3, 4]); + let b = BigNum::__derive_from_seed([1, 2, 3, 4]); - bn.assert_is_not_equal(a, b); + a.assert_is_not_equal(b); } -fn assert_is_not_equal_overloaded_lhs_fail(bn: BigNumInstance) where Params: BigNumParamsTrait { - let a: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); - let b: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); +fn assert_is_not_equal_overloaded_lhs_fail(_: BigNum, __: [Field; N]) where BigNum: BigNumTrait { + let a = BigNum::__derive_from_seed([1, 2, 3, 4]); + let b = BigNum::__derive_from_seed([1, 2, 3, 4]); - let modulus = bn.modulus(); + let modulus = BigNum::modulus(); - let t0: U60Repr = U60Repr::from(a.limbs); - let t1: U60Repr = U60Repr::from(modulus.limbs); - let a_plus_modulus: BigNum = BigNum { limbs: U60Repr::into(t0 + t1) }; - bn.assert_is_not_equal(a_plus_modulus, b); + let t0: U60Repr = U60Repr::from(a.get().as_array()); + let t1: U60Repr = U60Repr::from(modulus.get().as_array()); + let a_plus_modulus: BigNum = BigNum::from(U60Repr::into(t0 + t1).as_slice()); + a_plus_modulus.assert_is_not_equal(b); } -fn assert_is_not_equal_overloaded_rhs_fail(bn: BigNumInstance) where Params: BigNumParamsTrait { - let a: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); - let b: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); +fn assert_is_not_equal_overloaded_rhs_fail(_: BigNum, __: [Field; N]) where BigNum: BigNumTrait { + let a = BigNum::__derive_from_seed([1, 2, 3, 4]); + let b = BigNum::__derive_from_seed([1, 2, 3, 4]); - let modulus = bn.modulus(); + let modulus = BigNum::modulus(); - let t0: U60Repr = U60Repr::from(b.limbs); - let t1: U60Repr = U60Repr::from(modulus.limbs); - let b_plus_modulus: BigNum = BigNum { limbs: U60Repr::into(t0 + t1) }; - bn.assert_is_not_equal(a, b_plus_modulus); + let t0: U60Repr = U60Repr::from(b.get().as_array()); + let t1: U60Repr = U60Repr::from(modulus.get().as_array()); + let b_plus_modulus: BigNum = BigNum::from(U60Repr::into(t0 + t1).as_slice()); + a.assert_is_not_equal(b_plus_modulus); } -fn assert_is_not_equal_overloaded_fail(bn: BigNumInstance) where Params: BigNumParamsTrait { - let a: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); - let b: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); +fn assert_is_not_equal_overloaded_fail(_: BigNum, __: [Field; N]) where BigNum: BigNumTrait { + let a = BigNum::__derive_from_seed([1, 2, 3, 4]); + let b = BigNum::__derive_from_seed([1, 2, 3, 4]); - let modulus = bn.modulus(); + let modulus = BigNum::modulus(); - let t0: U60Repr = U60Repr::from(a.limbs); - let t1: U60Repr = U60Repr::from(b.limbs); - let t2: U60Repr = U60Repr::from(modulus.limbs); - let a_plus_modulus: BigNum = BigNum { limbs: U60Repr::into(t0 + t2) }; - let b_plus_modulus: BigNum = BigNum { limbs: U60Repr::into(t1 + t2) }; - bn.assert_is_not_equal(a_plus_modulus, b_plus_modulus); + let t0: U60Repr = U60Repr::from(a.get().as_array()); + let t1: U60Repr = U60Repr::from(b.get().as_array()); + let t2: U60Repr = U60Repr::from(modulus.get().as_array()); + let a_plus_modulus: BigNum = BigNum::from(U60Repr::into(t0 + t2).as_slice()); + let b_plus_modulus: BigNum = BigNum::from(U60Repr::into(t1 + t2).as_slice()); + a_plus_modulus.assert_is_not_equal(b_plus_modulus); } #[test] fn test_eq_BN() { - let instance = BN254Instance(); - test_eq(instance); + let stub: BigNum<3, BNParams> = BigNum::new(); + test_eq(stub, [0; 3]); } #[test] fn test_add_BN() { - let instance = BN254Instance(); - - let mut a: Fq = instance.modulus(); - let mut b: Fq = instance.modulus(); - let mut expected: Fq = instance.modulus(); + let mut a: Fq = BigNum::modulus(); + let mut b: Fq = BigNum::modulus(); + let mut expected: Fq = BigNum::modulus(); a.limbs[0] -= 1; b.limbs[0] -= 1; expected.limbs[0] -= 2; - let result = instance.add(a, b); + let result = a.add(b); println(f"result = {result}"); - assert(instance.eq(result, expected)); + assert(result.eq(expected)); } #[test] fn test_sub_test_BN() { - let instance = BN254Instance(); // 0 - 1 should equal p - 1 let mut a: Fq = BigNum::new(); let mut b: Fq = BigNum::one(); - let mut expected: Fq = instance.modulus(); + let mut expected: Fq = BigNum::modulus(); expected.limbs[0] -= 1; // p - 1 - let result = instance.sub(a, b); - assert(instance.eq(result, expected)); + let result = a.sub(b); + assert(result.eq(expected)); } #[test] fn test_sub_modulus_limit() { - let instance = BN254Instance(); // if we underflow, maximum result should be ... // 0 - 1 = o-1 // 0 - p = 0 let mut a: Fq = BigNum::new(); - let mut b: Fq = instance.modulus(); - let mut expected: Fq = BigNum::new(); + let mut b: Fq = BigNum::modulus(); + let mut expected = BigNum::new(); - let result = instance.sub(a, b); - assert(instance.eq(result, expected)); + let result = a.sub(b); + assert(result.eq(expected)); } #[test(should_fail_with = "call to assert_max_bit_size")] fn test_sub_modulus_underflow() { - let instance = BN254Instance(); - // 0 - (p + 1) is smaller than p and should produce unsatisfiable constraints let mut a: Fq = BigNum::new(); - let mut b: Fq = instance.modulus(); + let mut b: Fq = BigNum::modulus(); b.limbs[0] += 1; - let mut expected: Fq = BigNum::one(); - - let result = instance.sub(a, b); + let mut expected = BigNum::one(); - assert(instance.eq(result, expected)); + let result = a.sub(b); + assert(result.eq(expected)); } #[test] fn test_add_modulus_limit() { - let instance = BN254Instance(); // p + 2^{254} - 1 should be the maximum allowed value fed into an add operation // when adding, if the result overflows the modulus, we conditionally subtract the modulus, producing 2^{254} - 1 // this is the largest value that will satisfy the range check applied when constructing a bignum - let p : U60Repr<3, 2> = U60Repr::from(instance.modulus().limbs); + let p : U60Repr<3, 2> = U60Repr::from(BNParams::get_instance().modulus().get().as_array()); let two_pow_254_minus_1: U60Repr<3, 2> = U60Repr::from([0xffffffffffffffffffffffffffffff, 0xffffffffffffffffffffffffffffff, 0x3fff]); let a: Fq = BigNum { limbs: U60Repr::into(p) }; let b: Fq = BigNum { limbs: U60Repr::into(two_pow_254_minus_1) }; - let result = instance.add(a, b); - assert(instance.eq(result, b)); + let result = a.add(b); + assert(result.eq(b)); } #[test(should_fail_with = "call to assert_max_bit_size")] fn test_add_modulus_overflow() { - let instance = BN254Instance(); //(2^{254} - 1) + (p - 1) = 2^{254} + p // after subtracting modulus, result is 2^{254} will does not satisfy the range check applied when constructing a BigNum - let p : U60Repr<3, 2> = U60Repr::from(instance.modulus().limbs); + let p : U60Repr<3, 2> = U60Repr::from(BNParams::get_instance().modulus().get().as_array()); let two_pow_254_minus_1: U60Repr<3, 2> = U60Repr::from([0xffffffffffffffffffffffffffffff, 0xffffffffffffffffffffffffffffff, 0x3fff]); let one = U60Repr::from([1, 0, 0]); let a: Fq = BigNum { limbs: U60Repr::into(p + one) }; let b: Fq = BigNum { limbs: U60Repr::into(two_pow_254_minus_1) }; - let result = instance.add(a, b); - assert(instance.eq(result, b)); + let result = a.add(b); + assert(result.eq(b)); } #[test] fn test_mul_BN() { - let instance = BN254Instance(); - test_mul(instance); + let stub: BigNum<3, BNParams> = BigNum::new(); + test_mul(stub); } #[test] fn test_add_BN2() { - let instance = BN254Instance(); - test_add(instance); + let stub: BigNum<3, BNParams> = BigNum::new(); + test_add(stub); } #[test] fn test_div_BN() { - let instance = BN254Instance(); - test_div(instance); + let stub: BigNum<3, BNParams> = BigNum::new(); + test_div(stub); } #[test] fn test_invmod_BN() { - let instance = BN254Instance(); - test_invmod(instance); + let stub: BigNum<3, BNParams> = BigNum::new(); + test_invmod(stub); } #[test] fn test_assert_is_not_equal_BN() { - let instance = BN254Instance(); - assert_is_not_equal(instance); + let stub: BigNum<3, BNParams> = BigNum::new(); + assert_is_not_equal(stub); } #[test(should_fail_with = "asssert_is_not_equal fail")] fn test_assert_is_not_equal_fail_BN() { - let instance = BN254Instance(); - assert_is_not_equal_fail(instance); + let stub: BigNum<3, BNParams> = BigNum::new(); + assert_is_not_equal_fail(stub); } #[test(should_fail_with = "asssert_is_not_equal fail")] fn test_assert_is_not_equal_overloaded_lhs_fail_BN() { - let instance = BN254Instance(); - assert_is_not_equal_overloaded_lhs_fail(instance); + let stub: BigNum<3, BNParams> = BigNum::new(); + assert_is_not_equal_overloaded_lhs_fail(stub, [0; 3]); } #[test(should_fail_with = "asssert_is_not_equal fail")] fn test_assert_is_not_equal_overloaded_rhs_fail_BN() { - let instance = BN254Instance(); - assert_is_not_equal_overloaded_rhs_fail(instance); + let stub: BigNum<3, BNParams> = BigNum::new(); + assert_is_not_equal_overloaded_rhs_fail(stub, [0; 3]); } #[test(should_fail_with = "asssert_is_not_equal fail")] fn test_assert_is_not_equal_overloaded_fail_BN() { - let instance = BN254Instance(); - assert_is_not_equal_overloaded_fail(instance); + let stub: BigNum<3, BNParams> = BigNum::new(); + assert_is_not_equal_overloaded_fail(stub, [0; 3]); } #[test] fn test_eq_2048() { - let instance = get_2048_BN_instance(); - test_eq(instance); + let stub: BigNum<18, Test2048Params> = BigNum::new(); + test_eq(stub, [0; 18]); } #[test] fn test_mul_2048() { - let instance = get_2048_BN_instance(); - test_mul(instance); + let stub: BigNum<18, Test2048Params> = BigNum::new(); + test_mul(stub); } #[test] fn test_add_2048() { - let instance = get_2048_BN_instance(); - test_add(instance); + let stub: BigNum<18, Test2048Params> = BigNum::new(); + test_add(stub); } #[test] fn test_assert_is_not_equal_2048() { - let instance = get_2048_BN_instance(); - assert_is_not_equal(instance); + let stub: BigNum<18, Test2048Params> = BigNum::new(); + assert_is_not_equal(stub); } #[test(should_fail_with = "asssert_is_not_equal fail")] fn test_assert_is_not_equal_fail_2048() { - let instance = get_2048_BN_instance(); - assert_is_not_equal_fail(instance); + let stub: BigNum<18, Test2048Params> = BigNum::new(); + assert_is_not_equal_fail(stub); } #[test(should_fail_with = "asssert_is_not_equal fail")] fn test_assert_is_not_equal_overloaded_lhs_fail_2048() { - let instance = get_2048_BN_instance(); - assert_is_not_equal_overloaded_lhs_fail(instance); + let stub: BigNum<18, Test2048Params> = BigNum::new(); + assert_is_not_equal_overloaded_lhs_fail(stub, [0; 18]); } #[test(should_fail_with = "asssert_is_not_equal fail")] fn test_assert_is_not_equal_overloaded_rhs_fail_2048() { - let instance = get_2048_BN_instance(); - assert_is_not_equal_overloaded_rhs_fail(instance); + let stub: BigNum<18, Test2048Params> = BigNum::new(); + assert_is_not_equal_overloaded_rhs_fail(stub, [0; 18]); } #[test(should_fail_with = "asssert_is_not_equal fail")] fn test_assert_is_not_equal_overloaded_fail_2048() { - let instance = get_2048_BN_instance(); - assert_is_not_equal_overloaded_fail(instance); + let stub: BigNum<18, Test2048Params> = BigNum::new(); + assert_is_not_equal_overloaded_fail(stub, [0; 18]); } -// N.B. witness generation times make these tests take ~15 minutes each! Uncomment at your peril +// // N.B. witness generation times make these tests take ~15 minutes each! Uncomment at your peril // #[test] // fn test_div_2048() { -// let instance = get_2048_BN_instance(); -// test_div(instance); +// let stub: BigNum<18, Test2048Params> = BigNum::new(); +// test_div(stub); // } -// N.B. witness generation times make these tests take ~15 minutes each! Uncomment at your peril +// // N.B. witness generation times make these tests take ~15 minutes each! Uncomment at your peril // #[test] // fn test_invmod_2048() { -// let instance = get_2048_BN_instance(); -// test_invmod(instance); +// let stub: BigNum<18, Test2048Params> = BigNum::new(); +// test_invmod(stub); // } #[test] fn test_2048_bit_quadratic_expression() { - let instance = get_2048_BN_instance(); let a: [Field; 18] = [ 0x000000000000000000000000000000000083684820ff40795b8d9f1be2220cba, 0x0000000000000000000000000000000000d4924fbdc522b07b6cd0ef5508fd66, @@ -526,17 +597,16 @@ fn test_2048_bit_quadratic_expression() { let a_bn: BigNum<18, Test2048Params> = BigNum { limbs: a }; let b_bn: BigNum<18, Test2048Params> = BigNum { limbs: b }; - let c_bn = instance.__mulmod(a_bn, b_bn); + let c_bn = a_bn.__mulmod(b_bn); assert(c_bn.limbs == c_expected); a_bn.validate_in_range(); - instance.evaluate_quadratic_expression([[a_bn]], [[false]], [[b_bn]], [[false]], [c_bn], [true]); + BigNum::evaluate_quadratic_expression([[a_bn]], [[false]], [[b_bn]], [[false]], [c_bn], [true]); } #[test] fn test_expressions() { - let instance = BN254Instance(); let x: [Field; 6] = [ 0x000000000000000000000000000000000083684820ff40795b8d9f1be2220cba, 0x0000000000000000000000000000000000d4924fbdc522b07b6cd0ef5508fd66, 0x0000000000000000000000000000000000d48f6c43c5930f3d70d6db09a48f4a, 0x0000000000000000000000000000000000e7f72b2c0756704bea85be38352b34, 0x00000000000000000000000000000000000000000000000000000000b05d5ac5, 0 @@ -556,7 +626,7 @@ fn test_expressions() { 0x0 ] }; - let yy = instance.__addmod(y, y); + let yy = y.__addmod(y); assert(yy.limbs == z.limbs); @@ -588,12 +658,12 @@ fn test_expressions() { 0x0000000000000000000000000000000000000000000000000000000000000f93 ] }; - let wx = instance.__mulmod(w, x); - let uv = instance.__mulmod(uu, vv); - let y = instance.__negate(instance.__addmod(uv, wx)); - let z = instance.__addmod(uv, wx); + let wx = w.__mulmod(x); + let uv = uu.__mulmod(vv); + let y = (uv.__addmod(wx)).__negate(); + let z = uv.__addmod(wx); - instance.evaluate_quadratic_expression( + BigNum::evaluate_quadratic_expression( [[uu], [w]], [[false], [false]], [[vv], [x]], @@ -601,7 +671,7 @@ fn test_expressions() { [z], [true] ); - instance.evaluate_quadratic_expression( + BigNum::evaluate_quadratic_expression( [[uu], [w]], [[false], [false]], [[vv], [x]], @@ -610,6 +680,6 @@ fn test_expressions() { [false] ); - let wx_constrained = instance.mul(w, x); + let wx_constrained = w * x; assert(wx_constrained.limbs == wx.limbs); } diff --git a/src/fields.nr b/src/fields.nr index 7e44b2d..794ba3d 100644 --- a/src/fields.nr +++ b/src/fields.nr @@ -2,7 +2,7 @@ mod bn254Fq; mod bls12381Fr; use crate::BigNum; -use crate::BigNumInstance; +use crate::runtime_bignum::BigNumInstance; use crate::fields::bn254Fq::BNParams; use crate::fields::bls12381Fr::Bls12_381_Fr_Params; diff --git a/src/fields/bls12381Fr.nr b/src/fields/bls12381Fr.nr index 70980e9..109c386 100644 --- a/src/fields/bls12381Fr.nr +++ b/src/fields/bls12381Fr.nr @@ -1,9 +1,11 @@ use crate::BigNum; -use crate::BigNumParamsTrait; +use crate::runtime_bignum::BigNumInstance; +use crate::runtime_bignum::BigNumParamsTrait as RuntimeBigNumParamsTrait; +use crate::BigNumParamsTrait as BigNumParamsTrait; struct Bls12_381_Fr_Params {} -impl BigNumParamsTrait<3> for Bls12_381_Fr_Params { +impl RuntimeBigNumParamsTrait<3> for Bls12_381_Fr_Params { fn k() -> u64 { 255 } @@ -11,3 +13,11 @@ impl BigNumParamsTrait<3> for Bls12_381_Fr_Params { 255 } } + +impl BigNumParamsTrait<3> for Bls12_381_Fr_Params { + fn get_instance() -> BigNumInstance<3, Self> { + let modulus = [0xbda402fffe5bfeffffffff00000001, 0xa753299d7d483339d80809a1d80553, 0x0073ed]; + let redc_param = [0x410fad2f92eb5c509cde80830358e4, 0x253b7fb78ddf0e2d772dc1f823b4d9, 0x008d54]; + BigNumInstance::new(modulus, redc_param) + } +} \ No newline at end of file diff --git a/src/fields/bn254Fq.nr b/src/fields/bn254Fq.nr index f978e9d..d576ed4 100644 --- a/src/fields/bn254Fq.nr +++ b/src/fields/bn254Fq.nr @@ -1,8 +1,41 @@ use crate::BigNum; use crate::BigNumParamsTrait; +use crate::runtime_bignum::BigNumInstance; +use crate::runtime_bignum::BigNumParamsTrait as RuntimeBigNumParamsTrait; + +// struct BNParamsInner {} +// impl RuntimeBigNumParamsTrait<3> for BNParamsInner { +// fn k() -> u64 { +// 255 +// } +// fn modulus_bits() -> u64 { +// 254 +// } +// } struct BNParams {} +impl RuntimeBigNumParamsTrait<3> for BNParams { + fn k() -> u64 { + 255 + } + fn modulus_bits() -> u64 { + 254 + } +} impl BigNumParamsTrait<3> for BNParams { + fn get_instance() -> BigNumInstance<3, Self> { + BigNumInstance { + redc_param: [ + 0x000000000000000000000000000000000059785d9f353021bcebb62866fe4394, 0x0000000000000000000000000000000000d18988e8129eac1d2961a01cc04eba, 0x0000000000000000000000000000000000000000000000000000000000015291 + ], + modulus: [ + 0x0000000000000000000000000000000000816a916871ca8d3c208c16d87cfd47, 0x00000000000000000000000000000000004e72e131a029b85045b68181585d97, 0x0000000000000000000000000000000000000000000000000000000000003064 + ], + double_modulus: [ + 0x000000000000000000000000000000000102d522d0e3951a7841182db0f9fa8e, 0x00000000000000000000000000000000019ce5c263405370a08b6d0302b0bb2e, 0x00000000000000000000000000000000000000000000000000000000000060c7 + ] + } + } // fn redc_param() -> [Field; 3] { // [ // 0x000000000000000000000000000000000059785d9f353021bcebb62866fe4394, 0x0000000000000000000000000000000000d18988e8129eac1d2961a01cc04eba, 0x0000000000000000000000000000000000000000000000000000000000015291 @@ -18,10 +51,10 @@ impl BigNumParamsTrait<3> for BNParams { // 0x000000000000000000000000000000000102d522d0e3951a7841182db0f9fa8e, 0x00000000000000000000000000000000019ce5c263405370a08b6d0302b0bb2e, 0x00000000000000000000000000000000000000000000000000000000000060c7 // ] // } - fn k() -> u64 { - 255 - } - fn modulus_bits() -> u64 { - 254 - } + // fn k() -> u64 { + // 255 + // } + // fn modulus_bits() -> u64 { + // 254 + // } } diff --git a/src/lib.nr b/src/lib.nr index 36c8613..91c9e9a 100644 --- a/src/lib.nr +++ b/src/lib.nr @@ -1,340 +1,169 @@ mod utils; mod bignum_test; mod fields; +mod runtime_bignum; +mod runtime_bignum_test; use dep::std; use crate::utils::u60_representation::U60Repr; use crate::utils::arrayX::ArrayX; use crate::utils::split_bits; +use crate::runtime_bignum::BigNumInstance as RuntimeBigNumInstance; +use crate::runtime_bignum::BigNumInstanceTrait as RuntimeBigNumInstanceTrait; +use crate::runtime_bignum::BigNumParamsTrait as RuntimeBigNumParamsTrait; +use crate::runtime_bignum::BigNumTrait as RuntimeBigNumTrait; -trait BigNumTrait { - fn new() -> Self; - fn one() -> Self; - //fn from_array(limbs: [Field; N]) -> Self; - fn from_byte_be(x: [u8; NBytes]) -> Self; - fn to_le_bytes(val: Self) -> [u8; NBytes]; - fn get(self) -> [Field]; - fn get_limb(self, idx: u64) -> Field; - fn set_limb(&mut self, idx: u64, value: Field); - fn conditional_select(lhs: Self, rhs: Self, predicate: bool) -> Self; - fn validate_in_range(self); - fn validate_quotient_in_range(self); - fn get_modulus_bits() -> u64; - fn num_limbs() -> u64; - fn __is_zero(self) -> bool; - fn __eq(self, rhs: Self) -> bool; -} - -trait BigNumInstanceTrait where BN: BigNumTrait { - // fn new(modulus: [Field; N], redc_param: [Field; N]) -> Self; - // fn __derive_from_seed(self, seed: [u8; SeedBytes]) -> BN; - fn modulus(self) -> BN; - fn __derive_from_seed(self, seed: [u8; SeedBytes]) -> BN; - fn eq(self, lhs: BN, rhs: BN) -> bool; - fn __negate(self, val: BN) -> BN; - fn __addmod(self, lhs: BN, rhs: BN) -> BN; - fn __submod(self, lhs: BN, rhs: BN) -> BN; - fn __mulmod(self, lhs: BN, rhs: BN) -> BN; - fn __divmod(self, lhs: BN, rhs: BN) -> BN; - fn __batch_invert(self, x: [BN; M]) -> [BN; M]; - fn __invmod(self, val: BN) -> BN; - fn __powmod(self, val: BN, exponent: BN) -> BN; - fn __compute_quadratic_expression( - self, - lhs_terms: [[BN; LHS_N]; NUM_PRODUCTS], - lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], - rhs_terms: [[BN; RHS_N]; NUM_PRODUCTS], - rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], - linear_terms: [BN; ADD_N], - linear_flags: [bool; ADD_N] - ) -> (BN, BN); - - fn evaluate_quadratic_expression( - self, - lhs_terms: [[BN; LHS_N]; NUM_PRODUCTS], - lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], - rhs_terms: [[BN; RHS_N]; NUM_PRODUCTS], - rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], - linear_terms: [BN; ADD_N], - linear_flags: [bool; ADD_N] - ); - - fn validate_in_field(self, val: BN); - fn assert_is_not_equal(self, lhs: BN, rhs: BN); - fn neg(self, val: BN) -> BN; - fn add(self, lhs: BN, rhs: BN) -> BN; - fn sub(self, lhs: BN, rhs: BN) -> BN; - fn mul(self, lhs: BN, rhs: BN) -> BN; - fn div(self, lhs: BN, rhs: BN) -> BN; - +struct BigNum { + limbs: [Field; N] } /** * @brief BigNumParamsTrait defines a "field" with which to parametrise BigNum. * @description The "field" does not need to be prime, any value *should* work (TODO: test!) **/ -trait BigNumParamsTrait { +// +// trait BigNumParamsTrait where Params: RuntimeBigNumParamsTrait, RuntimeBigNumInstance: RuntimeBigNumInstanceTrait> { +trait BigNumParamsTrait where Self: RuntimeBigNumParamsTrait { + + fn get_instance() -> RuntimeBigNumInstance where Self: RuntimeBigNumParamsTrait;// ; + /** * @brief k used for __barrett_reduction. Should be at least modulus_bits() + 1 **/ - fn k() -> u64; + fn k() -> u64 { Self::k() } /** * @brief modulus_bits = log2(modulus) rounded up **/ - fn modulus_bits() -> u64; -} - -// add modulus_u60? -struct BigNumInstance { - redc_param: [Field; N], - modulus: [Field; N], - double_modulus: [Field; N], -} - -struct BigNum { - limbs: [Field; N] + fn modulus_bits() -> u64 { Self::modulus_bits() } } -impl BigNumTrait for BigNum where Params: BigNumParamsTrait { - fn new() -> Self { - BigNum { limbs: [0; N] } - } - - fn one() -> BigNum { - let mut result: Self = BigNum { limbs: [0; N] }; - result.limbs[0] = 1; - result - } - - /** - * @brief construct a BigNum instance out of an array of bytes in BIG ENDIAN format - * @description: each 120-bit limb represents 15 bytes, we require that the size of the byte array - * is precisely large enough to cover Params::modulus_bits() - * @param x: input byte array - **/ - fn from_byte_be(x: [u8; NBytes]) -> BigNum { - let num_bits: u64 = NBytes * 8; - let modulus_bits: u64 = Params::modulus_bits(); - assert(num_bits > modulus_bits); - assert(num_bits - modulus_bits < 8); - let mut result = BigNum::new(); - - let excess_bytes = N * 15 - NBytes; - let final_limb_bytes = 15 - excess_bytes; - let mut limb: Field = 0; - let mut k = 0; - for _j in 0..final_limb_bytes { - limb *= 256; - limb += x[k] as Field; - k += 1; - } - result.limbs[N - 1] = limb; - - for i in 1..N { - let mut limb: Field = 0; - for _j in 0..15 { - limb *= 256; - limb += x[k] as Field; - k += 1; - } - result.limbs[N - i - 1] = limb; - } - - // max_bits_in_most_significant_byte should be known at comptime. if not...messy! - let mut max_bits_in_most_significant_byte = num_bits - modulus_bits; - if num_bits == modulus_bits { - max_bits_in_most_significant_byte = 8; - } - - let most_significant_byte: Field = x[NBytes - 1] as Field; - most_significant_byte.assert_max_bit_size(max_bits_in_most_significant_byte as u32); - result - } - - fn to_le_bytes(val: BigNum) -> [u8; NBytes] { - let nbytes = (Params::modulus_bits() / 8) + (Params::modulus_bits() % 8 != 0) as u64; - assert(nbytes <= NBytes); - - let mut result: [u8; NBytes] = [0; NBytes]; - for i in 0..N - 1 { - let limb_bytes = val.limbs[i].to_le_bytes(15); - for j in 0..15 { - result[i * 15 + j] = limb_bytes[j]; - } - } - let last_limb_bytes = val.limbs[N - 1].to_le_bytes(15); - let num_last_bytes = (NBytes - (N - 1) * 15); - for i in 0..num_last_bytes { - result[(N-1) * 15 + i] = last_limb_bytes[i]; - } - result - } - - fn get(self) -> [Field] { - self.limbs - } - fn get_limb(self, idx: u64) -> Field { - self.limbs[idx] - } - fn set_limb(&mut self, idx: u64, value: Field) { - self.limbs[idx] = value; - } - - /** - * @brief conditional_select given the value of `predicate` return either `self` (if 0) or `other` (if 1) - * @description should be cheaper than using an IF statement (TODO: check!) - **/ - fn conditional_select(lhs: Self, rhs: Self, predicate: bool) -> Self { - let mut result: Self = lhs; - for i in 0..N { - result.limbs[i] = (lhs.limbs[i] - rhs.limbs[i]) * predicate as Field + rhs.limbs[i]; - } - result - } - - /** - * @brief Validate a BigNum instance is correctly range constrained to contain no more than Params::modulus_bits() - **/ - fn validate_in_range(self) { - for i in 0..(N - 1) { - self.limbs[i].assert_max_bit_size(120); - } - let final_limb_bits = Params::modulus_bits() - ((N - 1) * 120); - self.limbs[N - 1].assert_max_bit_size(final_limb_bits as u32); - } - - /** - * @brief validate quotient produced from `evaluate_quadratic_expression` is well-formed - * @description because the inputs into evaluate_quadratic_expression may cause the quotient to extend beyond `Params::modulus_bits`. - * We allow the quotient to extend 6 bits beyond Params::modulus_bits() - * Why is this? - * several factors: 1. quotient * modulus , limbs cannot overflow field boundary (254 bits) - * 2. in `evaluate_quadratic_expression`, we require that for `expression - quotient * modulus`, - * limbs cannot exceed 246 bits (246 magic number due to a higher number adding extra range check gates) - * because of factor 2 and the fact that modulus limbs are 120 bits, quotient limbs cannot be >126 bits - * - * Note: doesn't this mean that final_limb_bits should be constrained to be 126 bits, not modulus_bits() - ((N - 1) * 120) + 6? - * TODO: think about this more! we want the range constraint we apply to be as small as allowable as this is more efficient - **/ - fn validate_quotient_in_range(self) { - for i in 0..(N) { - self.limbs[i].assert_max_bit_size(120); - } - // Note: replace magic number 6 with definition - let final_limb_bits = Params::modulus_bits() - ((N - 1) * 120) + 6; - self.limbs[N - 1].assert_max_bit_size(final_limb_bits as u32); - } - - fn get_modulus_bits() -> u64 { - Params::modulus_bits() - } - fn num_limbs() -> u64 { - N - } - - fn __is_zero(self) -> bool { - self.__is_zero_impl() - } - - fn __eq(self, rhs: Self) -> bool { - self.__eq_impl(rhs) - } +trait BigNumTrait where BigNumTrait: std::ops::Add + std::ops::Sub + std::ops::Mul + std::ops::Div + std::ops::Eq + RuntimeBigNumTrait { + // TODO: this crashes the compiler? v0.32 + // fn default() -> Self { std::default::Default::default () } + fn from(limbs: [Field]) -> Self { RuntimeBigNumTrait::from(limbs) } + fn new() -> Self { RuntimeBigNumTrait::new() } + fn one() -> Self { RuntimeBigNumTrait::one() } + fn modulus() -> Self; + fn __derive_from_seed(seed: [u8; SeedBytes]) -> Self; + fn __powmod(self, exponent: Self) -> Self; + fn __negate(self) -> Self; + fn __addmod(self, other: Self) -> Self; + fn __submod(self, other: Self) -> Self; + fn __mulmod(self, other: Self) -> Self; + fn __divmod(self, other: Self) -> Self; + fn __invmod(self) -> Self; + fn __batch_invert(to_invert: [Self; M]) -> [Self; M]; + fn __is_zero(self) -> bool { RuntimeBigNumTrait::__is_zero(self) } + fn __eq(self, other: Self) -> bool { RuntimeBigNumTrait::__eq(self, other) } + fn __compute_quadratic_expression(lhs: [[Self; LHS_N]; NUM_PRODUCTS], lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], rhs: [[Self; RHS_N]; NUM_PRODUCTS], rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], add: [Self; ADD_N], add_flags: [bool; ADD_N]) -> (Self, Self); + fn evaluate_quadratic_expression(lhs: [[Self; LHS_N]; NUM_PRODUCTS], lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], rhs: [[Self; RHS_N]; NUM_PRODUCTS], rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], add: [Self; ADD_N], add_flags: [bool; ADD_N]); + fn validate_in_range(self){ RuntimeBigNumTrait::validate_in_range(self) } + fn validate_in_field(self); + fn assert_is_not_equal(self, other: Self); + fn neg(self) -> Self; + fn add(self, other: Self) -> Self { self + other } + fn sub(self, other: Self) -> Self { self - other } + fn mul(self, other: Self) -> Self { self * other } + fn div(self, other: Self) -> Self { self / other } + fn eq(self, other: Self) -> bool { self == other } + fn get(self) -> [Field] { RuntimeBigNumTrait::get(self) } + fn get_limb(self, idx: u64) -> Field { RuntimeBigNumTrait::get_limb(self, idx) } + fn set_limb(&mut self, idx: u64, value: Field) { RuntimeBigNumTrait::set_limb(self, idx, value) } + fn conditional_select(lhs: Self, rhs: Self, predicate: bool) -> Self { RuntimeBigNumTrait::conditional_select(lhs, rhs, predicate) } + fn to_le_bytes(self) -> [u8; X] { RuntimeBigNumTrait::to_le_bytes(self) } } -impl BigNum where Params: BigNumParamsTrait { - - fn from_array(limbs: [Field; N]) -> BigNum { - BigNum { limbs } - } +impl BigNumTrait for BigNum where Params: BigNumParamsTrait + RuntimeBigNumParamsTrait { - unconstrained fn __is_zero_impl(self) -> bool { - let mut result: bool = true; - for i in 0..N { - result = result & (self.limbs[i] == 0); - } - result + + fn modulus() -> Self { + Params::get_instance().modulus() } - unconstrained fn __eq_impl(lhs: Self, rhs: Self) -> bool { - lhs.limbs == rhs.limbs - } -} + // #################################################################################################################### + // #################################################################################################################### + // ### C O N S T R U C T O R S + // #################################################################################################################### + // #################################################################################################################### + // fn new() -> Self { + // BigNum { limbs: [0; N] } + // } -impl BigNumInstanceTrait> for BigNumInstance where Params: BigNumParamsTrait { + // fn one() -> Self { + // let mut result = BigNum::new(); + // result.limbs[0] = 1; + // result + // } - fn modulus(self) -> BigNum { BigNum{ limbs: self.modulus } } - fn __derive_from_seed(self, seed: [u8; SeedBytes]) -> BigNum { - self.__derive_from_seed_impl(seed) - } // #################################################################################################################### // #################################################################################################################### // ### U N C O N S T R A I N E D F U N C T I O N S // ### NOTE: these functions call unconstrained internal implementations because trait impl modifiers are not supported // #################################################################################################################### // #################################################################################################################### + fn __derive_from_seed(seed: [u8; SeedBytes]) -> Self { + Params::get_instance().__derive_from_seed(seed) + } - fn __negate(self, val: BigNum) -> BigNum { - self.__negate_impl(val) + fn __negate(self) -> Self { + Params::get_instance().__negate(self) } - fn __addmod(self, lhs: BigNum, rhs: BigNum) -> BigNum { - self.__addmod_impl(lhs, rhs) + fn __addmod(self, rhs: Self) -> Self { + Params::get_instance().__addmod(self, rhs) } - fn __submod(self, lhs: BigNum, rhs: BigNum) -> BigNum { - self.__submod_impl(lhs, rhs) + fn __submod(self, rhs: Self) -> Self { + Params::get_instance().__submod(self, rhs) } - fn __mulmod(self, lhs: BigNum, rhs: BigNum) -> BigNum { - self.__mulmod_impl(lhs, rhs) + fn __mulmod(self, rhs: Self) -> Self { + Params::get_instance().__mulmod(self, rhs) } - fn __divmod(self, lhs: BigNum, rhs: BigNum) -> BigNum { - self.__divmod_impl(lhs, rhs) + fn __divmod(self, rhs: Self) -> Self { + Params::get_instance().__divmod(self, rhs) } // n.b. needs to be declared unconstrained because we take in a mutable slice - fn __batch_invert(self, x: [BigNum; M]) -> [BigNum; M] { - self.batch_invert_impl(x) + unconstrained fn __batch_invert(x: [Self; M]) -> [Self; M] { + Params::get_instance().__batch_invert(x) } - fn __invmod(self, val: BigNum) -> BigNum { - self.__invmod_impl(val) + fn __invmod(self) -> Self { + Params::get_instance().__invmod(self) } - fn __powmod(self, val: BigNum, exponent: BigNum) -> BigNum { - self.__powmod_impl(val, exponent) + fn __powmod(self, exponent: Self) -> Self { + Params::get_instance().__powmod(self, exponent) } fn __compute_quadratic_expression( - self, - lhs_terms: [[BigNum; LHS_N]; NUM_PRODUCTS], - lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], - rhs_terms: [[BigNum; RHS_N]; NUM_PRODUCTS], - rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], - linear_terms: [BigNum; ADD_N], - linear_flags: [bool; ADD_N] - ) -> (BigNum, BigNum) { - self.__compute_quadratic_expression_impl( - lhs_terms, - lhs_flags, - rhs_terms, - rhs_flags, - linear_terms, - linear_flags - ) + lhs_terms: [[Self; LHS_N]; NUM_PRODUCTS], + lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], + rhs_terms: [[Self; RHS_N]; NUM_PRODUCTS], + rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], + linear_terms: [Self; ADD_N], + linear_flags: [bool; ADD_N] + ) -> (Self, Self) { + Params::get_instance().__compute_quadratic_expression( + lhs_terms, + lhs_flags, + rhs_terms, + rhs_flags, + linear_terms, + linear_flags + ) } - // #################################################################################################################### // #################################################################################################################### // ### C O N S T R A I N E D F U N C T I O N S // #################################################################################################################### // #################################################################################################################### - /** - * @brief Constrain a degree-2 BigNum expression to be equal to 0 modulo self.modulus + * @brief Constrain a degree-2 BigNum expression to be equal to 0 modulo Params::modulus() * @description The expression is of the form (when evaluated as an integer relation): * * \sum_{i=0}^{NUM_PRODUCTS - 1} ((\sum_{j=0}^{LHS_N-1}lhs[i][j]) * (\sum_{j=0}^{RHS_N-1}rhs[i][j])) + \sum_{i=0}^{ADD_N - 1}linear_terms[i] - quotient * modulus = 0 @@ -371,160 +200,25 @@ impl BigNumInstanceTrait> for BigNumInsta * @param linear_flags an array of sign flags **/ fn evaluate_quadratic_expression( - self, - lhs_terms: [[BigNum; LHS_N]; NUM_PRODUCTS], + lhs_terms: [[Self; LHS_N]; NUM_PRODUCTS], lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], - rhs_terms: [[BigNum; RHS_N]; NUM_PRODUCTS], + rhs_terms: [[Self; RHS_N]; NUM_PRODUCTS], rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], - linear_terms: [BigNum; ADD_N], + linear_terms: [Self; ADD_N], linear_flags: [bool; ADD_N] ) { - // use an unconstrained function to compute the value of the quotient - let (quotient, _, borrow_flags): (BigNum, BigNum, ArrayX) = self.__compute_quadratic_expression_with_borrow_flags( + Params::get_instance().evaluate_quadratic_expression( lhs_terms, lhs_flags, rhs_terms, rhs_flags, linear_terms, linear_flags - ); - // constrain the quotient to be in the range [0, ..., 2^{m} - 1], where `m` is log2(modulus) rounded up. - // Additionally, validate quotient limbs are also in the range [0, ..., 2^{120} - 1] - quotient.validate_quotient_in_range(); - // TODO, validate we do not overflow N2 when multiplying and N when adding - // (should be a compile-time check...unconstrained function?) - - // Compute the linear sums that represent lhs_1, rhs_1, lhs_2, rhs_2, add - let mut t0: [[Field; N]; NUM_PRODUCTS] = [[0; N]; NUM_PRODUCTS]; - let mut t1: [[Field; N]; NUM_PRODUCTS] = [[0; N]; NUM_PRODUCTS]; - let mut t4: [Field; N] = [0; N]; - - // TODO: this is super nasty as it requires a multiplication - let double_modulus: [Field; N] = self.double_modulus; - for k in 0..NUM_PRODUCTS { - for i in 0..N { - for j in 0..LHS_N { - // note: if is_negative is not known at comptime this is very expensive - if (lhs_flags[k][j]) { - t0[k][i] -= lhs_terms[k][j].limbs[i]; - t0[k][i] += double_modulus[i]; - } else { - t0[k][i] += lhs_terms[k][j].limbs[i]; - } - } - for j in 0..RHS_N { - if (rhs_flags[k][j]) { - t1[k][i] -= rhs_terms[k][j].limbs[i]; - t1[k][i] += double_modulus[i]; - } else { - t1[k][i] += rhs_terms[k][j].limbs[i]; - } - } - } - } - for i in 0..N { - for j in 0..ADD_N { - if (linear_flags[j]) { - t4[i] -= linear_terms[j].limbs[i]; - t4[i] += double_modulus[i]; - } else { - t4[i] += linear_terms[j].limbs[i]; - } - } - } - - // We want to evaluate that t0 * t1 + t2 * t3 + t4 - Quotient * Modulus = 0, evaluated over the integers - // For this we need to be able to borrow values from more-significant limbs into less-significant limbs, - // so that we can ensure that no limbs will underflow for an honest Prover - let mut product_limbs: ArrayX = ArrayX::new(); - // let fff: [Field; N] = quotient.limbs; - // let mut borrow_flags: ArrayX = BigNum::get_borrow_flags3(t0, t1, t2, t3, t4, fff, self.modulus); - - // Compute the product t0 * t1 + t2 * t3 + t4 - Quotient * Modulus - // TODO: this is super nasty as it requires a multiplication - for i in 0..N { - for j in 0..N { - for k in 0..NUM_PRODUCTS { - if k == 0 { - let new_term = t0[k][i] * t1[k][j] - quotient.limbs[i] * self.modulus[j]; - std::as_witness(new_term); // width-4 optimization (n.b. might not be optimal if t2, t3 input arrays are nonzero) - product_limbs.add_assign(i + j, new_term); - } else { - product_limbs.add_assign(i + j, t0[k][i] * t1[k][j]); - } - } - if (NUM_PRODUCTS == 0) { - product_limbs.sub_assign(i + j, quotient.limbs[i] * self.modulus[j]); - } - } - product_limbs.add_assign(i, t4[i]); - } - - // each limb product represents the sum of 120-bit products. - // by setting the borrow value to 2^246 we are restricting this method's completeness to expressions - // where no more than 64 limb products are summed together. - // TODO: check in unconstrained function that this condition is satisfied - // TODO: define trade-offs regarding the value of borrow_shift - // (the larger the value, the greater the range check that is required on product_limbs) - // (126-bit range check is a sweet spot for the barretenberg backend as it decomposes into 9 14-bit range checks) - // (the barretenberg backend can evaluate these in 5.25 gates. 127 bits costs 6.5 gates) - let borrow_shift: Field = 0x40000000000000000000000000000000000000000000000000000000000000; // 2^{246} - let borrow_carry: Field = 0x40000000000000000000000000000000; // 2^{246 - 120} = 2^{126} - - // N.B. borrow_flags is `Field` type because making it `bool` would apply boolean constraints to all `N2` array entries. - // We only use `N2 - 2` borrow flags so applying 1-bit range checks on the array elements we use is more efficient. - // TODO: Once it is possible to perform arithmetic on generics we can use `borrow_flags: [bool;N+N-2]` to avoid this issue - borrow_flags.get(0).assert_max_bit_size(1); - product_limbs.add_assign(0, borrow_flags.get(0) * borrow_shift); - for i in 1..(N + N - 2) { - borrow_flags.get(i).assert_max_bit_size(1); - product_limbs.add_assign( - i, - (borrow_flags.get(i) * borrow_shift - borrow_flags.get(i - 1) * borrow_carry) - ); - } - product_limbs.sub_assign(N + N - 2, borrow_flags.get(N + N - 3) * borrow_carry); - - // Final step: Validate `product_limbs` represents the integer value `0` - // Each element `i` in `product_limbs` overlaps in bitrange with element `i+1`, EXCEPT for the low 120 bits - // i.e. we need to do the following for each limb `i`: - // 1. validate the limb's low-120 bits equals zero - // 2. compute the limb "carry" by right-shifting by 2^{120} - // 3. add the carry into limb `i+1` - // We can efficiently do all of the above by multiplying the limb by 2^{-120} and constraining the result to be <2^{126} - // (if the low 120 bits are nonzero the result will underflow and product a large value that cannot be range constrained) - // (the probability of an underflow value satisfying a 126-bit range constraint is approx. 2^{k - 126}, - // where k is the number of bits in the prime field) - // We then add the result into the next limb and repeat. - let hi_shift: Field = 0x1000000000000000000000000000000; - let hi_downshift: Field = 1 / hi_shift; - for i in 0..N + N - 2 { - product_limbs.mul_assign(i, hi_downshift); - std::as_witness(product_limbs.get(i)); - product_limbs.get(i).assert_max_bit_size(126); // N.B. is this sufficient? going beyond 126 costs us 1 gate per limb - product_limbs.add_assign(i + 1, product_limbs.get(i)); - } - // the most significant limb has no limb to "carry" values into - the entire limb must equal zero - assert(product_limbs.get(N + N - 2) == 0); + ) } - fn validate_in_field(self, val: BigNum) { - // N.B. need to combine with validate_in_range if `self` limbs have not been range constrained - let mut p_minus_self: [Field; N] = [0; N]; - let modulus: [Field; N] = self.modulus; - for i in 0..N { - p_minus_self[i] = modulus[i] - val.limbs[i]; - } - let borrow_flags = self.__validate_in_field_compute_borrow_flags(val); - let two_pow_120: Field = 0x1000000000000000000000000000000; - p_minus_self[0] += borrow_flags[0] as Field * two_pow_120; - for i in 1..N - 1 { - p_minus_self[i] += (borrow_flags[i] as Field * two_pow_120 - borrow_flags[i-1] as Field); - } - p_minus_self[N - 1] -= borrow_flags[N - 2] as Field; - let mut compare = val; - compare.limbs = p_minus_self; - compare.validate_in_range(); + fn validate_in_field(self: Self) { + Params::get_instance().validate_in_field(self); } /** @@ -535,749 +229,52 @@ impl BigNumInstanceTrait> for BigNumInsta * However the probability of an honest Prover being unable to satisfy this check is tiny! * (todo: compute how tiny) **/ - fn assert_is_not_equal(self, lhs: BigNum, rhs: BigNum) { - let mut l: Field = 0; - let mut r: Field = 0; - let mut modulus_mod_n: Field = 0; - let mut two_pow_120: Field = 0x1000000000000000000000000000000; - let modulus = self.modulus; - for i in 0..N { - l *= two_pow_120; - r *= two_pow_120; - modulus_mod_n *= two_pow_120; - l += lhs.limbs[N - i - 1]; - r += rhs.limbs[N - i - 1] ; - modulus_mod_n += modulus[N - i - 1]; - } - - // lhs can be either X mod N or P + X mod N - // rhs can be either Y mod N or P + Y mod N - // If lhs - rhs = 0 mod P then lhs - rhs = 0, P or -P mod N - let mut diff = l - r; - let mut target = diff * (diff + modulus_mod_n) * (diff - modulus_mod_n); - assert(target != 0, "asssert_is_not_equal fail"); - } - - fn eq(self, lhs: BigNum, rhs: BigNum) -> bool { - let diff = self.sub(lhs, rhs); - // if self == other, possible values of `diff` will be `p` or `0` - // (the subtract operator constrains diff to be < ceil(log(p))) - // TODO: can do this more efficiently via witngen in unconstrained functions? - let mut is_equal_modulus: bool = true; - let mut is_equal_zero: bool = true; - for i in 0..N { - is_equal_modulus = is_equal_modulus & (diff.limbs[i] == self.modulus[i]); - is_equal_zero = is_equal_zero & (diff.limbs[i] == 0); - } - is_equal_modulus | is_equal_zero + fn assert_is_not_equal(self, other: Self) { + Params::get_instance().assert_is_not_equal(self, other); } - fn neg(self, val: BigNum) -> BigNum { - // so we do... p - x - r = 0 and there might be borrow flags - let (result, borrow_flags) = self.__negate_with_flags(val); - result.validate_in_range(); - let modulus = self.modulus; - let borrow_shift = 0x1000000000000000000000000000000; - let result_limb = modulus[0] - val.limbs[0] - result.limbs[0] + (borrow_flags[0] as Field * borrow_shift); - assert(result_limb == 0); - for i in 1..N - 1 { - let result_limb = modulus[i] - val.limbs[i] - result.limbs[i] - borrow_flags[i - 1] as Field - + (borrow_flags[i] as Field * borrow_shift); - assert(result_limb == 0); - } - let result_limb = modulus[N - 1] - val.limbs[N - 1] - result.limbs[N - 1] - borrow_flags[N - 2] as Field; - assert(result_limb == 0); - result + fn neg(self) -> Self { + Params::get_instance().neg(self) } +} - fn add(self, lhs: BigNum, rhs: BigNum) -> BigNum { - // so we do... p - x - r = 0 and there might be borrow flags - let (result, carry_flags, borrow_flags, overflow_modulus) = self.__add_with_flags(lhs, rhs); - result.validate_in_range(); - let modulus = self.modulus; - let borrow_shift = 0x1000000000000000000000000000000; - let carry_shift = 0x1000000000000000000000000000000; - - let mut subtrahend: [Field; N] = [0; N]; - if (overflow_modulus) { - subtrahend = modulus; - } - let result_limb = lhs.limbs[0] + rhs.limbs[0] - subtrahend[0] - result.limbs[0] - + (borrow_flags[0] as Field * borrow_shift) - - (carry_flags[0] as Field * carry_shift); - assert(result_limb == 0); - for i in 1..N - 1 { - let result_limb = lhs.limbs[i] + rhs.limbs[i] - - subtrahend[i] - - result.limbs[i] - - borrow_flags[i - 1] as Field - + carry_flags[i - 1] as Field - + ((borrow_flags[i] as Field - carry_flags[i] as Field) * borrow_shift); - assert(result_limb == 0); - } - let result_limb = lhs.limbs[N - 1] + rhs.limbs[N - 1] - - subtrahend[N - 1] - - result.limbs[N - 1] - - borrow_flags[N - 2] as Field - + carry_flags[N - 2] as Field; - assert(result_limb == 0); - result +impl std::ops::Add for BigNum where Params: BigNumParamsTrait + RuntimeBigNumParamsTrait { + // Note: this method is expensive! Try to craft quadratic relations and directly evaluate them + // via evaluate_quadratic_expression + fn add(self, other: Self) -> Self { + Params::get_instance().add(self, other) } +} +impl std::ops::Sub for BigNum where Params: BigNumParamsTrait + RuntimeBigNumParamsTrait { // Note: this method is expensive! Try to craft quadratic relations and directly evaluate them // via evaluate_quadratic_expression - fn sub(self, lhs: BigNum, rhs: BigNum) -> BigNum { - // so we do... p - x - r = 0 and there might be borrow flags - let (result, carry_flags, borrow_flags, underflow) = self.__sub_with_flags(lhs, rhs); - result.validate_in_range(); - let modulus = self.modulus; - let borrow_shift = 0x1000000000000000000000000000000; - let carry_shift = 0x1000000000000000000000000000000; - - let mut addend: [Field; N] = [0; N]; - if (underflow) { - addend = modulus; - } - let result_limb = lhs.limbs[0] - rhs.limbs[0] + addend[0] - result.limbs[0] - + (borrow_flags[0] as Field * borrow_shift) - - (carry_flags[0] as Field * carry_shift); - assert(result_limb == 0); - for i in 1..N - 1 { - let result_limb = lhs.limbs[i] - rhs.limbs[i] + addend[i] - result.limbs[i] - borrow_flags[i - 1] as Field - + carry_flags[i - 1] as Field - + ((borrow_flags[i] as Field - carry_flags[i] as Field) * borrow_shift); - assert(result_limb == 0); - } - let result_limb = lhs.limbs[N - 1] - rhs.limbs[N - 1] + addend[N - 1] - - result.limbs[N - 1] - - borrow_flags[N - 2] as Field - + carry_flags[N - 2] as Field; - assert(result_limb == 0); - result + fn sub(self, other: Self) -> Self { + Params::get_instance().sub(self, other) } +} + +impl std::ops::Mul for BigNum where Params: BigNumParamsTrait + RuntimeBigNumParamsTrait { // Note: this method is expensive! Try to craft quadratic relations and directly evaluate them // via evaluate_quadratic_expression // e.g. performing a sum of multiple multiplications and additions via `evaluate_quadratic_expression` // will create much fewer constraints than calling `mul` and `add` directly - fn mul(self, lhs: BigNum, rhs: BigNum) -> BigNum { - let result = self.__mulmod(lhs, rhs); - self.evaluate_quadratic_expression([[lhs]], [[false]], [[rhs]], [[false]], [result], [true]); - result - } - // Note: this method is expensive! Witness computation is extremely expensive as it requires modular exponentiation - fn div(self, lhs: BigNum, rhs: BigNum) -> BigNum { - let result = self.__divmod(lhs, rhs); - self.evaluate_quadratic_expression([[result]], [[false]], [[rhs]], [[false]], [lhs], [true]); - result - } - -} -impl BigNumInstance where Params: BigNumParamsTrait { - // impl BigNumInstanceTrait for BigNumInstance where Params: BigNumParamsTrait { - // Repeatedly hashes a seed to produce enough entropy to cover (modulus_bits * 2) number of bits - // The result is then reduced modulo p to produce an element of the prime field - - // #################################################################################################################### - // #################################################################################################################### - // ### C O N S T R U C T O R S - // #################################################################################################################### - // #################################################################################################################### - - fn new(modulus: [Field; N], redc_param: [Field; N]) -> Self { - Self { redc_param, modulus, double_modulus: get_double_modulus(modulus) } - } - - unconstrained fn __derive_from_seed_impl(self, seed: [u8; SeedBytes]) -> BigNum { - let mut rolling_seed = seed; - - let mut to_reduce: ArrayX = ArrayX { segments: [[0; N], [0; N]] }; - - let mut double_modulus_bits = Params::modulus_bits() * 2; - let mut double_modulus_bytes = (double_modulus_bits) / 8 + (double_modulus_bits % 8 != 0) as u64; - - let mut last_limb_bytes = double_modulus_bytes % 15; - if (last_limb_bytes == 0) { - last_limb_bytes = 15; - } - let mut last_limb_bits = double_modulus_bits % 8; - if (last_limb_bits == 0) { - last_limb_bits = 8; - } - - for i in 0..(N - 1) { - let hash: [u8; 32] = std::hash::sha256(rolling_seed); - let mut lo: Field = 0; - let mut hi: Field = 0; - for j in 0..15 { - hi *= 256; - lo *= 256; - - if (i < 2 * N - 2) { - lo += hash[j + 15] as Field; - hi += hash[j] as Field; - } - } - to_reduce.set(2 * i, lo); - to_reduce.set(2 * i + 1, hi); - rolling_seed[0] += 1; - } - - { - let hash: [u8; 32] = std::hash::sha256(rolling_seed); - let mut hi: Field = 0; - for j in 0..(last_limb_bytes - 1) { - hi *= 256; - hi += hash[j] as Field; - } - hi *= 256; - let last_byte = hash[last_limb_bytes - 1]; - let mask = (1 as u64 << (last_limb_bits) as u8) - 1; - let last_bits = last_byte as u64 & mask; - hi += last_bits as Field; - to_reduce.set(2 * N - 2, hi); - } - - let (_, remainder) = __barrett_reduction(to_reduce, self.redc_param, Params::k(), self.modulus); - let mut result = BigNum::new(); - result.limbs = remainder; - result - } - - // #################################################################################################################### - // #################################################################################################################### - // ### U N C O N S T R A I N E D F U N C T I O N S - // #################################################################################################################### - // #################################################################################################################### - - unconstrained fn __validate_in_field_compute_borrow_flags(self: Self, val: BigNum) -> [bool; N] { - let mut flags: [bool; N] = [false; N]; - let modulus: [Field; N] = self.modulus; - flags[0] = modulus[0].lt(val.limbs[0]); - for i in 1..N - 1 { - flags[i] = modulus[i].lt(val.limbs[i] + flags[i - 1] as Field); - } - flags - } - - unconstrained fn __powmod_impl(self, val: BigNum, exponent: BigNum) -> BigNum { - let x: U60Repr = U60Repr::from(exponent.limbs); - - let num_bits = Params::modulus_bits() + 1; - - let mut accumulator = BigNum::one(); - - for i in 0..num_bits { - accumulator = self.__mulmod(accumulator, accumulator); - if x.get_bit(num_bits - i - 1) { - accumulator = self.__mulmod(accumulator, val); - } - } - accumulator - } - - unconstrained fn __mulmod_with_quotient( - self, - lhs: BigNum, - rhs: BigNum - ) -> (BigNum, BigNum) { - let mut mul: ArrayX = ArrayX::new(); - for i in 0..N { - for j in 0..N { - mul.add_assign(i + j, lhs.limbs[i] * rhs.limbs[j]); - } - } - let (q, r) = __barrett_reduction( - mul.__normalize_limbs(N + N), - self.redc_param, - Params::k(), - self.modulus - ); - - let mut quotient = BigNum::from_array(q); - let mut remainder = BigNum::from_array(r); - (quotient, remainder) - } - - unconstrained fn __mulmod_impl(self, lhs: BigNum, rhs: BigNum) -> BigNum { - let (_, b) = self.__mulmod_with_quotient(lhs, rhs); - b - } - - unconstrained fn __addmod_impl(self, lhs: BigNum, rhs: BigNum) -> BigNum { - let x_u60 : U60Repr = U60Repr::from(lhs.limbs); - let y_u60 : U60Repr = U60Repr::from(rhs.limbs); - - let mut z_u60 = x_u60 + y_u60; - - let modulus_u60 : U60Repr = U60Repr::from(self.modulus); - if z_u60.gte(modulus_u60) { - z_u60 = z_u60 - modulus_u60; - } - let mut result = BigNum::from_array(U60Repr::into(z_u60)); - result - } - - /** - * @brief given an input `x`, compute `2p - x` (unconstrained) - * - * @description we subtract the input from double the modulus, because all constrained BigNum operations - * only guarantee that the output is in the range [0, ceil(log2(p))]. - * I.E. the input may be larger than the modulus `p`. - * In order to ensure this operation does not underflow, we compute `2p - x` instead of `p - x`. - * N.B. constrained BigNum operations do not fully constrain outputs to be in the range [0, p-1] - * because such a check is expensive and usually unneccesary. - */ - unconstrained fn __negate_impl(self, val: BigNum) -> BigNum { - let f: [Field; N] = val.limbs; - let x_u60 : U60Repr = U60Repr::from(f); - let modulus_u60 : U60Repr = U60Repr::from(self.modulus); - let mut result = BigNum::from_array(U60Repr::into(modulus_u60 - x_u60)); - result - } - - unconstrained fn __add_with_flags( - self, - lhs: BigNum, - rhs: BigNum - ) -> (BigNum, [bool; N], [bool; N], bool) { - let a_u60 : U60Repr = U60Repr::from(lhs.limbs); - let b_u60 : U60Repr = U60Repr::from(rhs.limbs); - let add_u60 = a_u60 + b_u60; - let modulus_u60 : U60Repr = U60Repr::from(self.modulus); - - let overflow = add_u60.gte(modulus_u60); - - let mut subtrahend_u60 : U60Repr = U60Repr { limbs: ArrayX::new() }; - let mut result_u60 : U60Repr = U60Repr { limbs: ArrayX::new() }; - - if overflow { - subtrahend_u60 = modulus_u60; - } - - let mut carry: u64 = 0; - let mut carry_in: u64 = 0; - let mut borrow: u64 = 0; - let mut borrow_in: u64 = 0; - let mut borrow_flags: [bool; N] = [false; N]; - let mut carry_flags: [bool; N] = [false; N]; - for j in 0..2 { - for i in 0..N { - let mut add_term: u64 = a_u60.limbs.segments[j][i] + b_u60.limbs.segments[j][i] + carry_in; - carry = (add_term >= 0x1000000000000000) as u64; - add_term -= (carry as u64 * 0x1000000000000000); - result_u60.limbs.segments[j][i] = add_term; - carry_in = carry as u64; - borrow = ((subtrahend_u60.limbs.segments[j][i] + borrow_in) > result_u60.limbs.segments[j][i]) as u64; - let sub = (borrow << 60) + result_u60.limbs.segments[j][i] - - subtrahend_u60.limbs.segments[j][i] - - borrow_in; - result_u60.limbs.segments[j][i] = sub; - borrow_in = borrow; - - if ((j * N + i) & 1 == 1) { - let idx = (j * N + i - 1) / 2; - if (carry & borrow == 1) { - carry = 0; - borrow = 0; - } - carry_flags[idx] = carry as bool; - borrow_flags[idx] = borrow as bool; - } - } - } - let mut result = BigNum::from_array(U60Repr::into(result_u60)); - - (result, carry_flags, borrow_flags, overflow) - } - - unconstrained fn __sub_with_flags( - self, - lhs: BigNum, - rhs: BigNum - ) -> (BigNum, [bool; N], [bool; N], bool) { - let a_u60 : U60Repr = U60Repr::from(lhs.limbs); - let b_u60 : U60Repr = U60Repr::from(rhs.limbs); - let modulus_u60 : U60Repr = U60Repr::from(self.modulus); - - let underflow = b_u60.gte(a_u60 + U60Repr::one()); - - let mut addend_u60 : U60Repr = U60Repr { limbs: ArrayX::new() }; - let mut result_u60 : U60Repr = U60Repr { limbs: ArrayX::new() }; - - if underflow { - addend_u60 = modulus_u60; - } - - let mut carry: u64 = 0; - let mut carry_in: u64 = 0; - let mut borrow: u64 = 0; - let mut borrow_in: u64 = 0; - let mut borrow_flags: [bool; N] = [false; N]; - let mut carry_flags: [bool; N] = [false; N]; - for j in 0..2 { - for i in 0..N { - let mut add_term: u64 = a_u60.limbs.segments[j][i] + addend_u60.limbs.segments[j][i] + carry_in; - carry = (add_term >= 0x1000000000000000) as u64; - add_term -= (carry as u64 * 0x1000000000000000); - result_u60.limbs.segments[j][i] = add_term; - carry_in = carry as u64; - borrow = ((b_u60.limbs.segments[j][i] + borrow_in) > result_u60.limbs.segments[j][i]) as u64; - let sub = (borrow << 60) + result_u60.limbs.segments[j][i] - - b_u60.limbs.segments[j][i] - - borrow_in; - result_u60.limbs.segments[j][i] = sub; - borrow_in = borrow; - - if ((j * N + i) & 1 == 1) { - let idx = (j * N + i - 1) / 2; - if (carry & borrow == 1) { - carry = 0; - borrow = 0; - } - carry_flags[idx] = carry as bool; - borrow_flags[idx] = borrow as bool; - } - } - } - let mut result = BigNum::from_array(U60Repr::into(result_u60)); - (result, carry_flags, borrow_flags, underflow) - } - - unconstrained fn __negate_with_flags(self, val: BigNum) -> (BigNum, [bool; N]) { - let f: [Field; N] = val.limbs; - let x_u60 : U60Repr = U60Repr::from(f); - let modulus_u60 : U60Repr = U60Repr::from(self.modulus); - let mut result_u60 : U60Repr = U60Repr { limbs: ArrayX::new() }; - - let mut borrow: u64 = 0; - let mut borrow_in: u64 = 0; - - let mut borrow_flags: [bool; N] = [false; N]; - for j in 0..2 { - for i in 0..N { - borrow = ((x_u60.limbs.segments[j][i] + borrow_in) > modulus_u60.limbs.segments[j][i]) as u64; - let sub = (borrow << 60) + modulus_u60.limbs.segments[j][i] - - x_u60.limbs.segments[j][i] - - borrow_in; - result_u60.limbs.segments[j][i] = sub; - borrow_in = borrow; - if ((j * N + i) & 1 == 1) { - let idx = (j * N + i - 1) / 2; - borrow_flags[idx] = borrow as bool; - } - } - } - let mut result = BigNum::from_array(U60Repr::into(result_u60)); - (result, borrow_flags) - } - - /** - * @brief given inputs `x, y` compute 2p + x - y (unconstrained) - * @description see `__negate` for why we use 2p instead of p - **/ - unconstrained fn __submod_impl(self, lhs: BigNum, rhs: BigNum) -> BigNum { - self.__addmod(lhs, self.__negate(rhs)) - } - - unconstrained fn __invmod_impl(self, val: BigNum) -> BigNum { - let modulus_u60: U60Repr = U60Repr::from(self.modulus); - let one: BigNum = BigNum::one(); - let one_u60: U60Repr = U60Repr::from(one.limbs); - let exponent = modulus_u60.sub(one_u60.add(one_u60)); - let mut result = BigNum::from_array(U60Repr::into(exponent)); - self.__powmod(val, result) - } - - unconstrained fn batch_invert_impl(self, x: [BigNum; M]) -> [BigNum; M] { - // TODO: ugly! Will fail if input slice is empty - let mut accumulator: BigNum = BigNum::one(); - let mut result: [BigNum; M] = [BigNum::new(); M]; - let mut temporaries: [BigNum] = &[]; - for i in 0..x.len() { - temporaries = temporaries.push_back(accumulator); - if (x[i].__is_zero() == false) { - accumulator = self.__mulmod(accumulator, x[i]); - } - } - - accumulator = self.__invmod(accumulator); - let mut T0: BigNum = BigNum::new(); - T0.limbs = [0; N]; - for i in 0..x.len() { - let idx = x.len() - 1 - i; - if (x[idx].__is_zero() == false) { - T0 = self.__mulmod(accumulator, temporaries[idx]); - accumulator = self.__mulmod(accumulator, x[idx]); - result[idx] = T0; - } - } - result - } - - unconstrained fn __divmod_impl(self, numerator: BigNum, divisor: BigNum) -> BigNum { - let t0 = self.__invmod(divisor); - self.__mulmod(numerator, t0) - } - - /** - * @brief Computes the result of a linear combination of (possibly negative) BigNum values (unconstrained) - **/ - // NOTE: modulus2 is structured such that all limbs will be greater than 0, even when subtracting. - // To do this, when computing `p - x`, we ensure that each limb in `p` is greater than each limb in `x`. - // We know that, for a valid bignum element, the limbs in `x` will be <2^{120} - // Therefore each of the limbs in `p` (except the most significant) will borrow 2^{120} from the more significant limb. - // Finally, to ensure we do not underflow in the most significant limb, we use `2p` instead of `p` - unconstrained fn __add_linear_expression(self, x: [BigNum; M], flags: [bool; M]) -> ([Field; N]) { - // TODO, validate we do not overflow N2 when multiplying and N when adding - let mut sum: [Field; N] = [0; N]; - // TODO: ugly! Will fail if input array is empty - let modulus2: [Field;N] = self.double_modulus; - for i in 0..M { - if (flags[i]) { - for j in 0..N { - sum[j] = sum[j] + modulus2[j] - x[i].limbs[j]; - assert(x[i].limbs[j].lt(modulus2[j])); - } - } else { - for j in 0..N { - sum[j] = sum[j] + x[i].limbs[j]; - } - } - } - // problem if we normalize when used in computing quotient - sum - // let result_p: ArrayX = BigNum::__normalize_limbs(ArrayX::from_array(lhs_sum_p), N); - // let result_n: ArrayX = BigNum::__normalize_limbs(ArrayX::from_array(lhs_sum_n), N); - // (result_p.segments[0], result_n.segments[0]) - } - - /** - * @brief computes the limb products of a quadratic expression - * @details see __compute_quadratic_expression_with_borrow_flags for full description - **/ - unconstrained fn __compute_quadratic_expression_product( - self, - lhs_terms: [[BigNum; LHS_N]; NUM_PRODUCTS], - lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], - rhs_terms: [[BigNum; RHS_N]; NUM_PRODUCTS], - rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], - linear_terms: [BigNum; ADD_N], - linear_flags: [bool; ADD_N] - ) -> ArrayX { - // TODO, validate we do not overflow N2 when multiplying and N when adding - let mut lhs: [[Field; N]; NUM_PRODUCTS] = [[0; N]; NUM_PRODUCTS]; - let mut rhs: [[Field; N]; NUM_PRODUCTS] = [[0; N]; NUM_PRODUCTS]; - let mut add: [Field; N] = [0; N]; - - for i in 0..NUM_PRODUCTS { - lhs[i] = self.__add_linear_expression(lhs_terms[i], lhs_flags[i]); - rhs[i]= self.__add_linear_expression(rhs_terms[i], rhs_flags[i]); - } - - let add: [Field; N] = self.__add_linear_expression(linear_terms, linear_flags); - - let mut mulout: ArrayX = ArrayX::new(); - - for i in 0..N { - for j in 0..N { - for k in 0..NUM_PRODUCTS { - mulout.add_assign(i + j, (lhs[k][i] * rhs[k][j])); - } - } - mulout.add_assign(i, add[i]); - } - mulout - } - - /** - * @brief computes the quotient/remainder of a quadratic expression - * @details see __compute_quadratic_expression_with_borrow_flags for full description - **/ - unconstrained fn __compute_quadratic_expression_impl( - self, - lhs_terms: [[BigNum; LHS_N]; NUM_PRODUCTS], - lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], - rhs_terms: [[BigNum; RHS_N]; NUM_PRODUCTS], - rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], - linear_terms: [BigNum; ADD_N], - linear_flags: [bool; ADD_N] - ) -> (BigNum, BigNum) { - // TODO, validate we do not overflow N2 when multiplying and N when adding - let mulout = self.__compute_quadratic_expression_product( - lhs_terms, - lhs_flags, - rhs_terms, - rhs_flags, - linear_terms, - linear_flags - ); - let relation_result: ArrayX = mulout.__normalize_limbs(N + N); - - // TODO: ugly! Will fail if input slice is empty - let k = Params::k(); - - let (quotient, remainder) = __barrett_reduction(relation_result, self.redc_param, k, self.modulus); - - let mut q = BigNum::from_array(quotient); - let mut r = BigNum::from_array(remainder); - (q, r) - } - - /** - * @brief Given a degree-2 BigNum expression that is equal to 0 mod p, compute the quotient and borrow flags - * @description The expression is of the form: - * - * \sum_{i=0}^{NUM_PRODUCTS - 1} ((\sum_{j=0}^{LHS_N-1}lhs[i][j]) * (\sum_{j=0}^{RHS_N-1}rhs[i][j])) + \sum_{i=0}^{ADD_N - 1}linear_terms[i] = quotient * modulus - * - * The intent is to capture an arbitrary degree-2 expression within the limitations of Noir (no efficient dynamically-sized vectors) - * - * When performing BigNum arithmetic, we want to represent desired BigNum operations in a way that minimizes the number of modular reductions that are required. - * This can be achieved by minimizing the number of degree-2 relations required. - * - * The borrow flags describe whether individual Field limbs will underflow when evaluating the above relation. - * For example, when computing the product a * b - q * p = 0, it is possible that: - * 1. a[0]*b[0] - p[0]*q[0] = -2^{120} - * 2. a[0]*b[1] + a[1]*b[0] - p[0]*q[1] - p[1]*q[0] = 1 - * In the above example, the value represented by these two limbs is zero despite each limb being nonzero. - * In this case, to correctly constrain the result, we must add (at least) 2^{120} from the first limb and subtract 1 from the second. - * - * @param lhs_terms a 2D array of BigNum - * @param lhs_flags a 2D array of sign flags - * @param rhs_terms a 2D array of BigNum - * @param rhs_flags a 2D array of sign flags - * @param linear_terms an array of BigNum - * @param linear_flags an array of sign flags - **/ - unconstrained fn __compute_quadratic_expression_with_borrow_flags( - self, - lhs_terms: [[BigNum; LHS_N]; NUM_PRODUCTS], - lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], - rhs_terms: [[BigNum; RHS_N]; NUM_PRODUCTS], - rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], - linear_terms: [BigNum; ADD_N], - linear_flags: [bool; ADD_N] - ) -> (BigNum, BigNum, ArrayX) { - // TODO, validate we do not overflow N2 when multiplying and N when adding - - let mut mulout_p = self.__compute_quadratic_expression_product( - lhs_terms, - lhs_flags, - rhs_terms, - rhs_flags, - linear_terms, - linear_flags - ); - let mut mulout_n: ArrayX = ArrayX::new(); - - let relation_result: ArrayX = mulout_p.__normalize_limbs(N + N); - let modulus: [Field; N] = self.modulus; - let (quotient, remainder) = __barrett_reduction(relation_result, self.redc_param, Params::k(), modulus); - assert(remainder == [0; N]); - - for i in 0..N { - for j in 0..N { - mulout_n.add_assign(i + j, quotient[i] * modulus[j]); - } - } - - // compute borrow flags from mulout_p and mulout_n - let mut borrow_flags: ArrayX = ArrayX::new(); - let borrow_shift: Field = 0x40000000000000000000000000000000000000000000000000000000000000; // 2^{246} - let borrow_carry: Field = 0x40000000000000000000000000000000; // 2^{246 - 120} = 2^{126} - let two_pow_120: Field = 0x1000000000000000000000000000000; - let downshift: Field = 1 / two_pow_120; - - // determine whether we need to borrow from more significant limbs. - // initial limb is "simple" comparison operation - // TODO: check how expensive `lt` operator is w.r.t. witness generation - borrow_flags.set(0, mulout_p.get(0).lt(mulout_n.get(0)) as Field); - // we have 2N - 2 borrow flags. The number of limbs from our product computation is 2N - 1 - // and there is nothing to borrow against for the final limb. - let mut hi_bits = (mulout_p.get(0) - mulout_n.get(0) + (borrow_flags.get(0) * borrow_shift)) * downshift; - for i in 1..(N + N - 2) { - // compute the contribution from limb `i-1` that gets added into limb `i`, and add into limb `i` - // let hi_bits = (mulout_p.get(i - 1) - mulout_n.get(i - 1) + (borrow_flags.get(i - 1) * borrow_shift)) - // * downshift; - mulout_p.add_assign(i, hi_bits); - - // determine whether negative limb values are greater than positive limb values - let underflow: Field = mulout_p.get(i).lt(mulout_n.get(i) + (borrow_flags.get(i - 1) * borrow_carry)) as Field; - borrow_flags.set(i, underflow); - - hi_bits = (mulout_p.get(i) - mulout_n.get(i) + (borrow_flags.get(i) * borrow_shift) - - (borrow_flags.get(i - 1) * borrow_carry)) * downshift; - } - - let mut q = BigNum::from_array(quotient); - let mut r = BigNum::from_array(remainder); - (q, r, borrow_flags) + fn mul(self, other: Self) -> Self { + Params::get_instance().mul(self, other) } } -fn get_double_modulus(modulus: [Field; N]) -> [Field; N] { - let TWO_POW_120: Field = 0x1000000000000000000000000000000; - let m: U60Repr = U60Repr::from(modulus); - let mut result: [Field; N] = U60Repr::into(m + m); - - result[0] += TWO_POW_120; - for i in 1..N - 1 { - result[i] += (TWO_POW_120 - 1); +impl std::ops::Div for BigNum where Params: BigNumParamsTrait + RuntimeBigNumParamsTrait { + // Note: this method is expensive! Witness computation is extremely expensive as it requires modular exponentiation + fn div(self, other: Self) -> Self { + Params::get_instance().div(self, other) } - result[N - 1] -= 1; - result } -unconstrained fn __barrett_reduction( - x: ArrayX, - redc_param: [Field; N], - k: u64, - modulus: [Field; N] -) -> ([Field; N], [Field; N]) { - let mut mulout: ArrayX = ArrayX { segments: [[0; N]; 3] }; - for i in 0..(N + N) { - for j in 0..N { - mulout.add_assign(i + j, x.get(i) * redc_param[j]); - } - } - mulout = mulout.__normalize_limbs(3 * N - 1); - let mulout_u60: U60Repr = U60Repr::new(mulout); - let mut quotient_u60 = mulout_u60.shr((k + k)); +impl std::cmp::Eq for BigNum where Params: BigNumParamsTrait + RuntimeBigNumParamsTrait { - // N.B. we assume that the shifted quotient cannot exceed 2 times original bit size. - // (partial_quotient should be just slightly larger than the modulus, we could probably represent with a size N+1 array) - let partial_quotient: ArrayX = quotient_u60.into_arrayX(); - - // quotient_mul_modulus can never exceed input value `x` so can fit into size-2 array - let mut quotient_mul_modulus: ArrayX = ArrayX { segments: [[0; N]; 2] }; - let mut quotient_mul_modulus_normalized: ArrayX = ArrayX { segments: [[0; N]; 2] }; - for j in 0..N { - for i in 0..(N + N - j) { - quotient_mul_modulus.add_assign(i + j, partial_quotient.get(i) * modulus[j]); - } - } - - for i in 0..(N + N) { - let (lo, hi) = split_bits::split_120_bits(quotient_mul_modulus.get(i)); - quotient_mul_modulus_normalized.set(i, lo); - // TODO: what is faster, leaving this if statement in or out? - // (array is size-1 too large so we can tolerate adding 0 into max element) - if (i + 1 < N + N) { - quotient_mul_modulus.add_assign(i + 1, hi); - } + fn eq(self, other: Self) -> bool { + let bn: RuntimeBigNumInstance = Params::get_instance(); + bn.eq(self, other) } - let quotient_mul_modulus_u60: U60Repr = U60Repr::new(quotient_mul_modulus_normalized); - - let modulus_u60: U60Repr = U60Repr::from(modulus); - let x_u60 : U60Repr = U60Repr::new(x); - let mut remainder_u60 = x_u60 - quotient_mul_modulus_u60; - - if (remainder_u60.gte(modulus_u60)) { - remainder_u60 = remainder_u60 - modulus_u60; - quotient_u60.increment(); - } else {} - - let q: [Field; N] = U60Repr::into(quotient_u60); - let r: [Field; N] = U60Repr::into(remainder_u60); - - (q, r) } diff --git a/src/runtime_bignum.nr b/src/runtime_bignum.nr new file mode 100644 index 0000000..468c606 --- /dev/null +++ b/src/runtime_bignum.nr @@ -0,0 +1,1309 @@ +use dep::std; +use crate::utils::u60_representation::U60Repr; +use crate::utils::arrayX::ArrayX; +use crate::utils::split_bits; +use crate::BigNum; + +/** + * @brief runtime_bignum::BigNumTrait defines methods available to BigNum *if* the modulus is not known at compile time. + * e.g. RSA where the modulus is a witness value as it changes for every RSA signature + * tee `lib.nr` for a trait definition where the modulus is known at compile time + **/ +trait BigNumTrait { + fn new() -> Self; + fn one() -> Self; + fn from(limbs: [Field]) -> Self; + fn from_byte_be(x: [u8; NBytes]) -> Self; + fn to_le_bytes(val: Self) -> [u8; NBytes]; + fn get(self) -> [Field]; + fn get_limb(self, idx: u64) -> Field; + fn set_limb(&mut self, idx: u64, value: Field); + fn conditional_select(lhs: Self, rhs: Self, predicate: bool) -> Self; + fn validate_in_range(self); + fn validate_quotient_in_range(self); + fn get_modulus_bits() -> u64; + fn num_limbs() -> u64; + fn __is_zero(self) -> bool; + fn __eq(self, rhs: Self) -> bool; +} + +/** + * @brief BigNumInstanceTrait defines methods available to a runtime BigNumInstance. + * BigNumInstance wraps the modulus parameter (as well as a Barret reduction parameter), + * which is required for the majority of BigNum operations + **/ +trait BigNumInstanceTrait where BN: BigNumTrait { + fn modulus(self) -> BN; + fn __derive_from_seed(self, seed: [u8; SeedBytes]) -> BN; + fn eq(self, lhs: BN, rhs: BN) -> bool; + fn __negate(self, val: BN) -> BN; + fn __addmod(self, lhs: BN, rhs: BN) -> BN; + fn __submod(self, lhs: BN, rhs: BN) -> BN; + fn __mulmod(self, lhs: BN, rhs: BN) -> BN; + fn __divmod(self, lhs: BN, rhs: BN) -> BN; + fn __batch_invert(self, x: [BN; M]) -> [BN; M]; + fn __invmod(self, val: BN) -> BN; + fn __powmod(self, val: BN, exponent: BN) -> BN; + fn __compute_quadratic_expression( + self, + lhs_terms: [[BN; LHS_N]; NUM_PRODUCTS], + lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], + rhs_terms: [[BN; RHS_N]; NUM_PRODUCTS], + rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], + linear_terms: [BN; ADD_N], + linear_flags: [bool; ADD_N] + ) -> (BN, BN); + + fn evaluate_quadratic_expression( + self, + lhs_terms: [[BN; LHS_N]; NUM_PRODUCTS], + lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], + rhs_terms: [[BN; RHS_N]; NUM_PRODUCTS], + rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], + linear_terms: [BN; ADD_N], + linear_flags: [bool; ADD_N] + ); + + fn validate_in_field(self, val: BN); + fn assert_is_not_equal(self, lhs: BN, rhs: BN); + fn neg(self, val: BN) -> BN; + fn add(self, lhs: BN, rhs: BN) -> BN; + fn sub(self, lhs: BN, rhs: BN) -> BN; + fn mul(self, lhs: BN, rhs: BN) -> BN; + fn div(self, lhs: BN, rhs: BN) -> BN; + +} + +/** + * @brief BigNumParamsTrait defines a "field" with which to parametrise BigNum. + * @description The "field" does not need to be prime, any value *should* work (TODO: test!) +**/ +trait BigNumParamsTrait { + /** + * @brief k used for __barrett_reduction. Should be at least modulus_bits() + 1 + **/ + fn k() -> u64; + /** + * @brief modulus_bits = log2(modulus) rounded up + **/ + fn modulus_bits() -> u64; +} + +// add modulus_u60? +struct BigNumInstance { + + // /** + // * @brief modulus: all BigNum operations are evaluated modulo this value + // **/ + modulus: [Field; N], + // /** + // * @brief double_modulus: used when performing negations and subtractions + // **/ + double_modulus: [Field; N], + // /** + // * @brief redc_param used for __barrett_reduction. See https://en.wikipedia.org/wiki/Barrett_reduction + // **/ + redc_param: [Field; N], +} + +impl BigNum { + // some strange circular dependency problem means we need to define `new` as a member of BigNumTrait as well as a definition outside of the trait + // (delete this method to see. BigNumInstance methods that use BigNum::new() error out, and I can't find a way of declaring BigNum to satisfy BigNumTrait as part of the BigNumInstance definition because BigNumInstance has no contextual knowledge of the BigNum type...) + fn new() -> Self { + BigNum { limbs: [0; N] } + } + fn one() -> BigNum { + let mut result: Self = BigNum { limbs: [0; N] }; + result.limbs[0] = 1; + result + } +} + +impl BigNumTrait for BigNum where Params: BigNumParamsTrait { + + fn new() -> Self { + BigNum::new() + } + fn one() -> Self { + BigNum::one() + } + + fn from(limbs: [Field]) -> Self { BigNum{ limbs: limbs.as_array() }} + + /** + * @brief construct a BigNum instance out of an array of bytes in BIG ENDIAN format + * @description: each 120-bit limb represents 15 bytes, we require that the size of the byte array + * is precisely large enough to cover Params::modulus_bits() + * @param x: input byte array + **/ + fn from_byte_be(x: [u8; NBytes]) -> BigNum { + let num_bits: u64 = NBytes * 8; + let modulus_bits: u64 = Params::modulus_bits(); + assert(num_bits > modulus_bits); + assert(num_bits - modulus_bits < 8); + let mut result = BigNum::new(); + + let excess_bytes = N * 15 - NBytes; + let final_limb_bytes = 15 - excess_bytes; + let mut limb: Field = 0; + let mut k = 0; + for _j in 0..final_limb_bytes { + limb *= 256; + limb += x[k] as Field; + k += 1; + } + result.limbs[N - 1] = limb; + + for i in 1..N { + let mut limb: Field = 0; + for _j in 0..15 { + limb *= 256; + limb += x[k] as Field; + k += 1; + } + result.limbs[N - i - 1] = limb; + } + + // max_bits_in_most_significant_byte should be known at comptime. if not...messy! + let mut max_bits_in_most_significant_byte = num_bits - modulus_bits; + if num_bits == modulus_bits { + max_bits_in_most_significant_byte = 8; + } + + let most_significant_byte: Field = x[NBytes - 1] as Field; + most_significant_byte.assert_max_bit_size(max_bits_in_most_significant_byte as u32); + result + } + + fn to_le_bytes(val: BigNum) -> [u8; NBytes] { + let nbytes = (Params::modulus_bits() / 8) + (Params::modulus_bits() % 8 != 0) as u64; + assert(nbytes <= NBytes); + + let mut result: [u8; NBytes] = [0; NBytes]; + for i in 0..N - 1 { + let limb_bytes = val.limbs[i].to_le_bytes(15); + for j in 0..15 { + result[i * 15 + j] = limb_bytes[j]; + } + } + let last_limb_bytes = val.limbs[N - 1].to_le_bytes(15); + let num_last_bytes = (NBytes - (N - 1) * 15); + for i in 0..num_last_bytes { + result[(N-1) * 15 + i] = last_limb_bytes[i]; + } + result + } + + fn get(self) -> [Field] { + self.limbs + } + fn get_limb(self, idx: u64) -> Field { + self.limbs[idx] + } + fn set_limb(&mut self, idx: u64, value: Field) { + self.limbs[idx] = value; + } + + /** + * @brief conditional_select given the value of `predicate` return either `self` (if 0) or `other` (if 1) + * @description should be cheaper than using an IF statement (TODO: check!) + **/ + fn conditional_select(lhs: Self, rhs: Self, predicate: bool) -> Self { + let mut result: Self = lhs; + for i in 0..N { + result.limbs[i] = (lhs.limbs[i] - rhs.limbs[i]) * predicate as Field + rhs.limbs[i]; + } + result + } + + /** + * @brief Validate a BigNum instance is correctly range constrained to contain no more than Params::modulus_bits() + **/ + fn validate_in_range(self) { + for i in 0..(N - 1) { + self.limbs[i].assert_max_bit_size(120); + } + let final_limb_bits = Params::modulus_bits() - ((N - 1) * 120); + self.limbs[N - 1].assert_max_bit_size(final_limb_bits as u32); + } + + /** + * @brief validate quotient produced from `evaluate_quadratic_expression` is well-formed + * @description because the inputs into evaluate_quadratic_expression may cause the quotient to extend beyond `Params::modulus_bits`. + * We allow the quotient to extend 6 bits beyond Params::modulus_bits() + * Why is this? + * several factors: 1. quotient * modulus , limbs cannot overflow field boundary (254 bits) + * 2. in `evaluate_quadratic_expression`, we require that for `expression - quotient * modulus`, + * limbs cannot exceed 246 bits (246 magic number due to a higher number adding extra range check gates) + * because of factor 2 and the fact that modulus limbs are 120 bits, quotient limbs cannot be >126 bits + * + * Note: doesn't this mean that final_limb_bits should be constrained to be 126 bits, not modulus_bits() - ((N - 1) * 120) + 6? + * TODO: think about this more! we want the range constraint we apply to be as small as allowable as this is more efficient + **/ + fn validate_quotient_in_range(self) { + for i in 0..(N) { + self.limbs[i].assert_max_bit_size(120); + } + // Note: replace magic number 6 with definition + let final_limb_bits = Params::modulus_bits() - ((N - 1) * 120) + 6; + self.limbs[N - 1].assert_max_bit_size(final_limb_bits as u32); + } + + fn get_modulus_bits() -> u64 { + Params::modulus_bits() + } + fn num_limbs() -> u64 { + N + } + + fn __is_zero(self) -> bool { + self.__is_zero_impl() + } + + fn __eq(self, rhs: Self) -> bool { + self.__eq_impl(rhs) + } +} + +impl BigNum where Params: BigNumParamsTrait { + + fn from_array(limbs: [Field; N]) -> BigNum { + BigNum { limbs } + } + + unconstrained fn __is_zero_impl(self) -> bool { + let mut result: bool = true; + for i in 0..N { + result = result & (self.limbs[i] == 0); + } + result + } + + unconstrained fn __eq_impl(lhs: Self, rhs: Self) -> bool { + lhs.limbs == rhs.limbs + } +} + +impl BigNumInstanceTrait> for BigNumInstance where Params: BigNumParamsTrait { + + fn modulus(self) -> BigNum { BigNum{ limbs: self.modulus } } + fn __derive_from_seed(self, seed: [u8; SeedBytes]) -> BigNum { + self.__derive_from_seed_impl(seed) + } + // #################################################################################################################### + // #################################################################################################################### + // ### U N C O N S T R A I N E D F U N C T I O N S + // ### NOTE: these functions call unconstrained internal implementations because trait impl modifiers are not supported + // #################################################################################################################### + // #################################################################################################################### + + fn __negate(self, val: BigNum) -> BigNum { + self.__negate_impl(val) + } + + fn __addmod(self, lhs: BigNum, rhs: BigNum) -> BigNum { + self.__addmod_impl(lhs, rhs) + } + + fn __submod(self, lhs: BigNum, rhs: BigNum) -> BigNum { + self.__submod_impl(lhs, rhs) + } + + fn __mulmod(self, lhs: BigNum, rhs: BigNum) -> BigNum { + self.__mulmod_impl(lhs, rhs) + } + + fn __divmod(self, lhs: BigNum, rhs: BigNum) -> BigNum { + self.__divmod_impl(lhs, rhs) + } + + // n.b. needs to be declared unconstrained because we take in a mutable slice + fn __batch_invert(self, x: [BigNum; M]) -> [BigNum; M] { + self.batch_invert_impl(x) + } + + fn __invmod(self, val: BigNum) -> BigNum { + self.__invmod_impl(val) + } + + fn __powmod(self, val: BigNum, exponent: BigNum) -> BigNum { + self.__powmod_impl(val, exponent) + } + + fn __compute_quadratic_expression( + self, + lhs_terms: [[BigNum; LHS_N]; NUM_PRODUCTS], + lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], + rhs_terms: [[BigNum; RHS_N]; NUM_PRODUCTS], + rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], + linear_terms: [BigNum; ADD_N], + linear_flags: [bool; ADD_N] + ) -> (BigNum, BigNum) { + self.__compute_quadratic_expression_impl( + lhs_terms, + lhs_flags, + rhs_terms, + rhs_flags, + linear_terms, + linear_flags + ) + } + + + // #################################################################################################################### + // #################################################################################################################### + // ### C O N S T R A I N E D F U N C T I O N S + // #################################################################################################################### + // #################################################################################################################### + + + /** + * @brief Constrain a degree-2 BigNum expression to be equal to 0 modulo self.modulus + * @description The expression is of the form (when evaluated as an integer relation): + * + * \sum_{i=0}^{NUM_PRODUCTS - 1} ((\sum_{j=0}^{LHS_N-1}lhs[i][j]) * (\sum_{j=0}^{RHS_N-1}rhs[i][j])) + \sum_{i=0}^{ADD_N - 1}linear_terms[i] - quotient * modulus = 0 + * + * The intent is to capture an arbitrary degree-2 expression within the limitations of Noir (no efficient dynamically-sized vectors) + * + * Note: this method requires the remainder term of the expression to be ZERO + * When performing BigNum arithmetic, we want to represent desired BigNum operations in a way that minimizes the number of modular reductions that are required. + * This can be achieved by minimizing the number of degree-2 relations required. + * + * The expensive parts of this algorithm are the following: + * 1. evaluating the limb products required to compute `lhs * rhs` + * 2. applying range constraints to validate the result is 0 + * + * Range constraints are needed for the following reason: + * When evaluating the above expression over N-limb BigNum objects, the result will consist of 2N - 1 limbs. + * Each limb will be in the range [0, ..., 2^{240 + twiddle_factor} - 1] (twiddle_factor needs to be less than 6). + * Because of the subtractions, the limbs may underflow and represent NEGATIVE values. + * To account for this, we allow the Prover to borrow values from more significant limbs and add them into less significant limbs + * (explicitly, we can borrow 2^{126} from limb `i + 1` to add `2^{246}` into `i`). + * To ensure this has been done correctly, we validate that the borrow-adjusted limbs are all-zero for the first 120 bits. + * We do *this* by multiplying the borrow-adjusted limbs by 1 / 2^{120} modulo CircutModulus, and we validate the result is in the range [0, ..., 2^{126} - 1]. + * TODO: explain why this check works. It's statistically sound but not perfectly sound. Chance of the check failing is ~1 in 2^{120} + * I believe this is the most efficient way of performing the zero-check for this relation as it only requires `2N - 2` 126-bit range checks. + * TODO: explain why we apply a 126-bit range check, this feels like a magic number + * (it is. we could go higher, up to the number of bits in the CircuitModulus - 121, but 126 *should be* sufficient and is much cheaper) + * TODO: apply checks in this method to validate twiddle_factor does not exceed 6 + * + * @param lhs_terms a 2D array of BigNum + * @param lhs_flags a 2D array of sign flags + * @param rhs_terms a 2D array of BigNum + * @param rhs_flags a 2D array of sign flags + * @param linear_terms an array of BigNum + * @param linear_flags an array of sign flags + **/ + fn evaluate_quadratic_expression( + self, + lhs_terms: [[BigNum; LHS_N]; NUM_PRODUCTS], + lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], + rhs_terms: [[BigNum; RHS_N]; NUM_PRODUCTS], + rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], + linear_terms: [BigNum; ADD_N], + linear_flags: [bool; ADD_N] + ) { + // use an unconstrained function to compute the value of the quotient + let (quotient, _, borrow_flags): (BigNum, BigNum, ArrayX) = self.__compute_quadratic_expression_with_borrow_flags( + lhs_terms, + lhs_flags, + rhs_terms, + rhs_flags, + linear_terms, + linear_flags + ); + // constrain the quotient to be in the range [0, ..., 2^{m} - 1], where `m` is log2(modulus) rounded up. + // Additionally, validate quotient limbs are also in the range [0, ..., 2^{120} - 1] + quotient.validate_quotient_in_range(); + // TODO, validate we do not overflow N2 when multiplying and N when adding + // (should be a compile-time check...unconstrained function?) + + // Compute the linear sums that represent lhs_1, rhs_1, lhs_2, rhs_2, add + let mut t0: [[Field; N]; NUM_PRODUCTS] = [[0; N]; NUM_PRODUCTS]; + let mut t1: [[Field; N]; NUM_PRODUCTS] = [[0; N]; NUM_PRODUCTS]; + let mut t4: [Field; N] = [0; N]; + + // TODO: this is super nasty as it requires a multiplication + let double_modulus: [Field; N] = self.double_modulus; + for k in 0..NUM_PRODUCTS { + for i in 0..N { + for j in 0..LHS_N { + // note: if is_negative is not known at comptime this is very expensive + if (lhs_flags[k][j]) { + t0[k][i] -= lhs_terms[k][j].limbs[i]; + t0[k][i] += double_modulus[i]; + } else { + t0[k][i] += lhs_terms[k][j].limbs[i]; + } + } + for j in 0..RHS_N { + if (rhs_flags[k][j]) { + t1[k][i] -= rhs_terms[k][j].limbs[i]; + t1[k][i] += double_modulus[i]; + } else { + t1[k][i] += rhs_terms[k][j].limbs[i]; + } + } + } + } + for i in 0..N { + for j in 0..ADD_N { + if (linear_flags[j]) { + t4[i] -= linear_terms[j].limbs[i]; + t4[i] += double_modulus[i]; + } else { + t4[i] += linear_terms[j].limbs[i]; + } + } + } + + // We want to evaluate that t0 * t1 + t2 * t3 + t4 - Quotient * Modulus = 0, evaluated over the integers + // For this we need to be able to borrow values from more-significant limbs into less-significant limbs, + // so that we can ensure that no limbs will underflow for an honest Prover + let mut product_limbs: ArrayX = ArrayX::new(); + // let fff: [Field; N] = quotient.limbs; + // let mut borrow_flags: ArrayX = BigNum::get_borrow_flags3(t0, t1, t2, t3, t4, fff, self.modulus); + + // Compute the product t0 * t1 + t2 * t3 + t4 - Quotient * Modulus + // TODO: this is super nasty as it requires a multiplication + for i in 0..N { + for j in 0..N { + for k in 0..NUM_PRODUCTS { + if k == 0 { + let new_term = t0[k][i] * t1[k][j] - quotient.limbs[i] * self.modulus[j]; + std::as_witness(new_term); // width-4 optimization (n.b. might not be optimal if t2, t3 input arrays are nonzero) + product_limbs.add_assign(i + j, new_term); + } else { + product_limbs.add_assign(i + j, t0[k][i] * t1[k][j]); + } + } + if (NUM_PRODUCTS == 0) { + product_limbs.sub_assign(i + j, quotient.limbs[i] * self.modulus[j]); + } + } + product_limbs.add_assign(i, t4[i]); + } + + // each limb product represents the sum of 120-bit products. + // by setting the borrow value to 2^246 we are restricting this method's completeness to expressions + // where no more than 64 limb products are summed together. + // TODO: check in unconstrained function that this condition is satisfied + // TODO: define trade-offs regarding the value of borrow_shift + // (the larger the value, the greater the range check that is required on product_limbs) + // (126-bit range check is a sweet spot for the barretenberg backend as it decomposes into 9 14-bit range checks) + // (the barretenberg backend can evaluate these in 5.25 gates. 127 bits costs 6.5 gates) + let borrow_shift: Field = 0x40000000000000000000000000000000000000000000000000000000000000; // 2^{246} + let borrow_carry: Field = 0x40000000000000000000000000000000; // 2^{246 - 120} = 2^{126} + + // N.B. borrow_flags is `Field` type because making it `bool` would apply boolean constraints to all `N2` array entries. + // We only use `N2 - 2` borrow flags so applying 1-bit range checks on the array elements we use is more efficient. + // TODO: Once it is possible to perform arithmetic on generics we can use `borrow_flags: [bool;N+N-2]` to avoid this issue + borrow_flags.get(0).assert_max_bit_size(1); + product_limbs.add_assign(0, borrow_flags.get(0) * borrow_shift); + for i in 1..(N + N - 2) { + borrow_flags.get(i).assert_max_bit_size(1); + product_limbs.add_assign( + i, + (borrow_flags.get(i) * borrow_shift - borrow_flags.get(i - 1) * borrow_carry) + ); + } + product_limbs.sub_assign(N + N - 2, borrow_flags.get(N + N - 3) * borrow_carry); + + // Final step: Validate `product_limbs` represents the integer value `0` + // Each element `i` in `product_limbs` overlaps in bitrange with element `i+1`, EXCEPT for the low 120 bits + // i.e. we need to do the following for each limb `i`: + // 1. validate the limb's low-120 bits equals zero + // 2. compute the limb "carry" by right-shifting by 2^{120} + // 3. add the carry into limb `i+1` + // We can efficiently do all of the above by multiplying the limb by 2^{-120} and constraining the result to be <2^{126} + // (if the low 120 bits are nonzero the result will underflow and product a large value that cannot be range constrained) + // (the probability of an underflow value satisfying a 126-bit range constraint is approx. 2^{k - 126}, + // where k is the number of bits in the prime field) + // We then add the result into the next limb and repeat. + let hi_shift: Field = 0x1000000000000000000000000000000; + let hi_downshift: Field = 1 / hi_shift; + for i in 0..N + N - 2 { + product_limbs.mul_assign(i, hi_downshift); + std::as_witness(product_limbs.get(i)); + product_limbs.get(i).assert_max_bit_size(126); // N.B. is this sufficient? going beyond 126 costs us 1 gate per limb + product_limbs.add_assign(i + 1, product_limbs.get(i)); + } + // the most significant limb has no limb to "carry" values into - the entire limb must equal zero + assert(product_limbs.get(N + N - 2) == 0); + } + + fn validate_in_field(self, val: BigNum) { + // N.B. need to combine with validate_in_range if `self` limbs have not been range constrained + let mut p_minus_self: [Field; N] = [0; N]; + let modulus: [Field; N] = self.modulus; + for i in 0..N { + p_minus_self[i] = modulus[i] - val.limbs[i]; + } + let borrow_flags = self.__validate_in_field_compute_borrow_flags(val); + let two_pow_120: Field = 0x1000000000000000000000000000000; + p_minus_self[0] += borrow_flags[0] as Field * two_pow_120; + for i in 1..N - 1 { + p_minus_self[i] += (borrow_flags[i] as Field * two_pow_120 - borrow_flags[i-1] as Field); + } + p_minus_self[N - 1] -= borrow_flags[N - 2] as Field; + let mut compare = val; + compare.limbs = p_minus_self; + compare.validate_in_range(); + } + + /** + * @brief Validate self != other + * @details If A == B, then A == B mod N. + * We can efficiently evaluate A == B mod N where N = circuit modulus + * This method is *sound*, but not *complete* (i.e. A != B but A == B mod N) + * However the probability of an honest Prover being unable to satisfy this check is tiny! + * (todo: compute how tiny) + **/ + fn assert_is_not_equal(self, lhs: BigNum, rhs: BigNum) { + let mut l: Field = 0; + let mut r: Field = 0; + let mut modulus_mod_n: Field = 0; + let mut two_pow_120: Field = 0x1000000000000000000000000000000; + let modulus = self.modulus; + for i in 0..N { + l *= two_pow_120; + r *= two_pow_120; + modulus_mod_n *= two_pow_120; + l += lhs.limbs[N - i - 1]; + r += rhs.limbs[N - i - 1] ; + modulus_mod_n += modulus[N - i - 1]; + } + + // lhs can be either X mod N or P + X mod N + // rhs can be either Y mod N or P + Y mod N + // If lhs - rhs = 0 mod P then lhs - rhs = 0, P or -P mod N + let mut diff = l - r; + let mut target = diff * (diff + modulus_mod_n) * (diff - modulus_mod_n); + assert(target != 0, "asssert_is_not_equal fail"); + } + + fn eq(self, lhs: BigNum, rhs: BigNum) -> bool { + let diff = self.sub(lhs, rhs); + // if self == other, possible values of `diff` will be `p` or `0` + // (the subtract operator constrains diff to be < ceil(log(p))) + // TODO: can do this more efficiently via witngen in unconstrained functions? + let mut is_equal_modulus: bool = true; + let mut is_equal_zero: bool = true; + for i in 0..N { + is_equal_modulus = is_equal_modulus & (diff.limbs[i] == self.modulus[i]); + is_equal_zero = is_equal_zero & (diff.limbs[i] == 0); + } + is_equal_modulus | is_equal_zero + } + + fn neg(self, val: BigNum) -> BigNum { + // so we do... p - x - r = 0 and there might be borrow flags + let (result, borrow_flags) = self.__negate_with_flags(val); + result.validate_in_range(); + let modulus = self.modulus; + let borrow_shift = 0x1000000000000000000000000000000; + let result_limb = modulus[0] - val.limbs[0] - result.limbs[0] + (borrow_flags[0] as Field * borrow_shift); + assert(result_limb == 0); + for i in 1..N - 1 { + let result_limb = modulus[i] - val.limbs[i] - result.limbs[i] - borrow_flags[i - 1] as Field + + (borrow_flags[i] as Field * borrow_shift); + assert(result_limb == 0); + } + let result_limb = modulus[N - 1] - val.limbs[N - 1] - result.limbs[N - 1] - borrow_flags[N - 2] as Field; + assert(result_limb == 0); + result + } + + fn add(self, lhs: BigNum, rhs: BigNum) -> BigNum { + // so we do... p - x - r = 0 and there might be borrow flags + let (result, carry_flags, borrow_flags, overflow_modulus) = self.__add_with_flags(lhs, rhs); + result.validate_in_range(); + let modulus = self.modulus; + let borrow_shift = 0x1000000000000000000000000000000; + let carry_shift = 0x1000000000000000000000000000000; + + let mut subtrahend: [Field; N] = [0; N]; + if (overflow_modulus) { + subtrahend = modulus; + } + let result_limb = lhs.limbs[0] + rhs.limbs[0] - subtrahend[0] - result.limbs[0] + + (borrow_flags[0] as Field * borrow_shift) + - (carry_flags[0] as Field * carry_shift); + assert(result_limb == 0); + for i in 1..N - 1 { + let result_limb = lhs.limbs[i] + rhs.limbs[i] + - subtrahend[i] + - result.limbs[i] + - borrow_flags[i - 1] as Field + + carry_flags[i - 1] as Field + + ((borrow_flags[i] as Field - carry_flags[i] as Field) * borrow_shift); + assert(result_limb == 0); + } + let result_limb = lhs.limbs[N - 1] + rhs.limbs[N - 1] + - subtrahend[N - 1] + - result.limbs[N - 1] + - borrow_flags[N - 2] as Field + + carry_flags[N - 2] as Field; + assert(result_limb == 0); + result + } + + // Note: this method is expensive! Try to craft quadratic relations and directly evaluate them + // via evaluate_quadratic_expression + fn sub(self, lhs: BigNum, rhs: BigNum) -> BigNum { + // so we do... p - x - r = 0 and there might be borrow flags + let (result, carry_flags, borrow_flags, underflow) = self.__sub_with_flags(lhs, rhs); + result.validate_in_range(); + let modulus = self.modulus; + let borrow_shift = 0x1000000000000000000000000000000; + let carry_shift = 0x1000000000000000000000000000000; + + let mut addend: [Field; N] = [0; N]; + if (underflow) { + addend = modulus; + } + let result_limb = lhs.limbs[0] - rhs.limbs[0] + addend[0] - result.limbs[0] + + (borrow_flags[0] as Field * borrow_shift) + - (carry_flags[0] as Field * carry_shift); + assert(result_limb == 0); + for i in 1..N - 1 { + let result_limb = lhs.limbs[i] - rhs.limbs[i] + addend[i] - result.limbs[i] - borrow_flags[i - 1] as Field + + carry_flags[i - 1] as Field + + ((borrow_flags[i] as Field - carry_flags[i] as Field) * borrow_shift); + assert(result_limb == 0); + } + let result_limb = lhs.limbs[N - 1] - rhs.limbs[N - 1] + addend[N - 1] + - result.limbs[N - 1] + - borrow_flags[N - 2] as Field + + carry_flags[N - 2] as Field; + assert(result_limb == 0); + result + } + // Note: this method is expensive! Try to craft quadratic relations and directly evaluate them + // via evaluate_quadratic_expression + // e.g. performing a sum of multiple multiplications and additions via `evaluate_quadratic_expression` + // will create much fewer constraints than calling `mul` and `add` directly + fn mul(self, lhs: BigNum, rhs: BigNum) -> BigNum { + let result = self.__mulmod(lhs, rhs); + self.evaluate_quadratic_expression([[lhs]], [[false]], [[rhs]], [[false]], [result], [true]); + result + } + // Note: this method is expensive! Witness computation is extremely expensive as it requires modular exponentiation + fn div(self, lhs: BigNum, rhs: BigNum) -> BigNum { + let result = self.__divmod(lhs, rhs); + self.evaluate_quadratic_expression([[result]], [[false]], [[rhs]], [[false]], [lhs], [true]); + result + } + +} + +impl BigNumInstance where Params: BigNumParamsTrait { + + // #################################################################################################################### + // #################################################################################################################### + // ### C O N S T R U C T O R S + // #################################################################################################################### + // #################################################################################################################### + + fn new(modulus: [Field; N], redc_param: [Field; N]) -> Self { + Self { redc_param, modulus, double_modulus: get_double_modulus(modulus) } + } + + unconstrained fn __derive_from_seed_impl(self, seed: [u8; SeedBytes]) -> BigNum { + let mut rolling_seed = seed; + + let mut to_reduce: ArrayX = ArrayX { segments: [[0; N], [0; N]] }; + + let mut double_modulus_bits = Params::modulus_bits() * 2; + let mut double_modulus_bytes = (double_modulus_bits) / 8 + (double_modulus_bits % 8 != 0) as u64; + + let mut last_limb_bytes = double_modulus_bytes % 15; + if (last_limb_bytes == 0) { + last_limb_bytes = 15; + } + let mut last_limb_bits = double_modulus_bits % 8; + if (last_limb_bits == 0) { + last_limb_bits = 8; + } + + for i in 0..(N - 1) { + let hash: [u8; 32] = std::hash::sha256(rolling_seed); + let mut lo: Field = 0; + let mut hi: Field = 0; + for j in 0..15 { + hi *= 256; + lo *= 256; + + if (i < 2 * N - 2) { + lo += hash[j + 15] as Field; + hi += hash[j] as Field; + } + } + to_reduce.set(2 * i, lo); + to_reduce.set(2 * i + 1, hi); + rolling_seed[0] += 1; + } + + { + let hash: [u8; 32] = std::hash::sha256(rolling_seed); + let mut hi: Field = 0; + for j in 0..(last_limb_bytes - 1) { + hi *= 256; + hi += hash[j] as Field; + } + hi *= 256; + let last_byte = hash[last_limb_bytes - 1]; + let mask = (1 as u64 << (last_limb_bits) as u8) - 1; + let last_bits = last_byte as u64 & mask; + hi += last_bits as Field; + to_reduce.set(2 * N - 2, hi); + } + + let (_, remainder) = __barrett_reduction(to_reduce, self.redc_param, Params::k(), self.modulus); + let mut result = BigNum::new(); + result.limbs = remainder; + result + } + + // #################################################################################################################### + // #################################################################################################################### + // ### U N C O N S T R A I N E D F U N C T I O N S + // #################################################################################################################### + // #################################################################################################################### + + unconstrained fn __validate_in_field_compute_borrow_flags(self: Self, val: BigNum) -> [bool; N] { + let mut flags: [bool; N] = [false; N]; + let modulus: [Field; N] = self.modulus; + flags[0] = modulus[0].lt(val.limbs[0]); + for i in 1..N - 1 { + flags[i] = modulus[i].lt(val.limbs[i] + flags[i - 1] as Field); + } + flags + } + + unconstrained fn __powmod_impl(self, val: BigNum, exponent: BigNum) -> BigNum { + let x: U60Repr = U60Repr::from(exponent.limbs); + + let num_bits = Params::modulus_bits() + 1; + + let mut accumulator: BigNum = BigNum::one(); + + for i in 0..num_bits { + accumulator = self.__mulmod(accumulator, accumulator); + if x.get_bit(num_bits - i - 1) { + accumulator = self.__mulmod(accumulator, val); + } + } + accumulator + } + + unconstrained fn __mulmod_with_quotient( + self, + lhs: BigNum, + rhs: BigNum + ) -> (BigNum, BigNum) { + let mut mul: ArrayX = ArrayX::new(); + for i in 0..N { + for j in 0..N { + mul.add_assign(i + j, lhs.limbs[i] * rhs.limbs[j]); + } + } + let (q, r) = __barrett_reduction( + mul.__normalize_limbs(N + N), + self.redc_param, + Params::k(), + self.modulus + ); + + let mut quotient = BigNum::from_array(q); + let mut remainder = BigNum::from_array(r); + (quotient, remainder) + } + + unconstrained fn __mulmod_impl(self, lhs: BigNum, rhs: BigNum) -> BigNum { + let (_, b) = self.__mulmod_with_quotient(lhs, rhs); + b + } + + unconstrained fn __addmod_impl(self, lhs: BigNum, rhs: BigNum) -> BigNum { + let x_u60 : U60Repr = U60Repr::from(lhs.limbs); + let y_u60 : U60Repr = U60Repr::from(rhs.limbs); + + let mut z_u60 = x_u60 + y_u60; + + let modulus_u60 : U60Repr = U60Repr::from(self.modulus); + if z_u60.gte(modulus_u60) { + z_u60 = z_u60 - modulus_u60; + } + let mut result = BigNum::from_array(U60Repr::into(z_u60)); + result + } + + /** + * @brief given an input `x`, compute `2p - x` (unconstrained) + * + * @description we subtract the input from double the modulus, because all constrained BigNum operations + * only guarantee that the output is in the range [0, ceil(log2(p))]. + * I.E. the input may be larger than the modulus `p`. + * In order to ensure this operation does not underflow, we compute `2p - x` instead of `p - x`. + * N.B. constrained BigNum operations do not fully constrain outputs to be in the range [0, p-1] + * because such a check is expensive and usually unneccesary. + */ + unconstrained fn __negate_impl(self, val: BigNum) -> BigNum { + let f: [Field; N] = val.limbs; + let x_u60 : U60Repr = U60Repr::from(f); + let modulus_u60 : U60Repr = U60Repr::from(self.modulus); + let mut result = BigNum::from_array(U60Repr::into(modulus_u60 - x_u60)); + result + } + + unconstrained fn __add_with_flags( + self, + lhs: BigNum, + rhs: BigNum + ) -> (BigNum, [bool; N], [bool; N], bool) { + let a_u60 : U60Repr = U60Repr::from(lhs.limbs); + let b_u60 : U60Repr = U60Repr::from(rhs.limbs); + let add_u60 = a_u60 + b_u60; + let modulus_u60 : U60Repr = U60Repr::from(self.modulus); + + let overflow = add_u60.gte(modulus_u60); + + let mut subtrahend_u60 : U60Repr = U60Repr { limbs: ArrayX::new() }; + let mut result_u60 : U60Repr = U60Repr { limbs: ArrayX::new() }; + + if overflow { + subtrahend_u60 = modulus_u60; + } + + let mut carry: u64 = 0; + let mut carry_in: u64 = 0; + let mut borrow: u64 = 0; + let mut borrow_in: u64 = 0; + let mut borrow_flags: [bool; N] = [false; N]; + let mut carry_flags: [bool; N] = [false; N]; + for j in 0..2 { + for i in 0..N { + let mut add_term: u64 = a_u60.limbs.segments[j][i] + b_u60.limbs.segments[j][i] + carry_in; + carry = (add_term >= 0x1000000000000000) as u64; + add_term -= (carry as u64 * 0x1000000000000000); + result_u60.limbs.segments[j][i] = add_term; + carry_in = carry as u64; + borrow = ((subtrahend_u60.limbs.segments[j][i] + borrow_in) > result_u60.limbs.segments[j][i]) as u64; + let sub = (borrow << 60) + result_u60.limbs.segments[j][i] + - subtrahend_u60.limbs.segments[j][i] + - borrow_in; + result_u60.limbs.segments[j][i] = sub; + borrow_in = borrow; + + if ((j * N + i) & 1 == 1) { + let idx = (j * N + i - 1) / 2; + if (carry & borrow == 1) { + carry = 0; + borrow = 0; + } + carry_flags[idx] = carry as bool; + borrow_flags[idx] = borrow as bool; + } + } + } + let mut result = BigNum::from_array(U60Repr::into(result_u60)); + + (result, carry_flags, borrow_flags, overflow) + } + + unconstrained fn __sub_with_flags( + self, + lhs: BigNum, + rhs: BigNum + ) -> (BigNum, [bool; N], [bool; N], bool) { + let a_u60 : U60Repr = U60Repr::from(lhs.limbs); + let b_u60 : U60Repr = U60Repr::from(rhs.limbs); + let modulus_u60 : U60Repr = U60Repr::from(self.modulus); + + let underflow = b_u60.gte(a_u60 + U60Repr::one()); + + let mut addend_u60 : U60Repr = U60Repr { limbs: ArrayX::new() }; + let mut result_u60 : U60Repr = U60Repr { limbs: ArrayX::new() }; + + if underflow { + addend_u60 = modulus_u60; + } + + let mut carry: u64 = 0; + let mut carry_in: u64 = 0; + let mut borrow: u64 = 0; + let mut borrow_in: u64 = 0; + let mut borrow_flags: [bool; N] = [false; N]; + let mut carry_flags: [bool; N] = [false; N]; + for j in 0..2 { + for i in 0..N { + let mut add_term: u64 = a_u60.limbs.segments[j][i] + addend_u60.limbs.segments[j][i] + carry_in; + carry = (add_term >= 0x1000000000000000) as u64; + add_term -= (carry as u64 * 0x1000000000000000); + result_u60.limbs.segments[j][i] = add_term; + carry_in = carry as u64; + borrow = ((b_u60.limbs.segments[j][i] + borrow_in) > result_u60.limbs.segments[j][i]) as u64; + let sub = (borrow << 60) + result_u60.limbs.segments[j][i] + - b_u60.limbs.segments[j][i] + - borrow_in; + result_u60.limbs.segments[j][i] = sub; + borrow_in = borrow; + + if ((j * N + i) & 1 == 1) { + let idx = (j * N + i - 1) / 2; + if (carry & borrow == 1) { + carry = 0; + borrow = 0; + } + carry_flags[idx] = carry as bool; + borrow_flags[idx] = borrow as bool; + } + } + } + let mut result = BigNum::from_array(U60Repr::into(result_u60)); + (result, carry_flags, borrow_flags, underflow) + } + + unconstrained fn __negate_with_flags(self, val: BigNum) -> (BigNum, [bool; N]) { + let f: [Field; N] = val.limbs; + let x_u60 : U60Repr = U60Repr::from(f); + let modulus_u60 : U60Repr = U60Repr::from(self.modulus); + let mut result_u60 : U60Repr = U60Repr { limbs: ArrayX::new() }; + + let mut borrow: u64 = 0; + let mut borrow_in: u64 = 0; + + let mut borrow_flags: [bool; N] = [false; N]; + for j in 0..2 { + for i in 0..N { + borrow = ((x_u60.limbs.segments[j][i] + borrow_in) > modulus_u60.limbs.segments[j][i]) as u64; + let sub = (borrow << 60) + modulus_u60.limbs.segments[j][i] + - x_u60.limbs.segments[j][i] + - borrow_in; + result_u60.limbs.segments[j][i] = sub; + borrow_in = borrow; + if ((j * N + i) & 1 == 1) { + let idx = (j * N + i - 1) / 2; + borrow_flags[idx] = borrow as bool; + } + } + } + let mut result = BigNum::from_array(U60Repr::into(result_u60)); + (result, borrow_flags) + } + + /** + * @brief given inputs `x, y` compute 2p + x - y (unconstrained) + * @description see `__negate` for why we use 2p instead of p + **/ + unconstrained fn __submod_impl(self, lhs: BigNum, rhs: BigNum) -> BigNum { + self.__addmod(lhs, self.__negate(rhs)) + } + + unconstrained fn __invmod_impl(self, val: BigNum) -> BigNum { + let modulus_u60: U60Repr = U60Repr::from(self.modulus); + let one: BigNum = BigNum::one(); + let one_u60: U60Repr = U60Repr::from(one.limbs); + let exponent = modulus_u60.sub(one_u60.add(one_u60)); + let mut result = BigNum::from_array(U60Repr::into(exponent)); + self.__powmod(val, result) + } + + unconstrained fn batch_invert_impl(self, x: [BigNum; M]) -> [BigNum; M] { + // TODO: ugly! Will fail if input slice is empty + let mut accumulator: BigNum = BigNum::one(); + let mut result: [BigNum; M] = [BigNum::new(); M]; + let mut temporaries: [BigNum] = &[]; + for i in 0..x.len() { + temporaries = temporaries.push_back(accumulator); + if (x[i].__is_zero() == false) { + accumulator = self.__mulmod(accumulator, x[i]); + } + } + + accumulator = self.__invmod(accumulator); + let mut T0: BigNum = BigNum::new(); + T0.limbs = [0; N]; + for i in 0..x.len() { + let idx = x.len() - 1 - i; + if (x[idx].__is_zero() == false) { + T0 = self.__mulmod(accumulator, temporaries[idx]); + accumulator = self.__mulmod(accumulator, x[idx]); + result[idx] = T0; + } + } + result + } + + unconstrained fn __divmod_impl(self, numerator: BigNum, divisor: BigNum) -> BigNum { + let t0 = self.__invmod(divisor); + self.__mulmod(numerator, t0) + } + + /** + * @brief Computes the result of a linear combination of (possibly negative) BigNum values (unconstrained) + **/ + // NOTE: modulus2 is structured such that all limbs will be greater than 0, even when subtracting. + // To do this, when computing `p - x`, we ensure that each limb in `p` is greater than each limb in `x`. + // We know that, for a valid bignum element, the limbs in `x` will be <2^{120} + // Therefore each of the limbs in `p` (except the most significant) will borrow 2^{120} from the more significant limb. + // Finally, to ensure we do not underflow in the most significant limb, we use `2p` instead of `p` + unconstrained fn __add_linear_expression( + self, + x: [BigNum; M], + flags: [bool; M] + ) -> ([Field; N]) { + // TODO, validate we do not overflow N2 when multiplying and N when adding + let mut sum: [Field; N] = [0; N]; + // TODO: ugly! Will fail if input array is empty + let modulus2: [Field;N] = self.double_modulus; + for i in 0..M { + if (flags[i]) { + for j in 0..N { + sum[j] = sum[j] + modulus2[j] - x[i].limbs[j]; + assert(x[i].limbs[j].lt(modulus2[j])); + } + } else { + for j in 0..N { + sum[j] = sum[j] + x[i].limbs[j]; + } + } + } + // problem if we normalize when used in computing quotient + sum + // let result_p: ArrayX = BigNum::__normalize_limbs(ArrayX::from_array(lhs_sum_p), N); + // let result_n: ArrayX = BigNum::__normalize_limbs(ArrayX::from_array(lhs_sum_n), N); + // (result_p.segments[0], result_n.segments[0]) + } + + /** + * @brief computes the limb products of a quadratic expression + * @details see __compute_quadratic_expression_with_borrow_flags for full description + **/ + unconstrained fn __compute_quadratic_expression_product( + self, + lhs_terms: [[BigNum; LHS_N]; NUM_PRODUCTS], + lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], + rhs_terms: [[BigNum; RHS_N]; NUM_PRODUCTS], + rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], + linear_terms: [BigNum; ADD_N], + linear_flags: [bool; ADD_N] + ) -> ArrayX { + // TODO, validate we do not overflow N2 when multiplying and N when adding + let mut lhs: [[Field; N]; NUM_PRODUCTS] = [[0; N]; NUM_PRODUCTS]; + let mut rhs: [[Field; N]; NUM_PRODUCTS] = [[0; N]; NUM_PRODUCTS]; + let mut add: [Field; N] = [0; N]; + + for i in 0..NUM_PRODUCTS { + lhs[i] = self.__add_linear_expression(lhs_terms[i], lhs_flags[i]); + rhs[i]= self.__add_linear_expression(rhs_terms[i], rhs_flags[i]); + } + + let add: [Field; N] = self.__add_linear_expression(linear_terms, linear_flags); + + let mut mulout: ArrayX = ArrayX::new(); + + for i in 0..N { + for j in 0..N { + for k in 0..NUM_PRODUCTS { + mulout.add_assign(i + j, (lhs[k][i] * rhs[k][j])); + } + } + mulout.add_assign(i, add[i]); + } + mulout + } + + /** + * @brief computes the quotient/remainder of a quadratic expression + * @details see __compute_quadratic_expression_with_borrow_flags for full description + **/ + unconstrained fn __compute_quadratic_expression_impl( + self, + lhs_terms: [[BigNum; LHS_N]; NUM_PRODUCTS], + lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], + rhs_terms: [[BigNum; RHS_N]; NUM_PRODUCTS], + rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], + linear_terms: [BigNum; ADD_N], + linear_flags: [bool; ADD_N] + ) -> (BigNum, BigNum) { + // TODO, validate we do not overflow N2 when multiplying and N when adding + let mulout = self.__compute_quadratic_expression_product( + lhs_terms, + lhs_flags, + rhs_terms, + rhs_flags, + linear_terms, + linear_flags + ); + let relation_result: ArrayX = mulout.__normalize_limbs(N + N); + + // TODO: ugly! Will fail if input slice is empty + let k = Params::k(); + + let (quotient, remainder) = __barrett_reduction(relation_result, self.redc_param, k, self.modulus); + + let mut q = BigNum::from_array(quotient); + let mut r = BigNum::from_array(remainder); + (q, r) + } + + /** + * @brief Given a degree-2 BigNum expression that is equal to 0 mod p, compute the quotient and borrow flags + * @description The expression is of the form: + * + * \sum_{i=0}^{NUM_PRODUCTS - 1} ((\sum_{j=0}^{LHS_N-1}lhs[i][j]) * (\sum_{j=0}^{RHS_N-1}rhs[i][j])) + \sum_{i=0}^{ADD_N - 1}linear_terms[i] = quotient * modulus + * + * The intent is to capture an arbitrary degree-2 expression within the limitations of Noir (no efficient dynamically-sized vectors) + * + * When performing BigNum arithmetic, we want to represent desired BigNum operations in a way that minimizes the number of modular reductions that are required. + * This can be achieved by minimizing the number of degree-2 relations required. + * + * The borrow flags describe whether individual Field limbs will underflow when evaluating the above relation. + * For example, when computing the product a * b - q * p = 0, it is possible that: + * 1. a[0]*b[0] - p[0]*q[0] = -2^{120} + * 2. a[0]*b[1] + a[1]*b[0] - p[0]*q[1] - p[1]*q[0] = 1 + * In the above example, the value represented by these two limbs is zero despite each limb being nonzero. + * In this case, to correctly constrain the result, we must add (at least) 2^{120} from the first limb and subtract 1 from the second. + * + * @param lhs_terms a 2D array of BigNum + * @param lhs_flags a 2D array of sign flags + * @param rhs_terms a 2D array of BigNum + * @param rhs_flags a 2D array of sign flags + * @param linear_terms an array of BigNum + * @param linear_flags an array of sign flags + **/ + unconstrained fn __compute_quadratic_expression_with_borrow_flags( + self, + lhs_terms: [[BigNum; LHS_N]; NUM_PRODUCTS], + lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], + rhs_terms: [[BigNum; RHS_N]; NUM_PRODUCTS], + rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], + linear_terms: [BigNum; ADD_N], + linear_flags: [bool; ADD_N] + ) -> (BigNum, BigNum, ArrayX) { + // TODO, validate we do not overflow N2 when multiplying and N when adding + + let mut mulout_p = self.__compute_quadratic_expression_product( + lhs_terms, + lhs_flags, + rhs_terms, + rhs_flags, + linear_terms, + linear_flags + ); + let mut mulout_n: ArrayX = ArrayX::new(); + + let relation_result: ArrayX = mulout_p.__normalize_limbs(N + N); + let modulus: [Field; N] = self.modulus; + let (quotient, remainder) = __barrett_reduction(relation_result, self.redc_param, Params::k(), modulus); + assert(remainder == [0; N]); + + for i in 0..N { + for j in 0..N { + mulout_n.add_assign(i + j, quotient[i] * modulus[j]); + } + } + + // compute borrow flags from mulout_p and mulout_n + let mut borrow_flags: ArrayX = ArrayX::new(); + let borrow_shift: Field = 0x40000000000000000000000000000000000000000000000000000000000000; // 2^{246} + let borrow_carry: Field = 0x40000000000000000000000000000000; // 2^{246 - 120} = 2^{126} + let two_pow_120: Field = 0x1000000000000000000000000000000; + let downshift: Field = 1 / two_pow_120; + + // determine whether we need to borrow from more significant limbs. + // initial limb is "simple" comparison operation + // TODO: check how expensive `lt` operator is w.r.t. witness generation + borrow_flags.set(0, mulout_p.get(0).lt(mulout_n.get(0)) as Field); + // we have 2N - 2 borrow flags. The number of limbs from our product computation is 2N - 1 + // and there is nothing to borrow against for the final limb. + let mut hi_bits = (mulout_p.get(0) - mulout_n.get(0) + (borrow_flags.get(0) * borrow_shift)) * downshift; + for i in 1..(N + N - 2) { + // compute the contribution from limb `i-1` that gets added into limb `i`, and add into limb `i` + // let hi_bits = (mulout_p.get(i - 1) - mulout_n.get(i - 1) + (borrow_flags.get(i - 1) * borrow_shift)) + // * downshift; + mulout_p.add_assign(i, hi_bits); + + // determine whether negative limb values are greater than positive limb values + let underflow: Field = mulout_p.get(i).lt(mulout_n.get(i) + (borrow_flags.get(i - 1) * borrow_carry)) as Field; + borrow_flags.set(i, underflow); + + hi_bits = (mulout_p.get(i) - mulout_n.get(i) + (borrow_flags.get(i) * borrow_shift) + - (borrow_flags.get(i - 1) * borrow_carry)) * downshift; + } + + let mut q = BigNum::from_array(quotient); + let mut r = BigNum::from_array(remainder); + (q, r, borrow_flags) + } +} + +fn get_double_modulus(modulus: [Field; N]) -> [Field; N] { + let TWO_POW_120: Field = 0x1000000000000000000000000000000; + let m: U60Repr = U60Repr::from(modulus); + let mut result: [Field; N] = U60Repr::into(m + m); + + result[0] += TWO_POW_120; + for i in 1..N - 1 { + result[i] += (TWO_POW_120 - 1); + } + result[N - 1] -= 1; + result +} + +unconstrained fn __barrett_reduction( + x: ArrayX, + redc_param: [Field; N], + k: u64, + modulus: [Field; N] +) -> ([Field; N], [Field; N]) { + let mut mulout: ArrayX = ArrayX { segments: [[0; N]; 3] }; + for i in 0..(N + N) { + for j in 0..N { + mulout.add_assign(i + j, x.get(i) * redc_param[j]); + } + } + mulout = mulout.__normalize_limbs(3 * N - 1); + let mulout_u60: U60Repr = U60Repr::new(mulout); + let mut quotient_u60 = mulout_u60.shr((k + k)); + + // N.B. we assume that the shifted quotient cannot exceed 2 times original bit size. + // (partial_quotient should be just slightly larger than the modulus, we could probably represent with a size N+1 array) + let partial_quotient: ArrayX = quotient_u60.into_arrayX(); + + // quotient_mul_modulus can never exceed input value `x` so can fit into size-2 array + let mut quotient_mul_modulus: ArrayX = ArrayX { segments: [[0; N]; 2] }; + let mut quotient_mul_modulus_normalized: ArrayX = ArrayX { segments: [[0; N]; 2] }; + for j in 0..N { + for i in 0..(N + N - j) { + quotient_mul_modulus.add_assign(i + j, partial_quotient.get(i) * modulus[j]); + } + } + + for i in 0..(N + N) { + let (lo, hi) = split_bits::split_120_bits(quotient_mul_modulus.get(i)); + quotient_mul_modulus_normalized.set(i, lo); + // TODO: what is faster, leaving this if statement in or out? + // (array is size-1 too large so we can tolerate adding 0 into max element) + if (i + 1 < N + N) { + quotient_mul_modulus.add_assign(i + 1, hi); + } + } + let quotient_mul_modulus_u60: U60Repr = U60Repr::new(quotient_mul_modulus_normalized); + + let modulus_u60: U60Repr = U60Repr::from(modulus); + let x_u60 : U60Repr = U60Repr::new(x); + let mut remainder_u60 = x_u60 - quotient_mul_modulus_u60; + + if (remainder_u60.gte(modulus_u60)) { + remainder_u60 = remainder_u60 - modulus_u60; + quotient_u60.increment(); + } else {} + + let q: [Field; N] = U60Repr::into(quotient_u60); + let r: [Field; N] = U60Repr::into(remainder_u60); + + (q, r) +} diff --git a/src/runtime_bignum_test.nr b/src/runtime_bignum_test.nr new file mode 100644 index 0000000..700741e --- /dev/null +++ b/src/runtime_bignum_test.nr @@ -0,0 +1,552 @@ +use crate::utils::arrayX::ArrayX; +use crate::BigNum; +use crate::runtime_bignum::BigNumInstance; +use crate::runtime_bignum::BigNumParamsTrait; +use crate::utils::u60_representation::U60Repr; +use crate::fields::bn254Fq::BNParams as BNParams; +use crate::fields::BN254Instance; +struct Test2048Params {} +impl BigNumParamsTrait<18> for Test2048Params { + fn k() -> u64 { + 2048 + } + fn modulus_bits() -> u64 { + 2048 + } +} + +fn get_2048_BN_instance() -> BigNumInstance<18, Test2048Params> { + let modulus: [Field; 18] = [ + 0x0000000000000000000000000000000000c0a197a5ae0fcdceb052c9732614fe, + 0x0000000000000000000000000000000000656ae034423283422243918ab83be3, + 0x00000000000000000000000000000000006bf590da48a7c1070b7d5aabaac678, + 0x00000000000000000000000000000000000cce39f530238b606f24b296e2bda9, + 0x000000000000000000000000000000000001e1fef9bb9c1c3ead98f226f1bfa0, + 0x0000000000000000000000000000000000ad8c1c816e12e0ed1379055e373abf, + 0x0000000000000000000000000000000000cebe80e474f753aa9d1461c435123d, + 0x0000000000000000000000000000000000aee5a18ceedef88d115a8b93c167ad, + 0x0000000000000000000000000000000000268ba83c4a65c4307427fc495d9e44, + 0x0000000000000000000000000000000000dd2777926848667b7df79f342639d4, + 0x0000000000000000000000000000000000f455074c96855ca0068668efe7da3d, + 0x00000000000000000000000000000000005ddba6b30bbc168bfb3a1225f27d65, + 0x0000000000000000000000000000000000591fec484f36707524133bcd6f4258, + 0x000000000000000000000000000000000059641b756766aeebe66781dd01d062, + 0x000000000000000000000000000000000058bc5eaff4b165e142bf9e2480eebb, + 0x0000000000000000000000000000000000667a3964f08e06df772ce64b229a72, + 0x00000000000000000000000000000000009c1fdb18907711bfe3e3c1cf918395, + 0x00000000000000000000000000000000000000000000000000000000000000b8 + ]; + let redc_param: [Field; 18] = [ + 0x000000000000000000000000000000000091697def7100cd5cf8d890b4ef2ec3, + 0x00000000000000000000000000000000006765ba8304214dac764d3f4adc3185, + 0x000000000000000000000000000000000048404bd14d927ea230e60d4bebf940, + 0x00000000000000000000000000000000007c4d53a23bacc251ecbfc4b7ba5a0b, + 0x000000000000000000000000000000000093eaf3499474a6f5b2fff83f1259c8, + 0x00000000000000000000000000000000005bff4c737b97281f1a5f2384a8c16d, + 0x000000000000000000000000000000000061b4cf2f55358476b5323782999055, + 0x00000000000000000000000000000000001e7a804e8eacfe3a2a5673bc3885b8, + 0x0000000000000000000000000000000000eabadeae4282906c817adf70eab4ae, + 0x0000000000000000000000000000000000166f7df257fe2bf27f0809aceed9b0, + 0x00000000000000000000000000000000007d90fb7428901b8bed11f6b81e36bf, + 0x0000000000000000000000000000000000f36e6ba885c60b7024c563605df7e0, + 0x000000000000000000000000000000000052b7c58d2fb5d2c8478963ae6d4a44, + 0x000000000000000000000000000000000036ee761de26635f114ccc3f7d74f85, + 0x0000000000000000000000000000000000e3fb726a10cf2220897513f05243de, + 0x0000000000000000000000000000000000f43a26bbd732496eb4d828591b8056, + 0x0000000000000000000000000000000000ff4e42304e60fb3a54fca735499f2c, + 0x0000000000000000000000000000000000000000000000000000000000000162 + ]; + BigNumInstance::new(modulus, redc_param) +} + +type Fq = BigNum<3, BNParams>; +type FqInstance = BigNumInstance<3, BNParams>; +type Fqq = BigNum<18, Test2048Params>; +type FqqInstance = BigNumInstance<18, Test2048Params>; + +fn test_eq(BNInstance: BigNumInstance) where Params: BigNumParamsTrait { + let a: BigNum = BNInstance.__derive_from_seed([1, 2, 3, 4]); + let b: BigNum = BNInstance.__derive_from_seed([1, 2, 3, 4]); + let c: BigNum = BNInstance.__derive_from_seed([2, 2, 3, 4]); + + let modulus: BigNum = BNInstance.modulus(); + let t0: U60Repr = (U60Repr::from(modulus.limbs)); + let t1: U60Repr = (U60Repr::from(b.limbs)); + let b_plus_modulus: BigNum = BigNum { limbs: U60Repr::into(t0 + t1) }; + + assert(BNInstance.eq(a, b) == true); + assert(BNInstance.eq(a, b_plus_modulus) == true); + assert(BNInstance.eq(c, b) == false); + assert(BNInstance.eq(c, a) == false); +} + +// 98760 +// 99689 +// 929 gates for a 2048 bit mul +fn test_mul(BNInstance: BigNumInstance) where Params: BigNumParamsTrait { + let a: BigNum = BNInstance.__derive_from_seed([1, 2, 3, 4]); + let b: BigNum = BNInstance.__derive_from_seed([4, 5, 6, 7]); + + let c = BNInstance.mul(BNInstance.add(a, b), BNInstance.add(a, b)); + let d = BNInstance.add( + BNInstance.add( + BNInstance.add(BNInstance.mul(a, a), BNInstance.mul(b, b)), + BNInstance.mul(a, b) + ), + BNInstance.mul(a, b) + ); + assert(BNInstance.eq(c, d)); +} + +fn test_add(bn: BigNumInstance) where Params: BigNumParamsTrait { + let a: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); + let b: BigNum = bn.__derive_from_seed([4, 5, 6, 7]); + let one: BigNum = BigNum::one(); + a.validate_in_range(); + bn.validate_in_field(a); + b.validate_in_range(); + bn.validate_in_field(b); + + let mut c = bn.add(a, b); + c = bn.add(c, c); + let d = bn.mul(bn.add(a, b), bn.add(one, one)); + assert(bn.eq(c, d)); + + let e = bn.add(one, one); + for i in 1..N { + assert(e.limbs[i] == 0); + } + assert(e.limbs[0] == 2); +} + +fn test_div(bn: BigNumInstance) where Params: BigNumParamsTrait { + let a: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); + let b: BigNum = bn.__derive_from_seed([4, 5, 6, 7]); + + let c = bn.div(a, b); + assert(bn.eq(bn.mul(b, c), a)); +} + +fn test_invmod(bn: BigNumInstance) where Params: BigNumParamsTrait { + let u: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); + for _ in 0..1 { + let v = bn.__invmod(u); + let result = bn.__mulmod(u, v); + let expected: BigNum = BigNum::one(); + assert(result.limbs == expected.limbs); + } +} + +fn assert_is_not_equal(bn: BigNumInstance) where Params: BigNumParamsTrait { + let a: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); + let b: BigNum = bn.__derive_from_seed([4, 5, 6, 7]); + + bn.assert_is_not_equal(a, b); +} + +fn assert_is_not_equal_fail(bn: BigNumInstance) where Params: BigNumParamsTrait { + let a: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); + let b: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); + + bn.assert_is_not_equal(a, b); +} + +fn assert_is_not_equal_overloaded_lhs_fail(bn: BigNumInstance) where Params: BigNumParamsTrait { + let a: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); + let b: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); + + let modulus = bn.modulus(); + + let t0: U60Repr = U60Repr::from(a.limbs); + let t1: U60Repr = U60Repr::from(modulus.limbs); + let a_plus_modulus: BigNum = BigNum { limbs: U60Repr::into(t0 + t1) }; + bn.assert_is_not_equal(a_plus_modulus, b); +} + +fn assert_is_not_equal_overloaded_rhs_fail(bn: BigNumInstance) where Params: BigNumParamsTrait { + let a: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); + let b: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); + + let modulus = bn.modulus(); + + let t0: U60Repr = U60Repr::from(b.limbs); + let t1: U60Repr = U60Repr::from(modulus.limbs); + let b_plus_modulus: BigNum = BigNum { limbs: U60Repr::into(t0 + t1) }; + bn.assert_is_not_equal(a, b_plus_modulus); +} + +fn assert_is_not_equal_overloaded_fail(bn: BigNumInstance) where Params: BigNumParamsTrait { + let a: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); + let b: BigNum = bn.__derive_from_seed([1, 2, 3, 4]); + + let modulus = bn.modulus(); + + let t0: U60Repr = U60Repr::from(a.limbs); + let t1: U60Repr = U60Repr::from(b.limbs); + let t2: U60Repr = U60Repr::from(modulus.limbs); + let a_plus_modulus: BigNum = BigNum { limbs: U60Repr::into(t0 + t2) }; + let b_plus_modulus: BigNum = BigNum { limbs: U60Repr::into(t1 + t2) }; + bn.assert_is_not_equal(a_plus_modulus, b_plus_modulus); +} + +#[test] +fn test_eq_BN() { + let instance = BN254Instance(); + test_eq(instance); +} +#[test] +fn test_add_BN() { + let instance = BN254Instance(); + + let mut a: Fq = instance.modulus(); + let mut b: Fq = instance.modulus(); + let mut expected: Fq = instance.modulus(); + + a.limbs[0] -= 1; + b.limbs[0] -= 1; + expected.limbs[0] -= 2; + + let result = instance.add(a, b); + println(f"result = {result}"); + assert(instance.eq(result, expected)); +} + +#[test] +fn test_sub_test_BN() { + let instance = BN254Instance(); + // 0 - 1 should equal p - 1 + let mut a: Fq = BigNum::new(); + let mut b: Fq = BigNum::one(); + let mut expected: Fq = instance.modulus(); + expected.limbs[0] -= 1; // p - 1 + + let result = instance.sub(a, b); + assert(instance.eq(result, expected)); +} + +#[test] +fn test_sub_modulus_limit() { + let instance = BN254Instance(); + // if we underflow, maximum result should be ... + // 0 - 1 = o-1 + // 0 - p = 0 + let mut a: Fq = BigNum::new(); + let mut b: Fq = instance.modulus(); + let mut expected: Fq = BigNum::new(); + + let result = instance.sub(a, b); + assert(instance.eq(result, expected)); +} + +#[test(should_fail_with = "call to assert_max_bit_size")] +fn test_sub_modulus_underflow() { + let instance = BN254Instance(); + + // 0 - (p + 1) is smaller than p and should produce unsatisfiable constraints + let mut a: Fq = BigNum::new(); + let mut b: Fq = instance.modulus(); + b.limbs[0] += 1; + let mut expected: Fq = BigNum::one(); + + let result = instance.sub(a, b); + + assert(instance.eq(result, expected)); +} + +#[test] +fn test_add_modulus_limit() { + let instance = BN254Instance(); + // p + 2^{254} - 1 should be the maximum allowed value fed into an add operation + // when adding, if the result overflows the modulus, we conditionally subtract the modulus, producing 2^{254} - 1 + // this is the largest value that will satisfy the range check applied when constructing a bignum + let p : U60Repr<3, 2> = U60Repr::from(instance.modulus().limbs); + let two_pow_254_minus_1: U60Repr<3, 2> = U60Repr::from([0xffffffffffffffffffffffffffffff, 0xffffffffffffffffffffffffffffff, 0x3fff]); + let a: Fq = BigNum { limbs: U60Repr::into(p) }; + let b: Fq = BigNum { limbs: U60Repr::into(two_pow_254_minus_1) }; + let result = instance.add(a, b); + assert(instance.eq(result, b)); +} + +#[test(should_fail_with = "call to assert_max_bit_size")] +fn test_add_modulus_overflow() { + let instance = BN254Instance(); + //(2^{254} - 1) + (p - 1) = 2^{254} + p + // after subtracting modulus, result is 2^{254} will does not satisfy the range check applied when constructing a BigNum + let p : U60Repr<3, 2> = U60Repr::from(instance.modulus().limbs); + let two_pow_254_minus_1: U60Repr<3, 2> = U60Repr::from([0xffffffffffffffffffffffffffffff, 0xffffffffffffffffffffffffffffff, 0x3fff]); + let one = U60Repr::from([1, 0, 0]); + let a: Fq = BigNum { limbs: U60Repr::into(p + one) }; + let b: Fq = BigNum { limbs: U60Repr::into(two_pow_254_minus_1) }; + let result = instance.add(a, b); + assert(instance.eq(result, b)); +} + +#[test] +fn test_mul_BN() { + let instance = BN254Instance(); + test_mul(instance); +} + +#[test] +fn test_add_BN2() { + let instance = BN254Instance(); + test_add(instance); +} + +#[test] +fn test_div_BN() { + let instance = BN254Instance(); + test_div(instance); +} + +#[test] +fn test_invmod_BN() { + let instance = BN254Instance(); + test_invmod(instance); +} + +#[test] +fn test_assert_is_not_equal_BN() { + let instance = BN254Instance(); + assert_is_not_equal(instance); +} + +#[test(should_fail_with = "asssert_is_not_equal fail")] +fn test_assert_is_not_equal_fail_BN() { + let instance = BN254Instance(); + assert_is_not_equal_fail(instance); +} + +#[test(should_fail_with = "asssert_is_not_equal fail")] +fn test_assert_is_not_equal_overloaded_lhs_fail_BN() { + let instance = BN254Instance(); + assert_is_not_equal_overloaded_lhs_fail(instance); +} + +#[test(should_fail_with = "asssert_is_not_equal fail")] +fn test_assert_is_not_equal_overloaded_rhs_fail_BN() { + let instance = BN254Instance(); + assert_is_not_equal_overloaded_rhs_fail(instance); +} + +#[test(should_fail_with = "asssert_is_not_equal fail")] +fn test_assert_is_not_equal_overloaded_fail_BN() { + let instance = BN254Instance(); + assert_is_not_equal_overloaded_fail(instance); +} + +#[test] +fn test_eq_2048() { + let instance = get_2048_BN_instance(); + test_eq(instance); +} + +#[test] +fn test_mul_2048() { + let instance = get_2048_BN_instance(); + test_mul(instance); +} + +#[test] +fn test_add_2048() { + let instance = get_2048_BN_instance(); + test_add(instance); +} + +#[test] +fn test_assert_is_not_equal_2048() { + let instance = get_2048_BN_instance(); + assert_is_not_equal(instance); +} + +#[test(should_fail_with = "asssert_is_not_equal fail")] +fn test_assert_is_not_equal_fail_2048() { + let instance = get_2048_BN_instance(); + assert_is_not_equal_fail(instance); +} + +#[test(should_fail_with = "asssert_is_not_equal fail")] +fn test_assert_is_not_equal_overloaded_lhs_fail_2048() { + let instance = get_2048_BN_instance(); + assert_is_not_equal_overloaded_lhs_fail(instance); +} + +#[test(should_fail_with = "asssert_is_not_equal fail")] +fn test_assert_is_not_equal_overloaded_rhs_fail_2048() { + let instance = get_2048_BN_instance(); + assert_is_not_equal_overloaded_rhs_fail(instance); +} + +#[test(should_fail_with = "asssert_is_not_equal fail")] +fn test_assert_is_not_equal_overloaded_fail_2048() { + let instance = get_2048_BN_instance(); + assert_is_not_equal_overloaded_fail(instance); +} + +// N.B. witness generation times make these tests take ~15 minutes each! Uncomment at your peril +// #[test] +// fn test_div_2048() { +// let instance = get_2048_BN_instance(); +// test_div(instance); +// } + +// N.B. witness generation times make these tests take ~15 minutes each! Uncomment at your peril +// #[test] +// fn test_invmod_2048() { +// let instance = get_2048_BN_instance(); +// test_invmod(instance); +// } + +#[test] +fn test_2048_bit_quadratic_expression() { + let instance = get_2048_BN_instance(); + let a: [Field; 18] = [ + 0x000000000000000000000000000000000083684820ff40795b8d9f1be2220cba, + 0x0000000000000000000000000000000000d4924fbdc522b07b6cd0ef5508fd66, + 0x0000000000000000000000000000000000d48f6c43c5930f3d70d6db09a48f4a, + 0x0000000000000000000000000000000000e7f72b2c0756704bea85be38352b34, + 0x00000000000000000000000000000000008337197826e2e9ea000ed5b05d5ac5, + 0x000000000000000000000000000000000040680101b43f6d17de8e3507f3d820, + 0x00000000000000000000000000000000000c6ba0cdcf77cff1c10355ea48d387, + 0x0000000000000000000000000000000000e51717a72902214a9dbeb90e4f225f, + 0x0000000000000000000000000000000000c1bd5bec78406b691f71cbcddb4574, + 0x00000000000000000000000000000000001ce5e532cfb306d7b52e7d9f1aa442, + 0x000000000000000000000000000000000019575932f75ddf00595b22782e1ba2, + 0x0000000000000000000000000000000000d630b3fbf0a9e55861e4399900feb9, + 0x0000000000000000000000000000000000d6b37aeb2daa8d2e2f7e29b0f7752a, + 0x0000000000000000000000000000000000e9cacdd93406256b9eb46b73948849, + 0x00000000000000000000000000000000001400e1f0a38695db66993fe042c48b, + 0x0000000000000000000000000000000000e1d829cb4fa8cabb7d0265efbd8527, + 0x000000000000000000000000000000000055f1a92a5dd099ef2bcd89ac175b52, + 0x00000000000000000000000000000000000000000000000000000000000000fc + ]; + let b: [Field; 18] = [ + 0x0000000000000000000000000000000000c5694493e9bcc76e68dfcf73e0fde1, + 0x0000000000000000000000000000000000ede5e4b8b3e0dec1f4705c35521620, + 0x00000000000000000000000000000000007aa800bab1b33eda0f07695af6c583, + 0x000000000000000000000000000000000045892edea2c02bf0b8b1d2d9a4ebcc, + 0x00000000000000000000000000000000004dffb06bf396f3d0a5b67cff714bdd, + 0x00000000000000000000000000000000004d691db495235e1e032f1ef3e90274, + 0x0000000000000000000000000000000000d92c069d0f2675b2f46cb497aa62d4, + 0x00000000000000000000000000000000003d3f23584f113cef1a4b8b7d183f5c, + 0x0000000000000000000000000000000000289ba11d897837f9cec57dcc430bfc, + 0x0000000000000000000000000000000000765dc64f6ed4a6efd7b26c38f79e59, + 0x00000000000000000000000000000000008edf31fabf5c330ecf7f92fb6487cd, + 0x000000000000000000000000000000000053392f8b14dd78af702b3be2e0d557, + 0x000000000000000000000000000000000034abf357bfd56e9786a7e47ed9a5ae, + 0x0000000000000000000000000000000000a9ebb234064c8ab10d4e7900d4b973, + 0x00000000000000000000000000000000002a6850cce14a20463913002ddc0fa6, + 0x0000000000000000000000000000000000a97e3b06586bfa62325ef7557ab536, + 0x0000000000000000000000000000000000b942b0d26e5be2e08cd425107c59f7, + 0x0000000000000000000000000000000000000000000000000000000000000031 + ]; + let c_expected: [Field; 18] = [ + 0x00000000000000000000000000000000004518a874adebbcf963fed876dfcf78, + 0x00000000000000000000000000000000002b1535070c2deca63e2dc7145a9997, + 0x0000000000000000000000000000000000d9b738665a290c09f09202043d9387, + 0x0000000000000000000000000000000000c88853b11034fe12661eb7a5e41ca7, + 0x0000000000000000000000000000000000357cc4053e7eb127abc2c1430972a1, + 0x0000000000000000000000000000000000224df5e1be31a51562f8574027a992, + 0x000000000000000000000000000000000070ad9287e6326d534f1d2835e159ad, + 0x00000000000000000000000000000000000efa138f75f20b5117955e15bbb447, + 0x0000000000000000000000000000000000d9f45c310be1865ad23fbcdeb1d93f, + 0x00000000000000000000000000000000004f74ca4cf3df59a83f2df796fc9beb, + 0x0000000000000000000000000000000000ed1801428ebf7db771deb45f4311eb, + 0x00000000000000000000000000000000002ded3b46e3a84cda43157d4d927162, + 0x00000000000000000000000000000000009bcd6ac8f90601a44a84a026d4b383, + 0x0000000000000000000000000000000000ab098478b39031a1de85062fd5712b, + 0x00000000000000000000000000000000004432a79276f4375ff3ec2ced8b6cf6, + 0x0000000000000000000000000000000000a0922d75e96e3f9e31c0cbbcbd708a, + 0x00000000000000000000000000000000004013822c9e9aa5b5b1e9c33e4332b7, + 0x0000000000000000000000000000000000000000000000000000000000000058 + ]; + + let a_bn: BigNum<18, Test2048Params> = BigNum { limbs: a }; + let b_bn: BigNum<18, Test2048Params> = BigNum { limbs: b }; + let c_bn = instance.__mulmod(a_bn, b_bn); + assert(c_bn.limbs == c_expected); + + a_bn.validate_in_range(); + + instance.evaluate_quadratic_expression([[a_bn]], [[false]], [[b_bn]], [[false]], [c_bn], [true]); +} + +#[test] +fn test_expressions() { + let instance = BN254Instance(); + let x: [Field; 6] = [ + 0x000000000000000000000000000000000083684820ff40795b8d9f1be2220cba, 0x0000000000000000000000000000000000d4924fbdc522b07b6cd0ef5508fd66, 0x0000000000000000000000000000000000d48f6c43c5930f3d70d6db09a48f4a, + 0x0000000000000000000000000000000000e7f72b2c0756704bea85be38352b34, 0x00000000000000000000000000000000000000000000000000000000b05d5ac5, 0 + ]; + + let y: Fq = BigNum { + limbs: [ + 0x1, + 0x1, + 0x0 + ] + }; + let z: Fq = BigNum { + limbs: [ + 0x2, + 0x2, + 0x0 + ] + }; + let yy = instance.__addmod(y, y); + + assert(yy.limbs == z.limbs); + + let uu: Fq = BigNum { + limbs: [ + 0x0000000000000000000000000000000000b4a832748da6ad742a1fd81b787643, + 0x00000000000000000000000000000000009575f594e04080471712c1d7f18e89, + 0x000000000000000000000000000000000000000000000000000000000000063 + ] + }; + let vv: Fq = BigNum { + limbs: [ + 0x0000000000000000000000000000000000b4aec2748da6ad742a1fd81b787643, + 0x00000000000000000000000000000000009575f594e0408047171a01d7f18e89, + 0x0000000000000000000000000000000000000000000000000000000000000062 + ] + }; + let w: Fq = BigNum { + limbs: [ + 0x0000000000000000000000000000000000b4a832748da6ad742a1fd81b787643, + 0x00000000000000000000000000000000009575f594e04080471712c1d7f18e89, + 0x0000000000000000000000000000000000000000000000000000000000001f93 + ] + }; + let x: Fq = BigNum { + limbs: [ + 0x0000000000000000000000000000000000b4aec2748da6ad742a1fd81b787643, + 0x00000000000000000000000000000000009575f594e0408047171a01d7f18e89, + 0x0000000000000000000000000000000000000000000000000000000000000f93 + ] + }; + let wx = instance.__mulmod(w, x); + let uv = instance.__mulmod(uu, vv); + let y = instance.__negate(instance.__addmod(uv, wx)); + let z = instance.__addmod(uv, wx); + + instance.evaluate_quadratic_expression( + [[uu], [w]], + [[false], [false]], + [[vv], [x]], + [[false], [false]], + [z], + [true] + ); + instance.evaluate_quadratic_expression( + [[uu], [w]], + [[false], [false]], + [[vv], [x]], + [[false], [false]], + [y], + [false] + ); + + let wx_constrained = instance.mul(w, x); + assert(wx_constrained.limbs == wx.limbs); +}