diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 83fbfde5..07732ad1 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -17,7 +17,7 @@ jobs: - name: Install Nargo uses: noir-lang/noirup@v0.1.4 with: - toolchain: 1.0.0-beta.3 + toolchain: 1.0.0-beta.4 - name: Install bb run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6d634d65..1a6aa674 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ on: env: CARGO_TERM_COLOR: always - MINIMUM_NOIR_VERSION: v1.0.0-beta.3 + MINIMUM_NOIR_VERSION: v1.0.0-beta.4 jobs: noir-version-list: diff --git a/src/benchmarks/bignum_benchmarks.nr b/src/benchmarks/bignum_benchmarks.nr index a1573f63..0e330bec 100644 --- a/src/benchmarks/bignum_benchmarks.nr +++ b/src/benchmarks/bignum_benchmarks.nr @@ -1,4 +1,8 @@ -use crate::bignum::BigNum; +use crate::bignum::{BigNum, BigNumTrait}; +use crate::fields::bls12_381Fq::BLS12_381_Fq; +use crate::fields::bn254Fq::BN254_Fq; +use crate::fields::U2048::U2048; +use crate::fields::U256::U256; comptime fn make_bench(_m: Module, N: u32, MOD_BITS: u32, params: Quoted) -> Quoted { let module_name = _m.name(); @@ -14,7 +18,11 @@ comptime fn make_bench(_m: Module, N: u32, MOD_BITS: u32, params: Quoted) -> Quo f"evaluate_quadratic_expression_3_elements_{module_name}".quoted_contents(); let evaluate_quadratic_expression_12_elements_bench_name = f"evaluate_quadratic_expression_12_elements_{module_name}".quoted_contents(); - + let to_be_bytes_bench_name = f"to_be_bytes_{module_name}".quoted_contents(); + let from_be_bytes_bench_name = f"from_be_bytes_{module_name}".quoted_contents(); + let to_le_bytes_bench_name = f"to_le_bytes_{module_name}".quoted_contents(); + let from_le_bytes_bench_name = f"from_le_bytes_{module_name}".quoted_contents(); + let BigNumTrait = quote { crate::bignum::BigNumTrait }; let BigNum = quote { crate::bignum::BigNum }; let BigNumTrait = quote { crate::bignum::BigNumTrait }; @@ -82,7 +90,27 @@ comptime fn make_bench(_m: Module, N: u32, MOD_BITS: u32, params: Quoted) -> Quo ) { $BigNumTrait::evaluate_quadratic_expression(lhs, lhs_flags, rhs, rhs_flags, add, add_flags) } - + + #[export] + fn $to_be_bytes_bench_name(a: $BigNum<$N, $MOD_BITS, $params>) -> [u8; ($MOD_BITS+7) / 8] { + $BigNum::<$N, $MOD_BITS, $params> ::to_be_bytes(a) + } + + #[export] + fn $from_be_bytes_bench_name(a: [u8; ($MOD_BITS+7) / 8]) -> $BigNum<$N, $MOD_BITS, $params> { + $BigNum::<$N, $MOD_BITS, $params> ::from_be_bytes(a) + } + + #[export] + fn $to_le_bytes_bench_name(a: $BigNum<$N, $MOD_BITS, $params>) -> [u8; ($MOD_BITS+7) / 8] { + $BigNum::<$N, $MOD_BITS, $params> ::to_le_bytes(a) + } + + #[export] + fn $from_le_bytes_bench_name(a: [u8; ($MOD_BITS+7) / 8]) -> $BigNum<$N, $MOD_BITS, $params> { + $BigNum::<$N, $MOD_BITS, $params> ::from_le_bytes(a) + } + } } @@ -93,21 +121,25 @@ comptime fn make_bench(_m: Module, N: u32, MOD_BITS: u32, params: Quoted) -> Quo // type U2048 #[make_bench(3, 254, quote { BN254_Fq_Params })] pub mod BN254_Fq_Bench { + use crate::bignum::BigNumTrait; use crate::fields::bn254Fq::BN254_Fq_Params; } #[make_bench(3, 257, quote { U256Params })] pub mod U256_Bench { + use crate::bignum::BigNumTrait; use crate::fields::U256::U256Params; } #[make_bench(4, 381, quote { BLS12_381_Fq_Params })] pub mod BLS12_381Fq_Bench { + use crate::bignum::BigNumTrait; use crate::fields::bls12_381Fq::BLS12_381_Fq_Params; } #[make_bench(18, 2049, quote { U2048Params })] pub mod U2048_Bench { + use crate::bignum::BigNumTrait; use crate::fields::U2048::U2048Params; } diff --git a/src/bignum.nr b/src/bignum.nr index 84f558f9..f3f47c28 100644 --- a/src/bignum.nr +++ b/src/bignum.nr @@ -10,7 +10,7 @@ use crate::fns::{ validate_in_range, }, expressions::{__compute_quadratic_expression, evaluate_quadratic_expression}, - serialization::{from_be_bytes, to_le_bytes}, + serialization::{from_be_bytes, from_le_bytes, to_be_bytes, to_le_bytes}, unconstrained_ops::{ __add, __batch_invert, __batch_invert_slice, __derive_from_seed, __div, __eq, __invmod, __is_zero, __mul, __neg, __pow, __sub, __tonelli_shanks_sqrt, __udiv_mod, @@ -24,6 +24,7 @@ pub struct BigNum { // We aim to avoid needing to add a generic parameter to this trait, for this reason we do not allow // accessing the limbs of the bignum except through slices. pub trait BigNumTrait: Neg + Add + Sub + Mul + Div + Eq { + let MOD_BITS: u32; // TODO: this crashes the compiler? v0.32 // fn default() -> Self { std::default::Default::default () } fn new() -> Self; @@ -32,8 +33,10 @@ pub trait BigNumTrait: Neg + Add + Sub + Mul + Div + Eq { fn derive_from_seed(seed: [u8; SeedBytes]) -> Self; unconstrained fn __derive_from_seed(seed: [u8; SeedBytes]) -> Self; fn from_slice(limbs: [u128]) -> Self; - fn from_be_bytes(x: [u8; NBytes]) -> Self; - fn to_le_bytes(self) -> [u8; NBytes]; + fn from_be_bytes(x: [u8; (MOD_BITS + 7) / 8]) -> Self; + fn to_be_bytes(self) -> [u8; (MOD_BITS + 7) / 8]; + fn from_le_bytes(x: [u8; (MOD_BITS + 7) / 8]) -> Self; + fn to_le_bytes(self) -> [u8; (MOD_BITS + 7) / 8]; fn modulus() -> Self; fn modulus_bits(self) -> u32; @@ -112,7 +115,7 @@ impl BigNumTrait for BigNum, { - + let MOD_BITS: u32 = MOD_BITS; #[deprecated("`BigNum::zero()` is preferred")] fn new() -> Self { Self::zero() @@ -142,12 +145,20 @@ where Self { limbs: limbs.as_array() } } - fn from_be_bytes(x: [u8; NBytes]) -> Self { - Self { limbs: from_be_bytes::<_, MOD_BITS, _>(x) } + fn from_be_bytes(x: [u8; (MOD_BITS + 7) / 8]) -> Self { + Self { limbs: from_be_bytes::<_, MOD_BITS>(x) } + } + + fn to_be_bytes(self) -> [u8; (MOD_BITS + 7) / 8] { + to_be_bytes::<_, MOD_BITS>(self.limbs) + } + + fn from_le_bytes(x: [u8; (MOD_BITS + 7) / 8]) -> Self { + Self { limbs: from_le_bytes::<_, MOD_BITS>(x) } } - fn to_le_bytes(self) -> [u8; NBytes] { - to_le_bytes::<_, MOD_BITS, _>(self.limbs) + fn to_le_bytes(self) -> [u8; (MOD_BITS + 7) / 8] { + to_le_bytes::<_, MOD_BITS>(self.limbs) } fn modulus() -> Self { diff --git a/src/fns/constrained_ops.nr b/src/fns/constrained_ops.nr index 8e203975..fbae31f6 100644 --- a/src/fns/constrained_ops.nr +++ b/src/fns/constrained_ops.nr @@ -207,7 +207,7 @@ pub(crate) fn derive_from_seed( - x: [u8; NBytes], +use crate::utils::map::invert_array; +/// conversions between big endian and little endian byte arrays and BigNum instances +/// the byte serialization should have `(MOD_BITS + 7) / 8` bytes. +/// each 120-bit limb is represented by 15 bytes, and there are fewer bytes for covering the most significant limb +pub(crate) fn from_be_bytes( + x: [u8; (MOD_BITS + 7) / 8], ) -> [u128; N] { - let num_bits = NBytes * 8; + let num_bits = (MOD_BITS + 7) / 8 * 8; assert(num_bits >= MOD_BITS); assert(num_bits - MOD_BITS < 8); let mut result: [u128; N] = [0; N]; - let excess_bytes = N * 15 - NBytes; + let excess_bytes = N * 15 - (MOD_BITS + 7) / 8; let final_limb_bytes = 15 - excess_bytes; - let mut limb: u128 = 0; + let mut limb: Field = 0; let mut k = 0; for _j in 0..final_limb_bytes { limb *= 256; - limb += x[k] as u128; + limb += x[k] as Field; k += 1; } - result[N - 1] = limb; + limb.assert_max_bit_size::<128>(); + result[N - 1] = limb as u128; for i in 1..N { - let mut limb: u128 = 0; + let mut limb: Field = 0; for _j in 0..15 { limb *= 256; - limb += x[k] as u128; + limb += x[k] as Field; k += 1; } - result[N - i - 1] = limb; + limb.assert_max_bit_size::<128>(); + result[N - i - 1] = limb as u128; } let most_significant_byte: Field = x[0] as Field; - most_significant_byte.assert_max_bit_size::<8 - (NBytes * 8 - MOD_BITS)>(); + most_significant_byte.assert_max_bit_size::<8 - ((MOD_BITS + 7) / 8 * 8 - MOD_BITS)>(); result } -pub(crate) fn to_le_bytes( +pub(crate) fn to_be_bytes( val: [u128; N], -) -> [u8; NBytes] { - let nbytes = (MOD_BITS / 8) + (MOD_BITS % 8 != 0) as u32; - assert(nbytes <= NBytes); - - let mut result: [u8; NBytes] = [0; NBytes]; +) -> [u8; (MOD_BITS + 7) / 8] { + let mut result: [u8; (MOD_BITS + 7) / 8] = [0; (MOD_BITS + 7) / 8]; + // the last limb will not have all the 15 bytes so we deal with the full limbs first for i in 0..N - 1 { - let limb_bytes: [u8; 15] = (val[i] as Field).to_le_bytes(); + let index = N - i - 2; + let limb_bytes: [u8; 15] = (val[index] as Field).to_be_bytes(); for j in 0..15 { - result[i * 15 + j] = limb_bytes[j]; + // we leave the space for the first byte empty, which would take (MOD_BITS+7)/8 - MOD_BITS/8 bytes + result[i * 15 + j + (MOD_BITS + 7) / 8 - (N - 1) * 15] = limb_bytes[j]; } } - let last_limb_bytes: [u8; 15] = (val[N - 1] as Field).to_le_bytes(); - let num_last_bytes = (NBytes - (N - 1) * 15); - for i in 0..num_last_bytes { - result[(N - 1) * 15 + i] = last_limb_bytes[i]; + // now we deal with the last limb + let last_limb_bytes: [u8; ((MOD_BITS + 7) / 8 - (N - 1) * 15)] = + (val[N - 1] as Field).to_be_bytes(); + + for i in 0..((MOD_BITS + 7) / 8 - (N - 1) * 15) { + result[i] = last_limb_bytes[i]; } result } + +pub(crate) fn to_le_bytes( + val: [u128; N], +) -> [u8; (MOD_BITS + 7) / 8] { + let result_be: [u8; (MOD_BITS + 7) / 8] = to_be_bytes(val); + let result = invert_array(result_be); + result +} + +pub(crate) fn from_le_bytes( + x: [u8; (MOD_BITS + 7) / 8], +) -> [u128; N] { + // make the bytes big endian + let be_x = invert_array(x); + from_be_bytes(be_x) +} diff --git a/src/runtime_bignum.nr b/src/runtime_bignum.nr index 167684e3..ab952abc 100644 --- a/src/runtime_bignum.nr +++ b/src/runtime_bignum.nr @@ -7,7 +7,7 @@ use crate::fns::{ neg, sub, udiv, udiv_mod, umod, validate_in_field, validate_in_range, }, expressions::{__compute_quadratic_expression, evaluate_quadratic_expression}, - serialization::{from_be_bytes, to_le_bytes}, + serialization::{from_be_bytes, from_le_bytes, to_be_bytes, to_le_bytes}, unconstrained_ops::{ __add, __batch_invert, __batch_invert_slice, __derive_from_seed, __div, __eq, __invmod, __is_zero, __mul, __neg, __pow, __sub, __tonelli_shanks_sqrt, __udiv_mod, @@ -60,15 +60,20 @@ impl RuntimeBigNum { Self { limbs, params } } - pub fn from_be_bytes( - params: BigNumParams, - x: [u8; NBytes], - ) -> Self { - Self { limbs: from_be_bytes::<_, MOD_BITS, _>(x), params } + pub fn from_be_bytes(params: BigNumParams, x: [u8; (MOD_BITS + 7) / 8]) -> Self { + Self { limbs: from_be_bytes::<_, MOD_BITS>(x), params } + } + + pub fn from_le_bytes(params: BigNumParams, x: [u8; (MOD_BITS + 7) / 8]) -> Self { + Self { limbs: from_le_bytes::<_, MOD_BITS>(x), params } + } + + pub fn to_be_bytes(self) -> [u8; (MOD_BITS + 7) / 8] { + to_be_bytes::<_, MOD_BITS>(self.limbs) } - pub fn to_le_bytes(self) -> [u8; NBytes] { - to_le_bytes::<_, MOD_BITS, _>(self.limbs) + pub fn to_le_bytes(self) -> [u8; (MOD_BITS + 7) / 8] { + to_le_bytes::<_, MOD_BITS>(self.limbs) } pub fn modulus(self) -> Self { diff --git a/src/tests/bignum_test.nr b/src/tests/bignum_test.nr index bc01608b..b9868300 100644 --- a/src/tests/bignum_test.nr +++ b/src/tests/bignum_test.nr @@ -975,6 +975,22 @@ fn test_cmp_BN_fuzz(seed: [u8; 5]) { assert(a < modulus_sub_1_div_2); } +#[test] +fn fuzz_from_le_bytes(seed: [u8; 5]) { + let a = BN254_Fq::derive_from_seed(seed); + let bytes = a.to_le_bytes(); + let b = BN254_Fq::from_le_bytes(bytes); + assert(a == b); +} + +#[test] +fn fuzz_to_be_bytes(seed: [u8; 5]) { + let a = BN254_Fq::derive_from_seed(seed); + let bytes = a.to_be_bytes(); + let b = BN254_Fq::from_be_bytes(bytes); + assert(a == b); +} + struct SecP224r1_Params {} type SecP224r1 = BigNum<2, 224, SecP224r1_Params>; diff --git a/src/utils/map.nr b/src/utils/map.nr index 6dc48820..a3ee0d38 100644 --- a/src/utils/map.nr +++ b/src/utils/map.nr @@ -9,3 +9,12 @@ pub(crate) fn map(arr: [T; N], f: fn[Env](T) -> U) -> [U; ret } + +pub(crate) fn invert_array(array: [T; M]) -> [T; M] { + let mut ret: [T; M] = std::mem::zeroed(); + + for i in 0..M { + ret[i] = array[M - i - 1]; + } + ret +}