diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.hpp index 0c94dcdba448..46e09ad54475 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -25,9 +25,6 @@ class BN254 { using TargetField = bb::fq12; static constexpr const char* name = "BN254"; - // TODO(#673): This flag is temporary. It is needed in the verifier classes (GeminiVerifier, etc.) while these - // classes are instantiated with "native" curve types. Eventually, the verifier classes will be instantiated only - // with stdlib types, and "native" verification will be acheived via a simulated builder. static constexpr bool is_stdlib_type = false; // Required by SmallSubgroupIPA argument. This constant needs to divide the size of the multiplicative subgroup of @@ -35,8 +32,9 @@ class BN254 { // each BN254-Flavor, since in every round of Sumcheck, the prover sends Flavor::BATCHED_RELATION_PARTIAL_LENGTH // elements to the verifier. static constexpr size_t SUBGROUP_SIZE = 256; - // BN254's scalar field has a multiplicative subgroup of order 2^28. It is generated by 5. The generator below is - // 5^{2^{20}}. To avoid inversion in the recursive verifier, we also store the inverse of the chosen generator. + // BN254's scalar field has a multiplicative subgroup of order 2^28. It is generated by 5^{(r-1) / 2^28}. The + // generator below is 5^{(r-1) / 2^8} (so it has order 8). To avoid inversion in the recursive verifier, we also + // store the inverse of the chosen generator. static constexpr ScalarField subgroup_generator = ScalarField(uint256_t("0x07b0c561a6148404f086204a9f36ffb0617942546750f230c893619174a57a76")); static constexpr ScalarField subgroup_generator_inverse = diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.test.cpp new file mode 100644 index 000000000000..7e28079a3786 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.test.cpp @@ -0,0 +1,25 @@ +/** + * @brief Tests that verify the correctness of BN-254 field constants + * + */ +#include "bn254.hpp" +#include "barretenberg/numeric/uint256/uint256.hpp" +#include +#include + +using namespace bb; + +// ================================ +// BN254 Constants Tests +// ================================ + +TEST(Bn254Constants, SubgroupGenerator) +{ + fr subgroup_generator = bb::curve::BN254::subgroup_generator; + fr subgroup_generator_inverse = bb::curve::BN254::subgroup_generator_inverse; + fr expected = fr(5).pow((fr::modulus - 1) / (uint256_t(1) << 8)); + fr expected_inverse = expected.invert(); + + EXPECT_EQ(subgroup_generator, expected); + EXPECT_EQ(subgroup_generator_inverse, expected_inverse); +} diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/constants.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/constants.test.cpp deleted file mode 100644 index 8ea794633a3d..000000000000 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/constants.test.cpp +++ /dev/null @@ -1,289 +0,0 @@ -/** - * @brief Tests that verify the correctness of BN-254 field constants (both Fq and Fr). - * - */ -#include "barretenberg/numeric/random/engine.hpp" -#include "barretenberg/numeric/uint256/uint256.hpp" -#include "barretenberg/serialize/test_helper.hpp" -#include "fq.hpp" -#include "fr.hpp" -#include -#include - -using namespace bb; -namespace { -uint256_t native_q{ - Bn254FqParams::modulus_0, Bn254FqParams::modulus_1, Bn254FqParams::modulus_2, Bn254FqParams::modulus_3 -}; -uint256_t native_r{ - Bn254FrParams::modulus_0, Bn254FrParams::modulus_1, Bn254FrParams::modulus_2, Bn254FrParams::modulus_3 -}; - -// Helper to convert a decimal string to uint256_t -uint256_t from_decimal(const std::string& dec_str) -{ - uint256_t result = 0; - for (char c : dec_str) { - result = result * 10 + static_cast(c - '0'); - } - return result; -} -} // namespace - -TEST(FqConstants, Modulus) -{ - // BN254 base field prime: q = 21888242871839275222246405745257275088696311157297823662689037894645226208583 - // References: - // [eip-196](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-196.md) - // [ark-works](https://docs.rs/ark-bn254/latest/ark_bn254/) - // [BN-254 for the rest of us](https://hackmd.io/@jpw/bn254) - uint256_t expected_q = - from_decimal("21888242871839275222246405745257275088696311157297823662689037894645226208583"); - EXPECT_EQ(expected_q, native_q); -} - -TEST(FqConstants, RSquared) -{ - // R^2 = (2^256)^2 mod q. - uint512_t R = (uint512_t(1) << 256) % native_q; - uint512_t expected_r_sqr_mod_q = (R * R) % native_q; - uint256_t actual_r_sqr_mod_q{ - Bn254FqParams::r_squared_0, Bn254FqParams::r_squared_1, Bn254FqParams::r_squared_2, Bn254FqParams::r_squared_3 - }; - EXPECT_EQ(expected_r_sqr_mod_q.lo, actual_r_sqr_mod_q); -} - -TEST(FqConstants, RInv) -{ - // r_inv = -q^{-1} mod 2^64 - uint512_t r{ 0, 1 }; - uint512_t q{ -native_q, 0 }; - uint256_t q_inv = q.invmod(r).lo; - uint64_t expected = q_inv.data[0]; - uint64_t result = Bn254FqParams::r_inv; - EXPECT_EQ(result, expected); -} - -TEST(FqConstants, CubeRootOfUnity) -{ - fq beta = fq::cube_root_of_unity(); - - // Verify beta^3 = 1 and beta != 1 - EXPECT_EQ(beta * beta * beta, fq::one()); - EXPECT_NE(beta, fq::one()); -} - -// ================================ -// WASM Consistency Tests -// ================================ - -TEST(FqConstants, WasmModulusConsistency) -{ - // WASM uses 9 x 29-bit limbs to represent the modulus - // Verify that the 29-bit limb representation reconstructs to the same value as the 4 x 64-bit limb representation - constexpr std::array wasm_limbs = { Bn254FqParams::modulus_wasm_0, Bn254FqParams::modulus_wasm_1, - Bn254FqParams::modulus_wasm_2, Bn254FqParams::modulus_wasm_3, - Bn254FqParams::modulus_wasm_4, Bn254FqParams::modulus_wasm_5, - Bn254FqParams::modulus_wasm_6, Bn254FqParams::modulus_wasm_7, - Bn254FqParams::modulus_wasm_8 }; - - uint512_t wasm_modulus = 0; - for (size_t i = 0; i < 9; i++) { - wasm_modulus += uint512_t(wasm_limbs[i]) << (29UL * i); - // Verify each limb fits in 29 bits - EXPECT_LT(wasm_limbs[i], uint64_t(1) << 29); - } - - EXPECT_EQ(wasm_modulus.lo, native_q); - EXPECT_EQ(wasm_modulus.hi, uint256_t(0)); -} - -TEST(FqConstants, WasmRSquared) -{ - // WASM uses R = 2^261 (since 261 = 29 * 9) - // r_squared_wasm should be (2^261)^2 mod q = 2^522 mod q - uint512_t R_wasm = uint512_t(1) << 261; - uint512_t R_wasm_mod_q = R_wasm % native_q; - uint512_t expected_r_squared_wasm = (R_wasm_mod_q * R_wasm_mod_q) % native_q; - - uint256_t actual_r_squared_wasm{ Bn254FqParams::r_squared_wasm_0, - Bn254FqParams::r_squared_wasm_1, - Bn254FqParams::r_squared_wasm_2, - Bn254FqParams::r_squared_wasm_3 }; - - EXPECT_EQ(expected_r_squared_wasm.lo, actual_r_squared_wasm); -} - -TEST(FqConstants, WasmCubeRootConsistency) -{ - // The cube root in WASM Montgomery form should represent the same field element - // as the cube root in native Montgomery form. - // - // Native Montgomery form: cube_root_native = beta * R_native mod q, where R_native = 2^256 - // WASM Montgomery form: cube_root_wasm = beta * R_wasm mod q, where R_wasm = 2^261 - // - // Therefore: cube_root_wasm = cube_root_native * (R_wasm / R_native) mod q - // = cube_root_native * 2^5 mod q - - uint256_t cube_root_native{ - Bn254FqParams::cube_root_0, Bn254FqParams::cube_root_1, Bn254FqParams::cube_root_2, Bn254FqParams::cube_root_3 - }; - - uint256_t cube_root_wasm{ Bn254FqParams::cube_root_wasm_0, - Bn254FqParams::cube_root_wasm_1, - Bn254FqParams::cube_root_wasm_2, - Bn254FqParams::cube_root_wasm_3 }; - - // R_wasm / R_native = 2^261 / 2^256 = 2^5 = 32 - uint512_t expected_cube_root_wasm = (uint512_t(cube_root_native) * 32) % native_q; - - EXPECT_EQ(expected_cube_root_wasm.lo, cube_root_wasm); -} -// r_inv_wasm represents 2^(-29) mod q in 9 x 29-bit limbs -// this tests checks that that r_inv_wasm < q/2 (and in particular less than q). -TEST(FqConstants, WasmRInvLessThanModulus) -{ - // Verify that when reconstructed as a uint512_t, it is less than the modulus q - constexpr std::array r_inv_wasm_limbs = { Bn254FqParams::r_inv_wasm_0, Bn254FqParams::r_inv_wasm_1, - Bn254FqParams::r_inv_wasm_2, Bn254FqParams::r_inv_wasm_3, - Bn254FqParams::r_inv_wasm_4, Bn254FqParams::r_inv_wasm_5, - Bn254FqParams::r_inv_wasm_6, Bn254FqParams::r_inv_wasm_7, - Bn254FqParams::r_inv_wasm_8 }; - - uint512_t r_inv_wasm = 0; - for (size_t i = 0; i < 9; i++) { - r_inv_wasm += uint512_t(r_inv_wasm_limbs[i]) << (29UL * i); - // Verify each limb fits in 29 bits - EXPECT_LT(r_inv_wasm_limbs[i], uint64_t(1) << 29); - } - - // Verify r_inv_wasm < q/2 - EXPECT_LT(r_inv_wasm, uint512_t(native_q) / 2); -} - -// ================================ -// Fr Constants Tests -// ================================ - -TEST(FrConstants, Modulus) -{ - // BN254 scalar field prime (also the Baby Jubjub base field): - // r = 21888242871839275222246405745257275088548364400416034343698204186575808495617 - // References: - // [eip-196](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-196.md) - // [ark-works](https://docs.rs/ark-bn254/latest/ark_bn254/) - // [BN-254 for the rest of us](https://hackmd.io/@jpw/bn254) - uint256_t expected_r = - from_decimal("21888242871839275222246405745257275088548364400416034343698204186575808495617"); - EXPECT_EQ(expected_r, native_r); -} - -TEST(FrConstants, RSquared) -{ - // R^2 = (2^256)^2 mod r. - uint512_t R = (uint512_t(1) << 256) % native_r; - uint512_t expected_r_sqr_mod_r = (R * R) % native_r; - uint256_t actual_r_sqr_mod_r{ - Bn254FrParams::r_squared_0, Bn254FrParams::r_squared_1, Bn254FrParams::r_squared_2, Bn254FrParams::r_squared_3 - }; - EXPECT_EQ(expected_r_sqr_mod_r.lo, actual_r_sqr_mod_r); -} - -TEST(FrConstants, RInv) -{ - // r_inv = -r^{-1} mod 2^64 - uint512_t two_64{ 0, 1 }; - uint512_t neg_r{ -native_r, 0 }; - uint256_t r_inv = neg_r.invmod(two_64).lo; - uint64_t expected = r_inv.data[0]; - uint64_t result = Bn254FrParams::r_inv; - EXPECT_EQ(result, expected); -} - -TEST(FrConstants, CubeRootOfUnity) -{ - fr beta = fr::cube_root_of_unity(); - - // Verify beta^3 = 1 and beta != 1 - EXPECT_EQ(beta * beta * beta, fr::one()); - EXPECT_NE(beta, fr::one()); -} - -// ================================ -// Fr WASM Consistency Tests -// ================================ - -TEST(FrConstants, WasmModulusConsistency) -{ - // WASM uses 9 x 29-bit limbs to represent the modulus - constexpr std::array wasm_limbs = { Bn254FrParams::modulus_wasm_0, Bn254FrParams::modulus_wasm_1, - Bn254FrParams::modulus_wasm_2, Bn254FrParams::modulus_wasm_3, - Bn254FrParams::modulus_wasm_4, Bn254FrParams::modulus_wasm_5, - Bn254FrParams::modulus_wasm_6, Bn254FrParams::modulus_wasm_7, - Bn254FrParams::modulus_wasm_8 }; - - uint512_t wasm_modulus = 0; - for (size_t i = 0; i < 9; i++) { - wasm_modulus += uint512_t(wasm_limbs[i]) << (29UL * i); - EXPECT_LT(wasm_limbs[i], uint64_t(1) << 29); - } - - EXPECT_EQ(wasm_modulus.lo, native_r); - EXPECT_EQ(wasm_modulus.hi, uint256_t(0)); -} - -TEST(FrConstants, WasmRSquared) -{ - // WASM uses R = 2^261 (since 261 = 29 * 9) - uint512_t R_wasm = uint512_t(1) << 261; - uint512_t R_wasm_mod_r = R_wasm % native_r; - uint512_t expected_r_squared_wasm = (R_wasm_mod_r * R_wasm_mod_r) % native_r; - - uint256_t actual_r_squared_wasm{ Bn254FrParams::r_squared_wasm_0, - Bn254FrParams::r_squared_wasm_1, - Bn254FrParams::r_squared_wasm_2, - Bn254FrParams::r_squared_wasm_3 }; - - EXPECT_EQ(expected_r_squared_wasm.lo, actual_r_squared_wasm); -} - -TEST(FrConstants, WasmCubeRootConsistency) -{ - // The cube root in WASM Montgomery form should represent the same field element - // as the cube root in native Montgomery form. - uint256_t cube_root_native{ - Bn254FrParams::cube_root_0, Bn254FrParams::cube_root_1, Bn254FrParams::cube_root_2, Bn254FrParams::cube_root_3 - }; - - uint256_t cube_root_wasm{ Bn254FrParams::cube_root_wasm_0, - Bn254FrParams::cube_root_wasm_1, - Bn254FrParams::cube_root_wasm_2, - Bn254FrParams::cube_root_wasm_3 }; - - // R_wasm / R_native = 2^261 / 2^256 = 2^5 = 32 - uint512_t expected_cube_root_wasm = (uint512_t(cube_root_native) * 32) % native_r; - - EXPECT_EQ(expected_cube_root_wasm.lo, cube_root_wasm); -} - -// r_inv_wasm represents 2^(-29) mod r in 9 x 29-bit limbs -// this tests verifies that r_inv_wasm < r/2. -TEST(FrConstants, WasmRInvLessThanModulus) -{ - // Verify that when reconstructed as a uint512_t, it is less than the modulus r - constexpr std::array r_inv_wasm_limbs = { Bn254FrParams::r_inv_wasm_0, Bn254FrParams::r_inv_wasm_1, - Bn254FrParams::r_inv_wasm_2, Bn254FrParams::r_inv_wasm_3, - Bn254FrParams::r_inv_wasm_4, Bn254FrParams::r_inv_wasm_5, - Bn254FrParams::r_inv_wasm_6, Bn254FrParams::r_inv_wasm_7, - Bn254FrParams::r_inv_wasm_8 }; - - uint512_t r_inv_wasm = 0; - for (size_t i = 0; i < 9; i++) { - r_inv_wasm += uint512_t(r_inv_wasm_limbs[i]) << (29UL * i); - // Verify each limb fits in 29 bits - EXPECT_LT(r_inv_wasm_limbs[i], uint64_t(1) << 29); - } - - // Verify r_inv_wasm < r/2 - EXPECT_LT(r_inv_wasm, uint512_t(native_r) / 2); -} diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq.hpp index 245ce83fac99..2f66bc815016 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -16,9 +16,16 @@ // NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays) namespace bb { +/** + * @brief Parameters defining the base field of the BN254 curve. + * + * @details When split into 4 64-bit words, the parameters are represented in little-endian, i.e. the least significant + * bit comes first. For example, to recover the modulus from the 64-bit words we concatenate its limbs to obtain: + * 0x30644e72e131a029b85045b68181585d97816a916871ca8d3C208C16D87CFD47 + * + * @note These parameters can be extracted by running the script parameter_helper.py in ecc/fields + */ class Bn254FqParams { - // There is a helper script in ecc/fields/parameter_helper.py that can be used to extract these parameters from the - // source code public: // A little-endian representation of the modulus split into 4 64-bit words static constexpr uint64_t modulus_0 = 0x3C208C16D87CFD47UL; @@ -33,12 +40,40 @@ class Bn254FqParams { static constexpr uint64_t r_squared_2 = 0x47AB1EFF0A417FF6UL; static constexpr uint64_t r_squared_3 = 0x06D89F71CAB8351FUL; + // -(Modulus^-1) mod 2^64 + // This constant is used during multiplication: given an 8-limb representation of the multiplication of two field + // elements, for each of the lowest four limbs we compute: k_i = r_inv * limb_i and we add 2^{64 * i} * k_i * p to + // the result of the multiplication. In this way we zero out the lowest four limbs of the multiplication and we can + // divide by 2^256 by taking the highest four limbs. See field_docs.hpp for more details. + static constexpr uint64_t r_inv = 0x87d20782e4866389UL; + + // 2^(-64) mod Modulus + // Used in the reduction mechanism, see field_docs.md + // Instead of computing k, we multiply the lowest limb by this value and then add to the following 5 limbs. + // This saves us from having to compute k + static constexpr uint64_t r_inv_0 = 0x327d7c1b18f7bd41UL; + static constexpr uint64_t r_inv_1 = 0xdb8ed52f824ed32fUL; + static constexpr uint64_t r_inv_2 = 0x29b67b05eb29a6a1UL; + static constexpr uint64_t r_inv_3 = 0x19ac99126b459ddaUL; + // A little-endian representation of the cube root of 1 in Fq in Montgomery form split into 4 64-bit words static constexpr uint64_t cube_root_0 = 0x71930c11d782e155UL; static constexpr uint64_t cube_root_1 = 0xa6bb947cffbe3323UL; static constexpr uint64_t cube_root_2 = 0xaa303344d4741444UL; static constexpr uint64_t cube_root_3 = 0x2c3b3f0d26594943UL; + // Not used for Fq, but required for all field types + static constexpr uint64_t primitive_root_0 = 0UL; + static constexpr uint64_t primitive_root_1 = 0UL; + static constexpr uint64_t primitive_root_2 = 0UL; + static constexpr uint64_t primitive_root_3 = 0UL; + + // Coset generators in Montgomery form for R=2^256 mod Modulus. Used in FFT-based proving systems + static constexpr uint64_t coset_generator_0 = 0x7a17caa950ad28d7ULL; + static constexpr uint64_t coset_generator_1 = 0x1f6ac17ae15521b9ULL; + static constexpr uint64_t coset_generator_2 = 0x334bea4e696bd284ULL; + static constexpr uint64_t coset_generator_3 = 0x2a1f6744ce179d8eULL; + // A little-endian representation of the modulus split into 9 29-bit limbs // This is used in wasm because we can only do multiplication with 64-bit result instead of 128-bit like in x86_64 static constexpr uint64_t modulus_wasm_0 = 0x187cfd47; @@ -58,6 +93,20 @@ class Bn254FqParams { static constexpr uint64_t r_squared_wasm_2 = 0xff54c5802d3e2632UL; static constexpr uint64_t r_squared_wasm_3 = 0x2a11a68c34ea65a6UL; + // 2^(-29) mod Modulus + // Used in the reduction mechanism, see field_docs.md + // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. + // This saves us from having to compute k + static constexpr uint64_t r_inv_wasm_0 = 0x17789a9f; + static constexpr uint64_t r_inv_wasm_1 = 0x5ffc3dc; + static constexpr uint64_t r_inv_wasm_2 = 0xd6bde42; + static constexpr uint64_t r_inv_wasm_3 = 0x1cf152e3; + static constexpr uint64_t r_inv_wasm_4 = 0x18eb055f; + static constexpr uint64_t r_inv_wasm_5 = 0xed815e2; + static constexpr uint64_t r_inv_wasm_6 = 0x16626d2; + static constexpr uint64_t r_inv_wasm_7 = 0xb8bab0f; + static constexpr uint64_t r_inv_wasm_8 = 0x6d7c4; + // A little-endian representation of the cube root of 1 in Fq in Montgomery form for wasm (R=2^261 mod modulus) // split into 4 64-bit words static constexpr uint64_t cube_root_wasm_0 = 0x62b1a3a46a337995UL; @@ -65,20 +114,20 @@ class Bn254FqParams { static constexpr uint64_t cube_root_wasm_2 = 0x64ee82ede2db85faUL; static constexpr uint64_t cube_root_wasm_3 = 0x0c0afea1488a03bbUL; - // Not used for Fq, but required for all field types - static constexpr uint64_t primitive_root_0 = 0UL; - static constexpr uint64_t primitive_root_1 = 0UL; - static constexpr uint64_t primitive_root_2 = 0UL; - static constexpr uint64_t primitive_root_3 = 0UL; - // Not used for Fq, but required for all field types static constexpr uint64_t primitive_root_wasm_0 = 0x0000000000000000UL; static constexpr uint64_t primitive_root_wasm_1 = 0x0000000000000000UL; static constexpr uint64_t primitive_root_wasm_2 = 0x0000000000000000UL; static constexpr uint64_t primitive_root_wasm_3 = 0x0000000000000000UL; + // Coset generators in Montgomery form for R=2^261 mod Modulus. Used in FFT-based proving systems + static constexpr uint64_t coset_generator_wasm_0 = 0xeb8a8ec140766463ULL; + static constexpr uint64_t coset_generator_wasm_1 = 0xf2b1f20626a3da49ULL; + static constexpr uint64_t coset_generator_wasm_2 = 0xf905ef8d84d5fea4ULL; + static constexpr uint64_t coset_generator_wasm_3 = 0x2958a27c02b7cd5fULL; + // Parameters used for quickly splitting a scalar into two endomorphism scalars for faster scalar multiplication - // For specifics on how these have been derived, ask @zac-williamson + // For specifics on how these have been derived, see ecc/fields/endomorphim_scalars.py static constexpr uint64_t endo_g1_lo = 0x7a7bd9d4391eb18d; static constexpr uint64_t endo_g1_mid = 0x4ccef014a773d2cfUL; static constexpr uint64_t endo_g1_hi = 0x0000000000000002UL; @@ -89,72 +138,6 @@ class Bn254FqParams { static constexpr uint64_t endo_b2_lo = 0x89d3256894d213e2UL; static constexpr uint64_t endo_b2_mid = 0UL; - // -(Modulus^-1) mod 2^64 - // This is used to compute k = r_inv * lower_limb(scalar), such that scalar + k*modulus in integers would have 0 in - // the lowest limb. By performing this sequentially for 4 limbs, we get an 8-limb representation of the scalar, - // where the lowest 4 limbs are zeros. Then we can immediately divide by 2^256 by simply getting rid of the lowest 4 - // limbs - static constexpr uint64_t r_inv = 0x87d20782e4866389UL; - - // 2^(-64) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery - // Instead of computing k, we multiply the lowest limb by this value and then add to the following 5 limbs. - // This saves us from having to compute k - static constexpr uint64_t r_inv_0 = 0x327d7c1b18f7bd41UL; - static constexpr uint64_t r_inv_1 = 0xdb8ed52f824ed32fUL; - static constexpr uint64_t r_inv_2 = 0x29b67b05eb29a6a1UL; - static constexpr uint64_t r_inv_3 = 0x19ac99126b459ddaUL; - - // 2^(-29) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery - // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. - // This saves us from having to compute k - static constexpr uint64_t r_inv_wasm_0 = 0x17789a9f; - static constexpr uint64_t r_inv_wasm_1 = 0x5ffc3dc; - static constexpr uint64_t r_inv_wasm_2 = 0xd6bde42; - static constexpr uint64_t r_inv_wasm_3 = 0x1cf152e3; - static constexpr uint64_t r_inv_wasm_4 = 0x18eb055f; - static constexpr uint64_t r_inv_wasm_5 = 0xed815e2; - static constexpr uint64_t r_inv_wasm_6 = 0x16626d2; - static constexpr uint64_t r_inv_wasm_7 = 0xb8bab0f; - static constexpr uint64_t r_inv_wasm_8 = 0x6d7c4; - - // Coset generators in Montgomery form for R=2^256 mod Modulus. Used in FFT-based proving systems - static constexpr uint64_t coset_generators_0[8]{ - 0x7a17caa950ad28d7ULL, 0x4d750e37163c3674ULL, 0x20d251c4dbcb4411ULL, 0xf42f9552a15a51aeULL, - 0x4f4bc0b2b5ef64bdULL, 0x22a904407b7e725aULL, 0xf60647ce410d7ff7ULL, 0xc9638b5c069c8d94ULL, - }; - static constexpr uint64_t coset_generators_1[8]{ - 0x1f6ac17ae15521b9ULL, 0x29e3aca3d71c2cf7ULL, 0x345c97cccce33835ULL, 0x3ed582f5c2aa4372ULL, - 0x1a4b98fbe78db996ULL, 0x24c48424dd54c4d4ULL, 0x2f3d6f4dd31bd011ULL, 0x39b65a76c8e2db4fULL, - }; - static constexpr uint64_t coset_generators_2[8]{ - 0x334bea4e696bd284ULL, 0x99ba8dbde1e518b0ULL, 0x29312d5a5e5edcULL, 0x6697d49cd2d7a508ULL, - 0x5c65ec9f484e3a79ULL, 0xc2d4900ec0c780a5ULL, 0x2943337e3940c6d1ULL, 0x8fb1d6edb1ba0cfdULL, - }; - static constexpr uint64_t coset_generators_3[8]{ - 0x2a1f6744ce179d8eULL, 0x3829df06681f7cbdULL, 0x463456c802275bedULL, 0x543ece899c2f3b1cULL, - 0x180a96573d3d9f8ULL, 0xf8b21270ddbb927ULL, 0x1d9598e8a7e39857ULL, 0x2ba010aa41eb7786ULL, - }; - - // Coset generators in Montgomery form for R=2^261 mod Modulus. Used in FFT-based proving systems - static constexpr uint64_t coset_generators_wasm_0[8] = { 0xeb8a8ec140766463ULL, 0xfded87957d76333dULL, - 0x4c710c8092f2ff5eULL, 0x9af4916ba86fcb7fULL, - 0xe9781656bdec97a0ULL, 0xfbdb0f2afaec667aULL, - 0x4a5e94161069329bULL, 0x98e2190125e5febcULL }; - static constexpr uint64_t coset_generators_wasm_1[8] = { 0xf2b1f20626a3da49ULL, 0x56c12d76cb13587fULL, - 0x5251d378d7f4a143ULL, 0x4de2797ae4d5ea06ULL, - 0x49731f7cf1b732c9ULL, 0xad825aed9626b0ffULL, - 0xa91300efa307f9c3ULL, 0xa4a3a6f1afe94286ULL }; - static constexpr uint64_t coset_generators_wasm_2[8] = { 0xf905ef8d84d5fea4ULL, 0x93b7a45b84f1507eULL, - 0xe6b99ee0068dfab5ULL, 0x39bb9964882aa4ecULL, - 0x8cbd93e909c74f23ULL, 0x276f48b709e2a0fcULL, - 0x7a71433b8b7f4b33ULL, 0xcd733dc00d1bf56aULL }; - static constexpr uint64_t coset_generators_wasm_3[8] = { 0x2958a27c02b7cd5fULL, 0x06bc8a3277c371abULL, - 0x1484c05bce00b620ULL, 0x224cf685243dfa96ULL, - 0x30152cae7a7b3f0bULL, 0x0d791464ef86e357ULL, - 0x1b414a8e45c427ccULL, 0x290980b79c016c41ULL }; - // used in msgpack schema serialization static constexpr char schema_name[] = "fq"; static constexpr bool has_high_2adicity = false; diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq.test.cpp index 87770420a582..5ac7c972b88e 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq.test.cpp @@ -3,8 +3,8 @@ * * Other field arithmetic tests (both compile-time and runtime) are in ecc/fields/generic_field.test.cpp and * ecc/fields/prime_field.test.cpp. This file contains only BN254-specific functionality: + * - Fixed compile-time tests with field-specific expected values * - Endomorphism scalar decomposition - * - Buffer serialization (tests specific byte layout) * - Regression tests for specific values */ @@ -28,10 +28,9 @@ auto& engine = numeric::get_debug_randomness(); #if defined(__SIZEOF_INT128__) && !defined(__wasm__) TEST(BN254Fq, CompileTimeMultiplication) { - constexpr fq a = uint256_t{ 0xa9b879029c49e60eUL, 0x2517b72250caa7b3UL, 0x6b86c81105dae2d1UL, 0x3a81735d5aec0c3UL }; - constexpr fq b = uint256_t{ 0x744fc10aec23e56aUL, 0x5dea4788a3b936a6UL, 0xa0a89f4a8af01df1UL, 0x72ae28836807df3UL }; - constexpr fq expected = - uint256_t{ 0x6c0a789c0028fd09UL, 0xca9520d84c684efaUL, 0xcbf3f7b023a852b4UL, 0x1b2e4dac41400621UL }; + constexpr fq a{ 0x83aa80986c4f06f8, 0xbd01cce5e3b3afc3, 0x1cba208cb70aa13b, 0x2a582eb35a932e0d }; + constexpr fq b{ 0x348ea47f1840a528, 0x5e6eb8e57e1b246d, 0x10852d3d36002e53, 0x280130d2f6a97aba }; + constexpr fq expected{ 0x67eaddc2ba233427, 0x3c4f7dfe46ef24a9, 0x8fecb77e2ff74d64, 0x275537b321138ee7 }; constexpr fq result = a * b; static_assert(result == expected); @@ -39,9 +38,8 @@ TEST(BN254Fq, CompileTimeMultiplication) TEST(BN254Fq, CompileTimeSquaring) { - constexpr fq a = uint256_t{ 0xa9b879029c49e60eUL, 0x2517b72250caa7b3UL, 0x6b86c81105dae2d1UL, 0x3a81735d5aec0c3UL }; - constexpr fq expected = - uint256_t{ 0x41081a42fdaa7e23UL, 0x44d1140f756ed419UL, 0x53716b0a6f253e63UL, 0xb1a0b04044d75fUL }; + constexpr fq a{ 0x83aa80986c4f06f8, 0xbd01cce5e3b3afc3, 0x1cba208cb70aa13b, 0x2a582eb35a932e0d }; + constexpr fq expected{ 0xe441c0408a6fab60, 0xb94616ade6ed8752, 0x36cb53ba8e85397f, 0x17698305ec38b773 }; constexpr fq result = a.sqr(); static_assert(result == expected); @@ -70,158 +68,12 @@ TEST(BN254Fq, CompileTimeSubtraction) TEST(BN254Fq, CompileTimeInversion) { - constexpr fq a = uint256_t{ 0xa9b879029c49e60eUL, 0x2517b72250caa7b3UL, 0x6b86c81105dae2d1UL, 0x3a81735d5aec0c3UL }; + constexpr fq a{ 0x83aa80986c4f06f8, 0xbd01cce5e3b3afc3, 0x1cba208cb70aa13b, 0x2a582eb35a932e0d }; constexpr fq inv = a.invert(); // Verify a * a^-1 = 1 static_assert(a * inv == fq::one()); } -// ================================ -// Montgomery Form -// ================================ - -TEST(BN254Fq, FromMontgomeryForm) -{ - constexpr fq t0 = fq::one(); - constexpr fq result = t0.from_montgomery_form(); - constexpr fq expected{ 0x01, 0x00, 0x00, 0x00 }; - EXPECT_EQ(result, expected); -} - -TEST(BN254Fq, MontgomeryConsistencyCheck) -{ - fq a = fq::random_element(); - fq b = fq::random_element(); - fq aR; - fq bR; - fq aRR; - fq bRR; - fq bRRR; - fq result_a; - fq result_b; - fq result_c; - fq result_d; - aR = a.to_montgomery_form(); - aRR = aR.to_montgomery_form(); - bR = b.to_montgomery_form(); - bRR = bR.to_montgomery_form(); - bRRR = bRR.to_montgomery_form(); - result_a = aRR * bRR; // abRRR - result_b = aR * bRRR; // abRRR - result_c = aR * bR; // abR - result_d = a * b; // abR^-1 - EXPECT_EQ((result_a == result_b), true); - result_a.self_from_montgomery_form(); // abRR - result_a.self_from_montgomery_form(); // abR - result_a.self_from_montgomery_form(); // ab - result_c.self_from_montgomery_form(); // ab - result_d.self_to_montgomery_form(); // ab - EXPECT_EQ((result_a == result_c), true); - EXPECT_EQ((result_a == result_d), true); -} - -// ================================ -// Arithmetic Consistency -// ================================ - -TEST(BN254Fq, AddMulConsistency) -{ - fq multiplicand = { 0x09, 0, 0, 0 }; - multiplicand.self_to_montgomery_form(); - - fq a = fq::random_element(); - fq result; - result = a + a; // 2 - result += result; // 4 - result += result; // 8 - result += a; // 9 - - fq expected; - expected = a * multiplicand; - - EXPECT_EQ((result == expected), true); -} - -TEST(BN254Fq, SubMulConsistency) -{ - fq multiplicand = { 0x05, 0, 0, 0 }; - multiplicand.self_to_montgomery_form(); - - fq a = fq::random_element(); - fq result; - result = a + a; // 2 - result += result; // 4 - result += result; // 8 - result -= a; // 7 - result -= a; // 6 - result -= a; // 5 - - fq expected; - expected = a * multiplicand; - - EXPECT_EQ((result == expected), true); -} - -TEST(BN254Fq, Invert) -{ - fq input = fq::random_element(); - fq inverse = input.invert(); - fq result = input * inverse; - result = result.reduce_once(); - result = result.reduce_once(); - EXPECT_EQ(result, fq::one()); -} - -TEST(BN254Fq, InvertOneIsOne) -{ - fq result = fq::one(); - result = result.invert(); - EXPECT_EQ((result == fq::one()), true); -} - -TEST(BN254Fq, Sqrt) -{ - fq input = fq::one(); - auto [is_sqr, root] = input.sqrt(); - fq result = root.sqr(); - EXPECT_EQ(result, input); -} - -TEST(BN254Fq, SqrtRandom) -{ - for (size_t i = 0; i < 1; ++i) { - fq input = fq::random_element().sqr(); - auto [is_sqr, root] = input.sqrt(); - fq root_test = root.sqr(); - EXPECT_EQ(root_test, input); - } -} - -TEST(BN254Fq, OneAndZero) -{ - fq result; - result = fq::one() - fq::one(); - EXPECT_EQ((result == fq::zero()), true); -} - -TEST(BN254Fq, Copy) -{ - fq result = fq::random_element(); - fq expected; - fq::__copy(result, expected); - EXPECT_EQ((result == expected), true); -} - -TEST(BN254Fq, Neg) -{ - fq a = fq::random_element(); - fq b; - b = -a; - fq result; - result = a + b; - EXPECT_EQ((result == fq::zero()), true); -} - // ================================ // Endomorphism // ================================ @@ -334,78 +186,10 @@ TEST(BN254Fq, SplitIntoEndomorphismEdgeCase) } } -// ================================ -// Buffer Serialization -// ================================ - -TEST(BN254Fq, SerializeToBuffer) -{ - std::array buffer; - fq a = { 0x1234567876543210, 0x2345678987654321, 0x3456789a98765432, 0x006789abcba98765 }; - a = a.to_montgomery_form(); - - fq::serialize_to_buffer(a, &buffer[0]); - - EXPECT_EQ(buffer[31], 0x10); - EXPECT_EQ(buffer[30], 0x32); - EXPECT_EQ(buffer[29], 0x54); - EXPECT_EQ(buffer[28], 0x76); - EXPECT_EQ(buffer[27], 0x78); - EXPECT_EQ(buffer[26], 0x56); - EXPECT_EQ(buffer[25], 0x34); - EXPECT_EQ(buffer[24], 0x12); - - EXPECT_EQ(buffer[23], 0x21); - EXPECT_EQ(buffer[22], 0x43); - EXPECT_EQ(buffer[21], 0x65); - EXPECT_EQ(buffer[20], 0x87); - EXPECT_EQ(buffer[19], 0x89); - EXPECT_EQ(buffer[18], 0x67); - EXPECT_EQ(buffer[17], 0x45); - EXPECT_EQ(buffer[16], 0x23); - - EXPECT_EQ(buffer[15], 0x32); - EXPECT_EQ(buffer[14], 0x54); - EXPECT_EQ(buffer[13], 0x76); - EXPECT_EQ(buffer[12], 0x98); - EXPECT_EQ(buffer[11], 0x9a); - EXPECT_EQ(buffer[10], 0x78); - EXPECT_EQ(buffer[9], 0x56); - EXPECT_EQ(buffer[8], 0x34); - - EXPECT_EQ(buffer[7], 0x65); - EXPECT_EQ(buffer[6], 0x87); - EXPECT_EQ(buffer[5], 0xa9); - EXPECT_EQ(buffer[4], 0xcb); - EXPECT_EQ(buffer[3], 0xab); - EXPECT_EQ(buffer[2], 0x89); - EXPECT_EQ(buffer[1], 0x67); - EXPECT_EQ(buffer[0], 0x00); -} - -TEST(BN254Fq, SerializeFromBuffer) -{ - std::array buffer; - fq expected = { 0x1234567876543210, 0x2345678987654321, 0x3456789a98765432, 0x006789abcba98765 }; - - fq::serialize_to_buffer(expected, &buffer[0]); - fq result = fq::serialize_from_buffer(&buffer[0]); - - EXPECT_EQ(result, expected); -} - // ================================ // Regression Tests // ================================ -// TEST to check we don't have 0^0=0 -TEST(BN254Fq, PowRegressionCheck) -{ - fq zero = fq::zero(); - fq one = fq::one(); - EXPECT_EQ(zero.pow(uint256_t(0)), one); -} -// AUDITTODO: should we remove this test? TEST(BN254Fq, SqrRegression) { std::array values = { diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq12.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq12.hpp index 0617253cead4..05f8ed6d3a00 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq12.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq12.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -11,6 +11,28 @@ #include "./fq6.hpp" namespace bb { + +/** + * @brief The twelfth degree extension of the base field of BN254 + * + * @details Fq12 is defined as Fq6[w] / (w^2 - v), where v is the variable added to Fq2 to construct Fq6. We store in + * the struct the coefficients to compute the frobenius morphism (we need powers up to q^3 to compute the final + * exponentiation in the pairing calculation) + * 1. Power q + * \f[ + * (a + bw)^q = a^q + b^q * w^q = a^q + b^q * \xi^{(q-1)/6} * v + * \f] + * 2. Power q^2 + * \f[ + * (a + bw)^{q^2} = a^{q^2} + b^{q^2} * w^{q^2} = a + b * \xi^{(q^2-1)/6} * v + * \f] + * 3. Power q^3 + * \f[ + * (a + bw)^{q^3} = a^{q^3} + b^{q^3} * w^{q^3} = a^q + b^q * \xi^{(q^3-1)/6} * v + * \f] + * + * + */ struct Bn254Fq12Params { #if defined(__SIZEOF_INT128__) && !defined(__wasm__) @@ -47,4 +69,4 @@ struct Bn254Fq12Params { }; using fq12 = field12; -} // namespace bb \ No newline at end of file +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq12.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq12.test.cpp index d9e558271001..6324124463d7 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq12.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq12.test.cpp @@ -3,102 +3,6 @@ using namespace bb; -TEST(fq12, Eq) -{ - fq12 a = { { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }, - { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } } }; - fq12 b = { { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }, - { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } } }; - fq12 c = { { { { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } } }, - { { { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } } } }; - fq12 d = { { { { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }, - { { { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } } }; - fq12 e = { { { { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }, - { { { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } } }; - fq12 f = { { { { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }, - { { { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } } }; - fq12 g = { { { { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } } }, - { { { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } } } }; - fq12 h = { { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } } }, - { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } } } }; - fq12 i = { { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } } }, - { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } } } }; - fq12 j = { { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } } }, - { { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } } } }; - - EXPECT_EQ((a == b), true); - EXPECT_EQ((a == c), false); - EXPECT_EQ((a == d), false); - EXPECT_EQ((a == e), false); - EXPECT_EQ((a == f), false); - EXPECT_EQ((a == g), false); - EXPECT_EQ((a == h), false); - EXPECT_EQ((a == i), false); - EXPECT_EQ((a == j), false); -} - -TEST(fq12, IsZero) -{ - fq12 a = fq12::zero(); - fq12 b = fq12::zero(); - fq12 c = fq12::zero(); - b.c0.c0.c0.data[0] = 1; - c.c1.c0.c0.data[0] = 1; - EXPECT_EQ(a.is_zero(), true); - EXPECT_EQ(b.is_zero(), false); - EXPECT_EQ(c.is_zero(), false); -} - -TEST(fq12, RandomElement) -{ - fq12 a = fq12::random_element(); - fq12 b = fq12::random_element(); - - EXPECT_EQ(a == b, false); - EXPECT_EQ(a.is_zero(), false); - EXPECT_EQ(a.is_zero(), false); -} - TEST(fq12, AddCheckAgainstConstants) { fq12 a = { { { { 0xe5090b4f4ae647a8, 0xf5d4801f152fdf6c, 0xcdb69d33dba7f562, 0x228f26abab7d6687 }, @@ -403,22 +307,6 @@ TEST(fq12, SqrCheckAgainstConstants) EXPECT_EQ(result, expected); } -TEST(fq12, Inverse) -{ - fq12 input = fq12::random_element(); - fq12 result = input.invert() * input; - EXPECT_EQ(result, fq12::one()); -} - -TEST(fq12, UnitaryInverse) -{ - fq12 input = fq12::random_element(); - fq12 result = input.unitary_inverse(); - EXPECT_EQ(input.c0, result.c0); - result.c1 += input.c1; - EXPECT_EQ(result.c1, fq6::zero()); -} - TEST(fq12, FrobeniusMapThree) { #if defined(__SIZEOF_INT128__) && !defined(__wasm__) @@ -599,66 +487,44 @@ TEST(fq12, FrobeniusMapOne) EXPECT_EQ(result, expected); } -TEST(fq12, ToMontgomeryForm) -{ - fq12 result = fq12::zero(); - result.c0.c0.c0.data[0] = 1; - fq12 expected = fq12::one(); - result = result.to_montgomery_form(); - EXPECT_EQ(result, expected); -} - -TEST(fq12, FromMontgomeryForm) -{ - fq12 result = fq12::one(); - fq12 expected = fq12::zero(); - expected.c0.c0.c0.data[0] = 1; - result = result.from_montgomery_form(); - EXPECT_EQ(result, expected); -} - -TEST(fq12, MulSqrConsistency) +TEST(fq12, FrobeniusMapSixIsConjugation) { fq12 a = fq12::random_element(); - fq12 b = fq12::random_element(); - fq12 t1 = a - b; - fq12 t2 = a + b; - fq12 mul_result = t1 * t2; - fq12 sqr_result = a.sqr() - b.sqr(); - EXPECT_EQ(mul_result, sqr_result); + fq12 result = a.frobenius_map_three().frobenius_map_three(); + EXPECT_EQ(result.c0, a.c0); + EXPECT_EQ(result.c1, -a.c1); } -TEST(fq12, AddMulConsistency) +TEST(fq12, UnitaryInverse) { - fq12 multiplicand = fq12::zero(); - multiplicand.c0.c0.c0.data[0] = 9; - multiplicand = multiplicand.to_montgomery_form(); - - fq12 a = fq12::random_element(); - fq12 result = a + a; - result += result; - result += result; - result += a; - - fq12 expected = a * multiplicand; - - EXPECT_EQ(result, expected); + // Unitary elements can be obtained by taking a random element and raising it to the power + // (q^12 - 1) / (q^4 - q^2 + 1) = (q^2 + 1) (q^6 - 1) + // Note that q^4 - q^2 + 1 is the cyclotomic polynomial of degree 12 evaluated at q + fq12 unitary_input = fq12::random_element(); + unitary_input = unitary_input.frobenius_map_two() * unitary_input; + unitary_input = unitary_input.frobenius_map_three().frobenius_map_three() * unitary_input.invert(); + fq12 result = unitary_input.unitary_inverse(); + + EXPECT_EQ(unitary_input * result, fq12::one()); + EXPECT_EQ(result.c0, unitary_input.c0); + EXPECT_EQ(result.c1, -unitary_input.c1); } -TEST(fq12, SubMulConsistency) +TEST(fq12, FrobeniusCoefficients) { - fq12 multiplicand = fq12::zero(); - multiplicand.c0.c0.c0.data[0] = 5; - multiplicand = multiplicand.to_montgomery_form(); - - fq12 a = fq12::random_element(); - fq12 result = a + a; - result += result; - result += result; - result -= a; - result -= a; - result -= a; - - fq12 expected = a * multiplicand; - EXPECT_EQ(result, expected); + fq2 frobenius_coeff_1{ Bn254Fq12Params::frobenius_coefficients_1 }; + fq2 frobenius_coeff_2{ Bn254Fq12Params::frobenius_coefficients_2 }; + fq2 frobenius_coeff_3{ Bn254Fq12Params::frobenius_coefficients_3 }; + + // \xi^{(q-1)/6} + fq2 expected_frobenius_coeff_1 = fq2{ 0x09, 0x01 }.pow((fq::modulus - 1) / 6); + // \xi^{(q^2-1)/6} = \xi^{(q-1)/6}^{q+1} + fq2 expected_frobenius_coeff_2 = expected_frobenius_coeff_1.pow(fq::modulus + 1); + // \xi^{(q^3-1)/6} = \xi^{(q-1)/6}^{q^2+q+1} + fq2 expected_frobenius_coeff_3 = expected_frobenius_coeff_1.pow(fq::modulus).pow(fq::modulus) * + expected_frobenius_coeff_1.pow(fq::modulus) * expected_frobenius_coeff_1; + + EXPECT_EQ(frobenius_coeff_1, expected_frobenius_coeff_1); + EXPECT_EQ(frobenius_coeff_2, expected_frobenius_coeff_2); + EXPECT_EQ(frobenius_coeff_3, expected_frobenius_coeff_3); } diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq2.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq2.hpp index f6563381f47c..103a77395250 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq2.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq2.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -10,6 +10,22 @@ #include "./fq.hpp" namespace bb { + +/** + * @brief Quadratic extension of the base field of BN254 + * + * @details The quadratic extension Fq2 is defined as Fq[u] / (u^2 + 1). Fq2 is the base field of + * the twist of BN254, thus points in G2 have coordinates in Fq2. + * + * Inside this struct we define the parameters of the twist: + * - the coefficient twist_b = 3 / (9 + u) defining the equation of the twist: y^2 = x^3 + twist_b. The coefficient is + * derived as b / \xi, where b = 3 is the coefficient of BN254 in short-Weierstrass form, and \xi = 9 + u is not a + * sixth residue in Fq2. + * - the coefficients required to compute the frobenius map on the twisted curve: if \Psi : E' --> E is the isomorphism + * between the twist E' and the base curve E, then the frobenius map on E' is \Psi^{-1} \circ Frobenius \circ \Psi. + * This map is given by (x, y) --> (\xi^{(q-1)/3} * x^q, \xi^{(q-1)/2}} * y^q). We precompute the two powers of \xi + * and store them as frobenius_on_twisted_curve_x and frobenius_on_twisted_curve_y. + */ struct Bn254Fq2Params { #if defined(__SIZEOF_INT128__) && !defined(__wasm__) static constexpr fq twist_coeff_b_0{ @@ -18,24 +34,18 @@ struct Bn254Fq2Params { static constexpr fq twist_coeff_b_1{ 0x38e7ecccd1dcff67UL, 0x65f0b37d93ce0d3eUL, 0xd749d0dd22ac00aaUL, 0x0141b9ce4a688d4dUL }; - static constexpr fq twist_mul_by_q_x_0{ + static constexpr fq frobenius_on_twisted_curve_x_0{ 0xb5773b104563ab30UL, 0x347f91c8a9aa6454UL, 0x7a007127242e0991UL, 0x1956bcd8118214ecUL }; - static constexpr fq twist_mul_by_q_x_1{ + static constexpr fq frobenius_on_twisted_curve_x_1{ 0x6e849f1ea0aa4757UL, 0xaa1c7b6d89f89141UL, 0xb6e713cdfae0ca3aUL, 0x26694fbb4e82ebc3UL }; - static constexpr fq twist_mul_by_q_y_0{ + static constexpr fq frobenius_on_twisted_curve_y_0{ 0xe4bbdd0c2936b629UL, 0xbb30f162e133bacbUL, 0x31a9d1b6f9645366UL, 0x253570bea500f8ddUL }; - static constexpr fq twist_mul_by_q_y_1{ + static constexpr fq frobenius_on_twisted_curve_y_1{ 0xa1d77ce45ffe77c7UL, 0x07affd117826d1dbUL, 0x6d16bd27bb7edc6bUL, 0x2c87200285defeccUL }; - static constexpr fq twist_cube_root_0{ - 0x505ecc6f0dff1ac2UL, 0x2071416db35ec465UL, 0xf2b53469fa43ea78UL, 0x18545552044c99aaUL - }; - static constexpr fq twist_cube_root_1{ - 0xad607f911cfe17a8UL, 0xb6bb78aa154154c4UL, 0xb53dd351736b20dbUL, 0x1d8ed57c5cc33d41UL - }; #else static constexpr fq twist_coeff_b_0{ 0xdc19fa4aab489658UL, 0xd416744fbbf6e69UL, 0x8f7734ed0a8a033aUL, 0x19316b8353ee09bbUL @@ -43,26 +53,20 @@ struct Bn254Fq2Params { static constexpr fq twist_coeff_b_1{ 0x1cfd999a3b9fece0UL, 0xbe166fb279c1a7c7UL, 0xe93a1ba45580154cUL, 0x283739c94d11a9baUL }; - static constexpr fq twist_mul_by_q_x_0{ + static constexpr fq frobenius_on_twisted_curve_x_0{ 0xecdea09b24a59190UL, 0x17db8ffeae2fe1c2UL, 0xbb09c97c6dabac4dUL, 0x2492b3d41d289af3UL }; - static constexpr fq twist_mul_by_q_x_1{ + static constexpr fq frobenius_on_twisted_curve_x_1{ 0xf1663598f1142ef1UL, 0x77ec057e0bf56062UL, 0xdd0baaecb677a631UL, 0x135e4e31d284d463UL }; - static constexpr fq twist_mul_by_q_y_0{ + static constexpr fq frobenius_on_twisted_curve_y_0{ 0xf46e7f60db1f0678UL, 0x31fc2eba5bcc5c3eUL, 0xedb3adc3086a2411UL, 0x1d46bd0f837817bcUL }; - static constexpr fq twist_mul_by_q_y_1{ + static constexpr fq frobenius_on_twisted_curve_y_1{ 0x6b3fbdf579a647d5UL, 0xcc568fb62ff64974UL, 0xc1bfbf4ac4348ac6UL, 0x15871d4d3940b4d3UL }; - static constexpr fq twist_cube_root_0{ - 0x49d0cc74381383d0UL, 0x9611849fe4bbe3d6UL, 0xd1a231d73067c92aUL, 0x445c312767932c2UL - }; - static constexpr fq twist_cube_root_1{ - 0x35a58c718e7c28bbUL, 0x98d42c77e7b8901aUL, 0xf9c53da2d0ca8c84UL, 0x1a68dd04e1b8c51dUL - }; #endif }; using fq2 = field2; -} // namespace bb \ No newline at end of file +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq2.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq2.test.cpp index 0a0223b938d7..b7a2ec660032 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq2.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq2.test.cpp @@ -1,54 +1,16 @@ +/** + * @brief BN254 quadratic extension of the base field (fq2) specific tests. + * + * Other field arithmetic tests (both compile-time and runtime) are in ecc/fields/generic_field.test.cpp and + * ecc/fields/prime_field.test.cpp. This file contains only BN254-specific functionality: + * - Fixed tests with field-specific expected values + * - Precomputed constants related to the twist of BN254 + */ #include "fq2.hpp" #include using namespace bb; -TEST(fq2, eq) -{ - fq2 a{ { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }; - fq2 b{ { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }; - fq2 c{ { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } }; - fq2 d{ { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }; - fq2 e{ { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }; - fq2 f{ { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }; - fq2 g{ { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } }; - fq2 h{ { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } }; - fq2 i{ { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } }; - fq2 j{ { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } }; - - EXPECT_EQ(a == b, true); - EXPECT_EQ(a == c, false); - EXPECT_EQ(a == d, false); - EXPECT_EQ(a == e, false); - EXPECT_EQ(a == f, false); - EXPECT_EQ(a == g, false); - EXPECT_EQ(a == h, false); - EXPECT_EQ(a == i, false); - EXPECT_EQ(a == j, false); -} - -TEST(fq2, IsZero) -{ - fq2 a = fq2::zero(); - fq2 b = fq2::zero(); - fq2 c = fq2::zero(); - b.c0.data[0] = 1; - c.c1.data[0] = 1; - EXPECT_EQ(a.is_zero(), true); - EXPECT_EQ(b.is_zero(), false); - EXPECT_EQ(c.is_zero(), false); -} - -TEST(fq2, RandomElement) -{ - fq2 a = fq2::random_element(); - fq2 b = fq2::random_element(); - - EXPECT_EQ(a == b, false); - EXPECT_EQ(a.is_zero(), false); - EXPECT_EQ(b.is_zero(), false); -} - TEST(fq2, MulCheckAgainstConstants) { #if defined(__SIZEOF_INT128__) && !defined(__wasm__) @@ -134,93 +96,32 @@ TEST(fq2, SubCheckAgainstConstants) EXPECT_EQ(result, expected); } -TEST(fq2, ToMontgomeryForm) -{ - fq2 result = fq2::zero(); - result.c0.data[0] = 1; - fq2 expected = fq2::one(); - result.self_to_montgomery_form(); - EXPECT_EQ(result, expected); -} - -TEST(fq2, FromMontgomeryForm) +TEST(fq2, XiNotSexticResidue) { - fq2 result = fq2::one(); - fq2 expected = fq2::zero(); - expected.c0.data[0] = 1; - result.self_from_montgomery_form(); - EXPECT_EQ(result, expected); -} + constexpr fq2 xi{ fq(9), fq(1) }; // 9 + u + fq2 pow_xi = xi.pow((fq::modulus - 1) / 6).pow(fq::modulus + 1); // \xi^((q^2-1)/6) should not be 1 + fq2 one = fq2::one(); -TEST(fq2, MulSqrConsistency) -{ - fq2 a = fq2::random_element(); - fq2 b = fq2::random_element(); - fq2 t1; - fq2 t2; - fq2 mul_result; - fq2 sqr_result; - t1 = a - b; - t2 = a + b; - mul_result = t1 * t2; - t1 = a.sqr(); - t2 = b.sqr(); - sqr_result = t1 - t2; - EXPECT_EQ(mul_result, sqr_result); + EXPECT_NE(pow_xi, one); } -TEST(fq2, AddMulConsistency) +TEST(fq2, TwistBCoefficient) { - fq2 multiplicand = { { 0x09, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00 } }; - multiplicand = multiplicand.to_montgomery_form(); - - fq2 a = fq2::random_element(); - fq2 result = a + a; - result += result; - result += result; - result += a; - - fq2 expected = a * multiplicand; - - EXPECT_EQ(result, expected); + constexpr fq2 twist_b{ Bn254Fq2Params::twist_coeff_b_0, Bn254Fq2Params::twist_coeff_b_1 }; + fq2 expected = fq2{ fq(3), fq(0) } * fq2{ fq(9), fq(1) }.invert(); // 3 / (9+u) + EXPECT_EQ(twist_b, expected); } -TEST(fq2, SubMulConsistency) +TEST(fq2, InverseTwistFrobeniusTwistCoefficients) { - fq2 multiplicand = { { 0x05, 0, 0, 0 }, { 0x00, 0x00, 0x00, 0x00 } }; - multiplicand = multiplicand.to_montgomery_form(); - - fq2 a = fq2::random_element(); - fq2 result = a + a; - result += result; - result += result; - result -= a; - result -= a; - result -= a; + constexpr fq2 xi_q_minus_one_div_3{ Bn254Fq2Params::frobenius_on_twisted_curve_x_0, + Bn254Fq2Params::frobenius_on_twisted_curve_x_1 }; + constexpr fq2 xi_q_minus_one_div_2{ Bn254Fq2Params::frobenius_on_twisted_curve_y_0, + Bn254Fq2Params::frobenius_on_twisted_curve_y_1 }; - fq2 expected = a * multiplicand; - - EXPECT_EQ(result, expected); -} + fq2 expected_x = fq2{ fq(9), fq(1) }.pow((fq::modulus - 1) / 3); // \xi^((q-1)/3) + fq2 expected_y = fq2{ fq(9), fq(1) }.pow((fq::modulus - 1) / 2); // \xi^((q-1)/2) -TEST(fq2, Invert) -{ - fq2 input = fq2::random_element(); - fq2 inverse = input.invert(); - fq2 result = inverse * input; - EXPECT_EQ(result, fq2::one()); + EXPECT_EQ(xi_q_minus_one_div_3, expected_x); + EXPECT_EQ(xi_q_minus_one_div_2, expected_y); } - -TEST(fq2, Serialize) -{ - std::array buffer; - fq expected_c0 = { 0x1234567876543210, 0x2345678987654321, 0x3456789a98765432, 0x006789abcba98765 }; - fq expected_c1 = { 0x12a4e67f76b43210, 0x23e56f898a65cc21, 0x005678add98e5432, 0x1f6789a2cba98700 }; - fq2 expected{ expected_c0, expected_c1 }; - - fq2::serialize_to_buffer(expected, &buffer[0]); - - fq2 result = fq2::serialize_from_buffer(&buffer[0]); - - EXPECT_EQ(result, expected); -} \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq6.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq6.hpp index e4abf038884b..fc22af2fa6f7 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq6.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq6.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -11,6 +11,29 @@ #include "./fq2.hpp" namespace bb { + +/** + * @brief Sextic extension of the base field of BN254 + * + * @details Fq6 is defined as Fq2[v] / (v^3 - \xi), where \xi = 9 + u is not a cubic residue in Fq2. We store in the + * struct the coefficients to compute the frobenius morphism (we need powers up to q^3 to compute the final + * exponentiation in the pairing calculation) + * 1. Power q + * \f[ + * (a + bv + cv^2)^q = a^q + b^q * v^q + c^q * v^{2q} = a^q + b^q * \xi^{(q-1)/3} * v + c^q * \xi^{2(q-1)/3} * v^2 + * \f] + * 2. Power q^2 + * \f[ + * (a + bv + cv^2)^{q^2} = a^{q^2} + b^{q^2} * v^{q^2} + c^{q^2} * v^{2q^2} = + * a + b * \xi^{(q^2-1)/3} * v + c * \xi^{2(q^2-1)/3} * v^2 + * \f] + * 3. Power q^3 + * \f[ + * (a + bv + cv^2)^{q^3} = a^{q^3} + b^{q^3} * v^{q^3} + c^{q^3} * v^{2q^3} = + * a^q + b^q * \xi^{(q^3-1)/3} * v + c^q * \xi^{2(q^3-1)/3} * v^2 + * \f] + * + */ struct Bn254Fq6Params { #if defined(__SIZEOF_INT128__) && !defined(__wasm__) @@ -74,26 +97,26 @@ struct Bn254Fq6Params { }; #endif - // non residue = 9 + i \in Fq2 static inline constexpr fq2 mul_by_non_residue(const fq2& a) { - // non residue = 9 + i \in Fq2 - // r.c0 = 9a0 - a1 - // r.c1 = 9a1 + a0 + // non_residue = 9 + u + // (a + bu) * (9 + u) = (9a - b) + (9b + a)u + + // 9a fq T0 = a.c0 + a.c0; T0 += T0; T0 += T0; T0 += a.c0; + + // 9b fq T1 = a.c1 + a.c1; T1 += T1; T1 += T1; T1 += a.c1; - fq T2 = T0 - a.c1; - return { T2, T1 + a.c0 }; - T0 = a.c0 + a.c0; + return { T0 - a.c1, T1 + a.c0 }; } }; using fq6 = field6; -} // namespace bb \ No newline at end of file +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq6.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq6.test.cpp index cc0c7e45abb5..ca7026eca55f 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq6.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fq6.test.cpp @@ -3,75 +3,6 @@ using namespace bb; -TEST(fq6, Eq) -{ - fq6 a{ { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }; - fq6 b{ { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }; - fq6 c{ { { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x05 }, { 0x06, 0x07, 0x08, 0x09 } } }; - fq6 d{ { { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x04, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }; - fq6 e{ { { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x03, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }; - fq6 f{ { { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } }, - { { 0x02, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x09 } } }; - fq6 g{ { { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x07, 0x07, 0x08, 0x09 } } }; - fq6 h{ { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x08, 0x08, 0x09 } } }; - fq6 i{ { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x09, 0x09 } } }; - fq6 j{ { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } }, - { { 0x01, 0x02, 0x03, 0x04 }, { 0x06, 0x07, 0x08, 0x0a } } }; - - EXPECT_EQ((a == b), true); - EXPECT_EQ((a == c), false); - EXPECT_EQ((a == d), false); - EXPECT_EQ((a == e), false); - EXPECT_EQ((a == f), false); - EXPECT_EQ((a == g), false); - EXPECT_EQ((a == h), false); - EXPECT_EQ((a == i), false); - EXPECT_EQ((a == j), false); -} - -TEST(fq6, IsZero) -{ - fq6 a = fq6::zero(); - fq6 b = fq6::zero(); - fq6 c = fq6::zero(); - fq6 d = fq6::zero(); - b.c0.c0.data[0] = 1; - c.c1.c0.data[0] = 1; - d.c2.c0.data[0] = 1; - EXPECT_EQ(a.is_zero(), true); - EXPECT_EQ(b.is_zero(), false); - EXPECT_EQ(c.is_zero(), false); - EXPECT_EQ(d.is_zero(), false); -} - -TEST(fq6, RandomElement) -{ - fq6 a = fq6::random_element(); - fq6 b = fq6::random_element(); - - EXPECT_EQ((a == b), false); - EXPECT_EQ(a.is_zero(), false); - EXPECT_EQ(b.is_zero(), false); -} - TEST(fq6, AddCheckAgainstConstants) { fq6 a{ { { 0x68138b3c3e5e820b, 0x9bf71d36786da85f, 0x815831c12e257996, 0x2280b875a27e6d1d }, @@ -169,7 +100,6 @@ TEST(fq6, MulCheckAgainstConstants) TEST(fq6, SqrCheckAgainstConstants) { - #if defined(__SIZEOF_INT128__) && !defined(__wasm__) fq6 a{ { { 0xe337aaa063afce6, 0xff4b5477485eb20, 0xef6dcf13b3855ef8, 0x14554c38da988ece }, { 0x6a70e65e71431416, 0xd21f95045c45f422, 0x2a17b6c6ff517884, 0x1b01ad6487a3ff16 } }, @@ -202,84 +132,43 @@ TEST(fq6, SqrCheckAgainstConstants) EXPECT_EQ(result, expected); } -TEST(fq6, ToMontgomeryForm) -{ - fq6 result = fq6::zero(); - result.c0.c0.data[0] = 1; - fq6 expected = fq6::one(); - result = result.to_montgomery_form(); - EXPECT_EQ(result, expected); -} - -TEST(fq6, FromMontgomeryForm) -{ - fq6 result = fq6::one(); - fq6 expected = fq6::zero(); - expected.c0.c0.data[0] = 1; - result = result.from_montgomery_form(); - EXPECT_EQ(result, expected); -} - -TEST(fq6, MulSqrConsistency) +TEST(fq6, FrobeniusCoefficients) { - fq6 a = fq6::random_element(); - fq6 b = fq6::random_element(); - fq6 t1 = a - b; - fq6 t2 = a + b; - fq6 mul_result = t1 * t2; - fq6 sqr_result = a.sqr() - b.sqr(); - - EXPECT_EQ(mul_result, sqr_result); -} - -TEST(fq6, AddMulConsistency) -{ - fq6 multiplicand = fq6::zero(); - multiplicand.c0.c0.data[0] = 9; - multiplicand = multiplicand.to_montgomery_form(); - - fq6 a = fq6::random_element(); - fq6 result = a + a; - result += result; - result += result; - result += a; - - fq6 expected = a * multiplicand; - EXPECT_EQ(result, expected); + fq2 frobenius_coeff_1_1{ Bn254Fq6Params::frobenius_coeffs_c1_1 }; + fq2 frobenius_coeff_1_2{ Bn254Fq6Params::frobenius_coeffs_c1_2 }; + fq2 frobenius_coeff_1_3{ Bn254Fq6Params::frobenius_coeffs_c1_3 }; + fq2 frobenius_coeff_2_1{ Bn254Fq6Params::frobenius_coeffs_c2_1 }; + fq2 frobenius_coeff_2_2{ Bn254Fq6Params::frobenius_coeffs_c2_2 }; + fq2 frobenius_coeff_2_3{ Bn254Fq6Params::frobenius_coeffs_c2_3 }; + + // \xi^{(q-1)/3} + fq2 expected_frobenius_coeff_1_1 = fq2{ 0x09, 0x01 }.pow((fq::modulus - 1) / 3); + // \xi^{(q^2-1)/3} = \xi^{(q-1)/3}^{q+1} + fq2 expected_frobenius_coeff_1_2 = expected_frobenius_coeff_1_1.pow(fq::modulus + 1); + // \xi^{(q^3-1)/3} = \xi^{(q-1)/3}^{q^2+q+1} + fq2 expected_frobenius_coeff_1_3 = expected_frobenius_coeff_1_1.pow(fq::modulus).pow(fq::modulus) * + expected_frobenius_coeff_1_1.pow(fq::modulus) * expected_frobenius_coeff_1_1; + // \xi^{2(q-1)/3} + fq2 expected_frobenius_coeff_2_1 = fq2{ 0x09, 0x01 }.pow(uint256_t(2) * (fq::modulus - 1) / 3); + // \xi^{2(q^2-1)/3} = \xi^{2(q-1)/3}^{q+1} + fq2 expected_frobenius_coeff_2_2 = expected_frobenius_coeff_2_1.pow(fq::modulus + 1); + // \xi^{2(q^3-1)/3} = \xi^{2(q-1)/3}^{q^2+q+1} + fq2 expected_frobenius_coeff_2_3 = expected_frobenius_coeff_2_1.pow(fq::modulus).pow(fq::modulus) * + expected_frobenius_coeff_2_1.pow(fq::modulus) * expected_frobenius_coeff_2_1; + + EXPECT_EQ(frobenius_coeff_1_1, expected_frobenius_coeff_1_1); + EXPECT_EQ(frobenius_coeff_1_2, expected_frobenius_coeff_1_2); + EXPECT_EQ(frobenius_coeff_1_3, expected_frobenius_coeff_1_3); + EXPECT_EQ(frobenius_coeff_2_1, expected_frobenius_coeff_2_1); + EXPECT_EQ(frobenius_coeff_2_2, expected_frobenius_coeff_2_2); + EXPECT_EQ(frobenius_coeff_2_3, expected_frobenius_coeff_2_3); } -TEST(fq6, SubMulConsistency) +TEST(fq6, MulByNonResidue) { - fq6 multiplicand = fq6::zero(); - multiplicand.c0.c0.data[0] = 5; - multiplicand = multiplicand.to_montgomery_form(); - fq6 a = fq6::random_element(); - fq6 result = a + a; - result += result; - result += result; - result -= a; - result -= a; - result -= a; - - fq6 expected = a * multiplicand; + fq2 one = fq2::one(); + fq2 result = fq6::mul_by_non_residue(one); + fq2 expected = fq2{ 0x09, 0x01 }; EXPECT_EQ(result, expected); } - -TEST(fq6, Invert) -{ - fq6 input = fq6::random_element(); - fq6 result = input.invert(); - - result *= input; - EXPECT_EQ(result, fq6::one()); -} - -TEST(fq6, Copy) -{ - fq6 result = fq6::random_element(); - - // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) this is what we want to test! - fq6 expected = result; - EXPECT_EQ(result, expected); -} \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fr.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fr.hpp index 3f39b6541d24..b9acfd21a730 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fr.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fr.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -17,13 +17,17 @@ namespace bb { +/** + * @brief Parameters defining the scalar field of the BN254 curve. + * + * @details When split into 4 64-bit words, the parameters are represented in little-endian, i.e. the least significant + * bit comes first. For example, to recover the modulus from the 64-bit words we concatenate its limbs to obtain: + * 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000001 + * + * @note These parameters can be extracted by running the script parameter_helper.py in ecc/fields + */ class Bn254FrParams { - // There is a helper script in ecc/fields/parameter_helper.py that can be used to extract these parameters from the public: - // Note: limbs here are combined as concat(_3, _2, _1, _0) - // E.g. this modulus forms the value: - // 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000001 - // = 21888242871839275222246405745257275088548364400416034343698204186575808495617 // A little-endian representation of the modulus split into 4 64-bit words static constexpr uint64_t modulus_0 = 0x43E1F593F0000001UL; static constexpr uint64_t modulus_1 = 0x2833E84879B97091UL; @@ -36,39 +40,15 @@ class Bn254FrParams { static constexpr uint64_t r_squared_2 = 0x8C49833D53BB8085UL; static constexpr uint64_t r_squared_3 = 0x216D0B17F4E44A5UL; - // A little-endian representation of the cubic root of 1 in Fr in Montgomery form split into 4 64-bit words - static constexpr uint64_t cube_root_0 = 0x93e7cede4a0329b3UL; - static constexpr uint64_t cube_root_1 = 0x7d4fdca77a96c167UL; - static constexpr uint64_t cube_root_2 = 0x8be4ba08b19a750aUL; - static constexpr uint64_t cube_root_3 = 0x1cbd5653a5661c25UL; - - // A little-endian representation of the primitive root of 1 Fr split into 4 64-bit words in Montgomery form - // (R=2^256 mod modulus) This is a root of unity in a large power of 2 subgroup of Fr - static constexpr uint64_t primitive_root_0 = 0x636e735580d13d9cUL; - static constexpr uint64_t primitive_root_1 = 0xa22bf3742445ffd6UL; - static constexpr uint64_t primitive_root_2 = 0x56452ac01eb203d8UL; - static constexpr uint64_t primitive_root_3 = 0x1860ef942963f9e7UL; - - // Parameters used for quickly splitting a scalar into two endomorphism scalars for faster scalar multiplication - // For specifics on how these have been derived, ask @zac-williamson - static constexpr uint64_t endo_g1_lo = 0x7a7bd9d4391eb18dUL; - static constexpr uint64_t endo_g1_mid = 0x4ccef014a773d2cfUL; - static constexpr uint64_t endo_g1_hi = 0x0000000000000002UL; - static constexpr uint64_t endo_g2_lo = 0xd91d232ec7e0b3d7UL; - static constexpr uint64_t endo_g2_mid = 0x0000000000000002UL; - static constexpr uint64_t endo_minus_b1_lo = 0x8211bbeb7d4f1128UL; - static constexpr uint64_t endo_minus_b1_mid = 0x6f4d8248eeb859fcUL; - static constexpr uint64_t endo_b2_lo = 0x89d3256894d213e3UL; - static constexpr uint64_t endo_b2_mid = 0UL; - // -(Modulus^-1) mod 2^64 - // This is used to compute k = r_inv * lower_limb(scalar), such that scalar + k*modulus in integers would have 0 in - // the lowest limb By performing this sequentially for 4 limbs, we get an 8-limb representation of the scalar, where - // the lowest 4 limbs are zeros. Then we can immediately divide by 2^256 by simply getting rid of the lowest 4 limbs + // This constant is used during multiplication: given an 8-limb representation of the multiplication of two field + // elements, for each of the lowest four limbs we compute: k_i = r_inv * limb_i and we add 2^{64 * i} * k_i * p to + // the result of the multiplication. In this way we zero out the lowest four limbs of the multiplication and we can + // divide by 2^256 by taking the highest four limbs. See field_docs.hpp for more details. static constexpr uint64_t r_inv = 0xc2e1f593efffffffUL; // 2^(-64) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery + // Used in the reduction mechanism, see field_docs.md // Instead of computing k, we multiply the lowest limb by this value and then add to the following 5 limbs. // This saves us from having to compute k static constexpr uint64_t r_inv_0 = 0x2d3e8053e396ee4dUL; @@ -76,37 +56,24 @@ class Bn254FrParams { static constexpr uint64_t r_inv_2 = 0xb2d8f06f77f52a93UL; static constexpr uint64_t r_inv_3 = 0x24d6ba07f7aa8f04UL; - // 2^(-29) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery - // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. - // This saves us from having to compute k - static constexpr uint64_t r_inv_wasm_0 = 0x18f05361; - static constexpr uint64_t r_inv_wasm_1 = 0x12bb1fe; - static constexpr uint64_t r_inv_wasm_2 = 0xf5d8135; - static constexpr uint64_t r_inv_wasm_3 = 0x1e6275f6; - static constexpr uint64_t r_inv_wasm_4 = 0x7e7a880; - static constexpr uint64_t r_inv_wasm_5 = 0x10c6bf1f; - static constexpr uint64_t r_inv_wasm_6 = 0x11f74a6c; - static constexpr uint64_t r_inv_wasm_7 = 0x6fdaecb; - static constexpr uint64_t r_inv_wasm_8 = 0x183227; + // A little-endian representation of the cubic root of 1 in Fr in Montgomery form split into 4 64-bit words + static constexpr uint64_t cube_root_0 = 0x93e7cede4a0329b3UL; + static constexpr uint64_t cube_root_1 = 0x7d4fdca77a96c167UL; + static constexpr uint64_t cube_root_2 = 0x8be4ba08b19a750aUL; + static constexpr uint64_t cube_root_3 = 0x1cbd5653a5661c25UL; + + // A little-endian representation of the primitive root of 1 in Fr split into 4 64-bit words in Montgomery form + // (R=2^256 mod modulus). This is a root of unity in a large power of 2 (order 28) subgroup of Fr. + static constexpr uint64_t primitive_root_0 = 0x636e735580d13d9cUL; + static constexpr uint64_t primitive_root_1 = 0xa22bf3742445ffd6UL; + static constexpr uint64_t primitive_root_2 = 0x56452ac01eb203d8UL; + static constexpr uint64_t primitive_root_3 = 0x1860ef942963f9e7UL; // Coset generators in Montgomery form for R=2^256 mod Modulus. Used in FFT-based proving systems - static constexpr uint64_t coset_generators_0[8]{ - 0x5eef048d8fffffe7ULL, 0xb8538a9dfffffe2ULL, 0x3057819e4fffffdbULL, 0xdcedb5ba9fffffd6ULL, - 0x8983e9d6efffffd1ULL, 0x361a1df33fffffccULL, 0xe2b0520f8fffffc7ULL, 0x8f46862bdfffffc2ULL, - }; - static constexpr uint64_t coset_generators_1[8]{ - 0x12ee50ec1ce401d0ULL, 0x49eac781bc44cefaULL, 0x307f6d866832bb01ULL, 0x677be41c0793882aULL, - 0x9e785ab1a6f45554ULL, 0xd574d1474655227eULL, 0xc7147dce5b5efa7ULL, 0x436dbe728516bcd1ULL, - }; - static constexpr uint64_t coset_generators_2[8]{ - 0x29312d5a5e5ee7ULL, 0x6697d49cd2d7a515ULL, 0x5c65ec9f484e3a89ULL, 0xc2d4900ec0c780b7ULL, - 0x2943337e3940c6e5ULL, 0x8fb1d6edb1ba0d13ULL, 0xf6207a5d2a335342ULL, 0x5c8f1dcca2ac9970ULL, - }; - static constexpr uint64_t coset_generators_3[8]{ - 0x463456c802275bedULL, 0x543ece899c2f3b1cULL, 0x180a96573d3d9f8ULL, 0xf8b21270ddbb927ULL, - 0x1d9598e8a7e39857ULL, 0x2ba010aa41eb7786ULL, 0x39aa886bdbf356b5ULL, 0x47b5002d75fb35e5ULL, - }; + static constexpr uint64_t coset_generator_0 = 0x5eef048d8fffffe7ULL; + static constexpr uint64_t coset_generator_1 = 0x12ee50ec1ce401d0ULL; + static constexpr uint64_t coset_generator_2 = 0x29312d5a5e5ee7ULL; + static constexpr uint64_t coset_generator_3 = 0x463456c802275bedULL; // A little-endian representation of the modulus split into 9 29-bit limbs // This is used in wasm because we can only do multiplication with 64-bit result instead of 128-bit like in x86_64 @@ -121,12 +88,26 @@ class Bn254FrParams { static constexpr uint64_t modulus_wasm_8 = 0x30644e; // A little-endian representation of R^2 modulo the modulus (R=2^261 mod modulus) split into 4 64-bit words - // We use 2^261 in wasm, because 261=29*9, the 9 29-bit limbs used for arithmetic in + // We use 2^261 in wasm, because 261=29*9, the 9 29-bit limbs used for arithmetic static constexpr uint64_t r_squared_wasm_0 = 0x38c2e14b45b69bd4UL; static constexpr uint64_t r_squared_wasm_1 = 0x0ffedb1885883377UL; static constexpr uint64_t r_squared_wasm_2 = 0x7840f9f0abc6e54dUL; static constexpr uint64_t r_squared_wasm_3 = 0x0a054a3e848b0f05UL; + // 2^(-29) mod Modulus + // Used in the reduction mechanism, see field_docs.md + // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. + // This saves us from having to compute k + static constexpr uint64_t r_inv_wasm_0 = 0x18f05361; + static constexpr uint64_t r_inv_wasm_1 = 0x12bb1fe; + static constexpr uint64_t r_inv_wasm_2 = 0xf5d8135; + static constexpr uint64_t r_inv_wasm_3 = 0x1e6275f6; + static constexpr uint64_t r_inv_wasm_4 = 0x7e7a880; + static constexpr uint64_t r_inv_wasm_5 = 0x10c6bf1f; + static constexpr uint64_t r_inv_wasm_6 = 0x11f74a6c; + static constexpr uint64_t r_inv_wasm_7 = 0x6fdaecb; + static constexpr uint64_t r_inv_wasm_8 = 0x183227; + // A little-endian representation of the cubic root of 1 in Fr in Montgomery form for wasm (R=2^261 mod modulus) // split into 4 64-bit words static constexpr uint64_t cube_root_wasm_0 = 0x7334a1ce7065364dUL; @@ -142,22 +123,22 @@ class Bn254FrParams { static constexpr uint64_t primitive_root_wasm_3 = 0x05d90b5719653a4fUL; // Coset generators in Montgomery form for R=2^261 mod Modulus. Used in FFT-based proving systems - static constexpr uint64_t coset_generators_wasm_0[8] = { 0xab46711cdffffcb2ULL, 0xdb1b52736ffffc09ULL, - 0x0af033c9fffffb60ULL, 0xf6e31f8c9ffffab6ULL, - 0x26b800e32ffffa0dULL, 0x568ce239bffff964ULL, - 0x427fcdfc5ffff8baULL, 0x7254af52effff811ULL }; - static constexpr uint64_t coset_generators_wasm_1[8] = { 0x2476607dbd2dfff1ULL, 0x9a3208a561c2b00bULL, - 0x0fedb0cd06576026ULL, 0x5d7570ac31329faeULL, - 0xd33118d3d5c74fc9ULL, 0x48ecc0fb7a5bffe3ULL, - 0x967480daa5373f6cULL, 0x0c30290249cbef86ULL }; - static constexpr uint64_t coset_generators_wasm_2[8] = { 0xe6b99ee0068dfc25ULL, 0x39bb9964882aa6a5ULL, - 0x8cbd93e909c75126ULL, 0x276f48b709e2a349ULL, - 0x7a71433b8b7f4dc9ULL, 0xcd733dc00d1bf84aULL, - 0x6824f28e0d374a6dULL, 0xbb26ed128ed3f4eeULL }; - static constexpr uint64_t coset_generators_wasm_3[8] = { 0x1484c05bce00b620ULL, 0x224cf685243dfa96ULL, - 0x30152cae7a7b3f0bULL, 0x0d791464ef86e357ULL, - 0x1b414a8e45c427ccULL, 0x290980b79c016c41ULL, - 0x066d686e110d108dULL, 0x14359e97674a5502ULL }; + static constexpr uint64_t coset_generator_wasm_0 = 0xab46711cdffffcb2ULL; + static constexpr uint64_t coset_generator_wasm_1 = 0x2476607dbd2dfff1ULL; + static constexpr uint64_t coset_generator_wasm_2 = 0xe6b99ee0068dfc25ULL; + static constexpr uint64_t coset_generator_wasm_3 = 0x1484c05bce00b620ULL; + + // Parameters used for quickly splitting a scalar into two endomorphism scalars for faster scalar multiplication + // For specifics on how these have been derived, see ecc/fields/endomorphim_scalars.py + static constexpr uint64_t endo_g1_lo = 0x7a7bd9d4391eb18dUL; + static constexpr uint64_t endo_g1_mid = 0x4ccef014a773d2cfUL; + static constexpr uint64_t endo_g1_hi = 0x0000000000000002UL; + static constexpr uint64_t endo_g2_lo = 0xd91d232ec7e0b3d7UL; + static constexpr uint64_t endo_g2_mid = 0x0000000000000002UL; + static constexpr uint64_t endo_minus_b1_lo = 0x8211bbeb7d4f1128UL; + static constexpr uint64_t endo_minus_b1_mid = 0x6f4d8248eeb859fcUL; + static constexpr uint64_t endo_b2_lo = 0x89d3256894d213e3UL; + static constexpr uint64_t endo_b2_mid = 0UL; // used in msgpack schema serialization static constexpr char schema_name[] = "fr"; diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fr.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fr.test.cpp index 6defeae8da9e..527d8ff82501 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fr.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/fr.test.cpp @@ -89,6 +89,9 @@ TEST(BN254Fr, SplitIntoEndomorphismScalars) k1.self_to_montgomery_form(); k2.self_to_montgomery_form(); + EXPECT_LT(uint256_t(k1).get_msb(), 128); + EXPECT_LT(uint256_t(k2).get_msb(), 128); + fr lambda = fr::cube_root_of_unity(); result = k2 * lambda; result = k1 - result; @@ -106,11 +109,13 @@ TEST(BN254Fr, SplitIntoEndomorphismScalarsSimple) fr::__copy(input, k); fr::split_into_endomorphism_scalars(k, k1, k2); - // AUDITTODO: double check this test. fr result{ 0, 0, 0, 0 }; k1.self_to_montgomery_form_reduced(); k2.self_to_montgomery_form_reduced(); + EXPECT_LT(uint256_t(k1).get_msb(), 128); + EXPECT_LT(uint256_t(k2).get_msb(), 128); + fr lambda = fr::cube_root_of_unity(); result = k2 * lambda; result = k1 - result; diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g1.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g1.hpp index f30f2f94abb8..c6e5ea4afc63 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g1.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g1.hpp @@ -16,6 +16,8 @@ struct Bn254G1Params { static constexpr bool can_hash_to_curve = true; static constexpr bool small_elements = true; static constexpr bool has_a = false; + + // Generator = (1, sqrt(4)) = (1, 2) static constexpr fq one_x = fq::one(); #if defined(__SIZEOF_INT128__) && !defined(__wasm__) static constexpr fq one_y{ 0xa6ba871b8b1e1b3aUL, 0x14f1d651eb8e167bUL, 0xccdd46def0f28c58UL, 0x1c14ef83340fbe5eUL }; diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g1.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g1.test.cpp index ea3651b8a83c..8b6066ee8535 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g1.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g1.test.cpp @@ -23,6 +23,30 @@ typename Group::affine_element naive_scalar_mul(const typename Group::element& b } } // namespace +// ========================= +// Parameter-related tests +// ========================= + +TEST(g1, BIsCorrect) +{ + fq b = Bn254G1Params::b; + fq expected = fq(3); + + EXPECT_EQ(b, expected); +} + +TEST(g1, OneYIsCorrect) +{ + fq one_y = Bn254G1Params::one_y; + auto [_, expected] = (Bn254G1Params::b + fq::one()).sqrt(); + + EXPECT_EQ(one_y, expected); +} + +// ========================= +// Group-related tests +// ========================= + TEST(g1, RandomElement) { g1::element result = g1::element::random_element(); diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g2.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g2.test.cpp index ce198af48a91..efa574fed661 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g2.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/g2.test.cpp @@ -387,4 +387,15 @@ TEST(g2, InitializationCheck) // NOLINTNEXTLINE not our fault googletest uses `goto`! EXPECT_NO_THROW(write({})); } -#endif \ No newline at end of file +#endif + +TEST(g2, GeneratorIsCorrect) +{ + // Values taken from https://eips.ethereum.org/EIPS/eip-197 + g2::affine_element generator{ Bn254G2Params::one_x, Bn254G2Params::one_y }; + g2::affine_element expected{ fq2{ fq("0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed"), + fq("0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2") }, + fq2{ fq("0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa"), + fq("0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b") } }; + EXPECT_EQ(generator, expected); +} diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/pairing_impl.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/pairing_impl.hpp index 33d889c6dc75..1968365cdbbd 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/pairing_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/pairing_impl.hpp @@ -19,8 +19,8 @@ inline constexpr g2::element mul_by_q(const g2::element& a) fq2 T0 = a.x.frobenius_map(); fq2 T1 = a.y.frobenius_map(); return { - fq2::twist_mul_by_q_x() * T0, - fq2::twist_mul_by_q_y() * T1, + fq2::frobenius_on_twisted_curve_x() * T0, + fq2::frobenius_on_twisted_curve_y() * T1, a.z.frobenius_map(), }; } diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/field_params_constants.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/field_params_constants.test.cpp new file mode 100644 index 000000000000..1433076c7b5c --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/field_params_constants.test.cpp @@ -0,0 +1,353 @@ +/** + * @brief Typed test fixture verifying field parameter constants for all supported curves. + * + * Each field is described by a Config struct that bundles: + * - Params: the field params struct (e.g. Bn254FqParams) + * - Field: the instantiated field type (e.g. bb::fq = field) + * - expected_modulus_decimal: the expected modulus as a decimal string (ground truth reference) + * - has_cube_root: whether a meaningful cube root of unity exists in this field + * - has_primitive_root: whether a high-2-adicity primitive root of unity is used + * + * Tests cover native (64-bit limb) and WASM (29-bit limb) representations of all constants. + * + * Fields tested: + * - BN254: Fq (base field), Fr (scalar field) + * - secp256k1: Fq (base field), Fr (scalar field) + * - secp256r1: Fq (base field), Fr (scalar field) + * + * Note: Grumpkin reuses BN254's fq/fr (swapped), so no separate config is needed. + */ + +#include "barretenberg/ecc/curves/bn254/fq.hpp" +#include "barretenberg/ecc/curves/bn254/fr.hpp" +#include "barretenberg/ecc/curves/secp256k1/secp256k1.hpp" +#include "barretenberg/ecc/curves/secp256r1/secp256r1.hpp" +#include "barretenberg/numeric/random/engine.hpp" +#include "barretenberg/numeric/uint256/uint256.hpp" +#include +#include +#include + +using namespace bb; + +namespace { + +// Helper to convert a decimal string to uint256_t. +uint256_t from_decimal(const std::string& dec_str) +{ + uint256_t result = 0; + for (char c : dec_str) { + result = result * 10 + static_cast(c - '0'); + } + return result; +} + +// ============================================================ +// Config structs — one per field, providing expected constants +// and feature flags that control which tests are exercised. +// ============================================================ + +struct Bn254FqTestConfig { + using Params = Bn254FqParams; + using Field = bb::fq; + // BN254 base field prime q + // References: https://eips.ethereum.org/EIPS/eip-196, https://hackmd.io/@jpw/bn254 + static constexpr const char* expected_modulus_decimal = + "21888242871839275222246405745257275088696311157297823662689037894645226208583"; + static constexpr bool has_cube_root = true; + static constexpr bool has_primitive_root = false; +}; + +struct Bn254FrTestConfig { + using Params = Bn254FrParams; + using Field = bb::fr; + // BN254 scalar field prime r (also Baby Jubjub base field) + // References: https://eips.ethereum.org/EIPS/eip-196, https://hackmd.io/@jpw/bn254 + static constexpr const char* expected_modulus_decimal = + "21888242871839275222246405745257275088548364400416034343698204186575808495617"; + static constexpr bool has_cube_root = true; + static constexpr bool has_primitive_root = true; +}; + +struct Secp256k1FqTestConfig { + using Params = secp256k1::FqParams; + using Field = secp256k1::fq; + // secp256k1 base field prime p = 2^256 - 2^32 - 977 + // Reference: https://www.secg.org/sec2-v2.pdf + static constexpr const char* expected_modulus_decimal = + "115792089237316195423570985008687907853269984665640564039457584007908834671663"; + static constexpr bool has_cube_root = true; + static constexpr bool has_primitive_root = false; +}; + +struct Secp256k1FrTestConfig { + using Params = secp256k1::FrParams; + using Field = secp256k1::fr; + // secp256k1 scalar field order + // Reference: https://www.secg.org/sec2-v2.pdf + static constexpr const char* expected_modulus_decimal = + "115792089237316195423570985008687907852837564279074904382605163141518161494337"; + static constexpr bool has_cube_root = true; + static constexpr bool has_primitive_root = false; +}; + +struct Secp256r1FqTestConfig { + using Params = secp256r1::FqParams; + using Field = secp256r1::fq; + // secp256r1 (P-256) base field prime p = 2^256 - 2^224 + 2^192 + 2^96 - 1 + // Reference: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf + static constexpr const char* expected_modulus_decimal = + "115792089210356248762697446949407573530086143415290314195533631308867097853951"; + static constexpr bool has_cube_root = false; + static constexpr bool has_primitive_root = false; +}; + +struct Secp256r1FrTestConfig { + using Params = secp256r1::FrParams; + using Field = secp256r1::fr; + // secp256r1 (P-256) scalar field order + // Reference: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf + static constexpr const char* expected_modulus_decimal = + "115792089210356248762697446949407573529996955224135760342422259061068512044369"; + static constexpr bool has_cube_root = false; + static constexpr bool has_primitive_root = false; +}; + +} // namespace + +// ============================================================ +// Typed test fixture +// ============================================================ + +template class FieldConstantsTest : public testing::Test {}; + +TYPED_TEST_SUITE_P(FieldConstantsTest); + +// Verify that the 4 x 64-bit limbs reconstruct to the expected modulus. +TYPED_TEST_P(FieldConstantsTest, Modulus) +{ + using Params = typename TypeParam::Params; + uint256_t expected = from_decimal(TypeParam::expected_modulus_decimal); + uint256_t actual{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + EXPECT_EQ(expected, actual); +} + +// Verify R^2 mod p, where R = 2^256, stored in r_squared_{0-3}. +TYPED_TEST_P(FieldConstantsTest, RSquared) +{ + using Params = typename TypeParam::Params; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + uint512_t R = (uint512_t(1) << 256) % mod; + uint512_t expected = (R * R) % mod; + uint256_t actual{ Params::r_squared_0, Params::r_squared_1, Params::r_squared_2, Params::r_squared_3 }; + EXPECT_EQ(expected.lo, actual); +} + +// Verify r_inv = -(modulus^{-1}) mod 2^64, used for Montgomery reduction. +TYPED_TEST_P(FieldConstantsTest, RInv) +{ + using Params = typename TypeParam::Params; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + uint512_t two_64 = uint512_t(1) << 64; + uint512_t neg_mod{ -mod, 0 }; + uint64_t expected = neg_mod.invmod(two_64).lo.data[0]; + EXPECT_EQ(Params::r_inv, expected); +} + +// Verify r_inv_{0-3} = 2^{-64} mod p, used in the Barrett-Montgomery reduction. +TYPED_TEST_P(FieldConstantsTest, PowMinus64) +{ + using Params = typename TypeParam::Params; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + uint512_t two_64 = uint512_t(1) << 64; + uint256_t expected = two_64.invmod(mod).lo; + EXPECT_EQ(expected.data[0], Params::r_inv_0); + EXPECT_EQ(expected.data[1], Params::r_inv_1); + EXPECT_EQ(expected.data[2], Params::r_inv_2); + EXPECT_EQ(expected.data[3], Params::r_inv_3); +} + +// Verify that beta = cube_root_of_unity() satisfies beta^3 = 1 and beta != 1. +TYPED_TEST_P(FieldConstantsTest, CubeRootOfUnity) +{ + if constexpr (!TypeParam::has_cube_root) { + GTEST_SKIP() << "Cube root of unity is not defined for this field"; + } else { + using Field = typename TypeParam::Field; + Field beta = Field::cube_root_of_unity(); + EXPECT_EQ(beta * beta * beta, Field::one()); + EXPECT_NE(beta, Field::one()); + } +} + +// Verify that get_root_of_unity(order) is a primitive 2^order root of unity. +TYPED_TEST_P(FieldConstantsTest, PrimitiveRootOfUnity) +{ + if constexpr (!TypeParam::has_primitive_root) { + GTEST_SKIP() << "Primitive root of unity is not used for this field"; + } else { + using Field = typename TypeParam::Field; + size_t order = Field::primitive_root_log_size(); + Field root = Field::get_root_of_unity(order); + // root^{2^i} != 1 for all 0 <= i < order + for (size_t i = 0; i < order; i++) { + EXPECT_NE(root, Field::one()); + root = root.sqr(); + } + // root^{2^order} = 1 + EXPECT_EQ(root, Field::one()); + } +} + +// Verify that coset_generator() is not a quadratic residue mod p, i.e. coset_gen^{(p-1)/2} != 1. +TYPED_TEST_P(FieldConstantsTest, CosetGenerator) +{ + using Params = typename TypeParam::Params; + using Field = typename TypeParam::Field; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + Field coset_gen = Field::coset_generator(); + EXPECT_NE(coset_gen.pow((mod - 1) / 2), Field::one()); +} + +// ============================================================ +// WASM consistency tests (9 x 29-bit limb representation) +// ============================================================ + +// Verify that the 9 x 29-bit WASM limbs reconstruct to the same modulus as the 4 x 64-bit limbs, +// and that each limb fits in 29 bits. +TYPED_TEST_P(FieldConstantsTest, WasmModulusConsistency) +{ + using Params = typename TypeParam::Params; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + constexpr std::array wasm_limbs = { Params::modulus_wasm_0, Params::modulus_wasm_1, + Params::modulus_wasm_2, Params::modulus_wasm_3, + Params::modulus_wasm_4, Params::modulus_wasm_5, + Params::modulus_wasm_6, Params::modulus_wasm_7, + Params::modulus_wasm_8 }; + uint512_t wasm_modulus = 0; + for (size_t i = 0; i < 9; i++) { + wasm_modulus += uint512_t(wasm_limbs[i]) << (29UL * i); + EXPECT_LT(wasm_limbs[i], uint64_t(1) << 29); + } + EXPECT_EQ(wasm_modulus.lo, mod); + EXPECT_EQ(wasm_modulus.hi, uint256_t(0)); +} + +// Verify R_wasm^2 mod p, where R_wasm = 2^261 = 2^(29*9). +TYPED_TEST_P(FieldConstantsTest, WasmRSquared) +{ + using Params = typename TypeParam::Params; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + uint512_t R_wasm = uint512_t(1) << 261; + uint512_t R_wasm_mod = R_wasm % mod; + uint512_t expected = (R_wasm_mod * R_wasm_mod) % mod; + uint256_t actual{ + Params::r_squared_wasm_0, Params::r_squared_wasm_1, Params::r_squared_wasm_2, Params::r_squared_wasm_3 + }; + EXPECT_EQ(expected.lo, actual); + EXPECT_EQ(expected.hi, uint256_t(0)); +} + +// Verify r_inv_wasm_{0-8} = 2^{-29} mod p, stored as 9 x 29-bit limbs. +// Also verify each limb fits in 29 bits, and that it is smaller than the modulus +TYPED_TEST_P(FieldConstantsTest, WasmPowMinus29) +{ + using Params = typename TypeParam::Params; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + constexpr std::array r_inv_wasm_limbs = { + Params::r_inv_wasm_0, Params::r_inv_wasm_1, Params::r_inv_wasm_2, Params::r_inv_wasm_3, Params::r_inv_wasm_4, + Params::r_inv_wasm_5, Params::r_inv_wasm_6, Params::r_inv_wasm_7, Params::r_inv_wasm_8 + }; + uint512_t r_inv_wasm = 0; + for (size_t i = 0; i < 9; i++) { + r_inv_wasm += uint512_t(r_inv_wasm_limbs[i]) << (29UL * i); + EXPECT_LT(r_inv_wasm_limbs[i], uint64_t(1) << 29); + } + uint512_t two_29 = uint512_t(1) << 29; + uint512_t expected = two_29.invmod(mod); + EXPECT_EQ(r_inv_wasm, expected); + EXPECT_LT(r_inv_wasm, uint512_t(mod)); +} + +// Verify cube_root_wasm is the same field element as cube_root_native but in WASM Montgomery form. +// Since R_wasm = 2^261 and R_native = 2^256, converting native -> WASM multiplies by 2^5 = 32. +TYPED_TEST_P(FieldConstantsTest, WasmCubeRootConsistency) +{ + if constexpr (!TypeParam::has_cube_root) { + GTEST_SKIP() << "Cube root is not used for this field"; + } else { + using Params = typename TypeParam::Params; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + uint256_t cube_root_native{ + Params::cube_root_0, Params::cube_root_1, Params::cube_root_2, Params::cube_root_3 + }; + uint256_t cube_root_wasm{ + Params::cube_root_wasm_0, Params::cube_root_wasm_1, Params::cube_root_wasm_2, Params::cube_root_wasm_3 + }; + // R_wasm / R_native = 2^261 / 2^256 = 2^5 = 32 + uint512_t expected = (uint512_t(cube_root_native) * 32) % mod; + EXPECT_EQ(expected.lo, cube_root_wasm); + } +} + +// Verify primitive_root_wasm is the same field element as primitive_root_native in WASM Montgomery form. +TYPED_TEST_P(FieldConstantsTest, WasmPrimitiveRootConsistency) +{ + if constexpr (!TypeParam::has_primitive_root) { + GTEST_SKIP() << "Primitive root is not used for this field"; + } else { + using Params = typename TypeParam::Params; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + uint256_t primitive_root_native{ + Params::primitive_root_0, Params::primitive_root_1, Params::primitive_root_2, Params::primitive_root_3 + }; + uint256_t primitive_root_wasm{ Params::primitive_root_wasm_0, + Params::primitive_root_wasm_1, + Params::primitive_root_wasm_2, + Params::primitive_root_wasm_3 }; + // R_wasm / R_native = 2^261 / 2^256 = 2^5 = 32 + uint512_t expected = (uint512_t(primitive_root_native) * 32) % mod; + EXPECT_EQ(expected.lo, primitive_root_wasm); + } +} + +// Verify coset_generator_wasm is the same field element as coset_generator_native in WASM Montgomery form. +TYPED_TEST_P(FieldConstantsTest, CosetGeneratorConsistency) +{ + using Params = typename TypeParam::Params; + uint256_t mod{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; + uint256_t coset_generator_native{ + Params::coset_generator_0, Params::coset_generator_1, Params::coset_generator_2, Params::coset_generator_3 + }; + uint256_t coset_generator_wasm{ Params::coset_generator_wasm_0, + Params::coset_generator_wasm_1, + Params::coset_generator_wasm_2, + Params::coset_generator_wasm_3 }; + // R_wasm / R_native = 2^261 / 2^256 = 2^5 = 32 + uint512_t expected = (static_cast(coset_generator_native) * 32) % mod; + EXPECT_EQ(expected, static_cast(coset_generator_wasm)); +} + +REGISTER_TYPED_TEST_SUITE_P(FieldConstantsTest, + Modulus, + RSquared, + RInv, + PowMinus64, + CubeRootOfUnity, + PrimitiveRootOfUnity, + CosetGenerator, + WasmModulusConsistency, + WasmRSquared, + WasmPowMinus29, + WasmCubeRootConsistency, + WasmPrimitiveRootConsistency, + CosetGeneratorConsistency); + +using FieldTestTypes = ::testing::Types; + +INSTANTIATE_TYPED_TEST_SUITE_P(AllFields, FieldConstantsTest, FieldTestTypes); diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.hpp index 27101586a9ac..3c38ecd19a5c 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -12,7 +12,10 @@ namespace bb::grumpkin { +// Max num bits such that all numbers represented by that many bits are smaller than fr::modulus constexpr size_t MAX_NO_WRAP_INTEGER_BIT_LENGTH = 252; +static_assert((uint256_t(1) << (MAX_NO_WRAP_INTEGER_BIT_LENGTH + 1)) - 1 < fr::modulus, + "MAX_NO_WRAP_INTEGER_BIT_LENGTH is too large"); using fq = bb::fr; using fr = bb::fq; @@ -22,7 +25,6 @@ struct G1Params { static constexpr bool can_hash_to_curve = true; static constexpr bool small_elements = true; static constexpr bool has_a = false; -// have checked in grumpkin.test_b that b is Montgomery form of -17 #if defined(__SIZEOF_INT128__) && !defined(__wasm__) static constexpr bb::fr b{ 0xdd7056026000005a, 0x223fa97acb319311, 0xcc388229877910c0, 0x34394632b724eaa }; #else @@ -30,7 +32,7 @@ struct G1Params { #endif static constexpr bb::fr a{ 0UL, 0UL, 0UL, 0UL }; - // generator point = (x, y) = (1, sqrt(-16)), sqrt(-16) = 4i + // generator point = (x, y) = (1, sqrt(-16)) = (1, -4i) static constexpr bb::fr one_x = bb::fr::one(); #if defined(__SIZEOF_INT128__) && !defined(__wasm__) static constexpr bb::fr one_y{ @@ -63,9 +65,6 @@ class Grumpkin { using AffineElement = typename Group::affine_element; static constexpr const char* name = "Grumpkin"; - // TODO(#673): This flag is temporary. It is needed in the verifier classes (GeminiVerifier, etc.) while these - // classes are instantiated with "native" curve types. Eventually, the verifier classes will be instantiated only - // with stdlib types, and "native" verification will be acheived via a simulated builder. static constexpr bool is_stdlib_type = false; // Required by SmallSubgroupIPA argument. This constant needs to divide the size of the multiplicative subgroup of diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.test.cpp index 9cb34c57740a..f2976d22423d 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.test.cpp @@ -23,6 +23,10 @@ typename Group::affine_element naive_scalar_mul(const typename Group::element& b } } // namespace +// ========================= +// Parameter-related tests +// ========================= + TEST(grumpkin, CheckB) { auto b = grumpkin::g1::curve_b; @@ -30,6 +34,29 @@ TEST(grumpkin, CheckB) EXPECT_EQ(seventeen, -b); } +TEST(grumpkin, SubgroupGenerator) +{ + bb::fq subgroup_generator = bb::curve::Grumpkin::subgroup_generator; + bb::fq subgroup_generator_inverse = bb::curve::Grumpkin::subgroup_generator_inverse; + + EXPECT_NE(subgroup_generator.pow(3), fq::one()); + EXPECT_NE(subgroup_generator.pow(29), fq::one()); + EXPECT_EQ(subgroup_generator.pow(87), fq::one()); + EXPECT_EQ(subgroup_generator * subgroup_generator_inverse, fq::one()); +} + +TEST(grumpkin, OneYIsCorrect) +{ + fr one_y = bb::grumpkin::G1Params::one_y; + auto [_, expected] = (bb::grumpkin::G1Params::b + fr::one()).sqrt(); + + EXPECT_EQ(one_y, -expected); +} + +// ========================= +// Group-related tests +// ========================= + TEST(grumpkin, RandomElement) { grumpkin::g1::element result = grumpkin::g1::element::random_element(); diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.hpp index 323c002898d6..ba2192672282 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -13,6 +13,16 @@ // NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays) namespace bb::secp256k1 { + +/** + * @brief Parameters defining the base field of the secp256k1 curve. + * + * @details When split into 4 64-bit words, the parameters are represented in little-endian, i.e. the least significant + * bit comes first. For example, to recover the modulus from the 64-bit words we concatenate its limbs to obtain: + * 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f + * + * @note These parameters can be extracted by running the script parameter_helper.py in ecc/fields + */ struct FqParams { // There is a helper script in ecc/fields/parameter_helper.py that can be used to extract these parameters from the // source code @@ -29,29 +39,15 @@ struct FqParams { static constexpr uint64_t r_squared_2 = 0; static constexpr uint64_t r_squared_3 = 0; - // Coset generators in Montgomery form for R=2^256 mod Modulus. Used in FFT-based proving systems - static constexpr uint64_t coset_generators_0[8]{ - 0x300000b73ULL, 0x400000f44ULL, 0x500001315ULL, 0x6000016e6ULL, - 0x700001ab7ULL, 0x800001e88ULL, 0x900002259ULL, 0xa0000262aULL, - }; - static constexpr uint64_t coset_generators_1[8]{ - 0, 0, 0, 0, 0, 0, 0, 0, - }; - static constexpr uint64_t coset_generators_2[8]{ - 0, 0, 0, 0, 0, 0, 0, 0, - }; - static constexpr uint64_t coset_generators_3[8]{ - 0, 0, 0, 0, 0, 0, 0, 0, - }; - // -(Modulus^-1) mod 2^64 - // This is used to compute k = r_inv * lower_limb(scalar), such that scalar + k*modulus in integers would have 0 in - // the lowest limb By performing this sequentially for 4 limbs, we get an 8-limb representation of the scalar, where - // the lowest 4 limbs are zeros. Then we can immediately divide by 2^256 by simply getting rid of the lowest 4 limbs + // This constant is used during multiplication: given an 8-limb representation of the multiplication of two field + // elements, for each of the lowest four limbs we compute: k_i = r_inv * limb_i and we add 2^{64 * i} * k_i * p to + // the result of the multiplication. In this way we zero out the lowest four limbs of the multiplication and we can + // divide by 2^256 by taking the highest four limbs. See field_docs.hpp for more details. static constexpr uint64_t r_inv = 15580212934572586289ULL; // 2^(-64) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery + // Used in the reduction mechanism, see field_docs.md // Instead of computing k, we multiply the lowest limb by this value and then add to the following 5 limbs. // This saves us from having to compute k static constexpr uint64_t r_inv_0 = 0xffffffff27c7f3a9UL; @@ -59,20 +55,6 @@ struct FqParams { static constexpr uint64_t r_inv_2 = 0xffffffffffffffffUL; static constexpr uint64_t r_inv_3 = 0xd838091dd2253530UL; - // 2^(-29) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery - // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. - // This saves us from having to compute k - static constexpr uint64_t r_inv_wasm_0 = 0xed6544e; - static constexpr uint64_t r_inv_wasm_1 = 0x1ffffffb; - static constexpr uint64_t r_inv_wasm_2 = 0x1fffffff; - static constexpr uint64_t r_inv_wasm_3 = 0x1fffffff; - static constexpr uint64_t r_inv_wasm_4 = 0x1fffffff; - static constexpr uint64_t r_inv_wasm_5 = 0x1fffffff; - static constexpr uint64_t r_inv_wasm_6 = 0x1fffffff; - static constexpr uint64_t r_inv_wasm_7 = 0x10ffffff; - static constexpr uint64_t r_inv_wasm_8 = 0x9129a9; - // A little-endian representation of the cubic root of 1 in Fq in Montgomery form split into 4 64-bit words static constexpr uint64_t cube_root_0 = 0x58a4361c8e81894eULL; static constexpr uint64_t cube_root_1 = 0x03fde1631c4b80afULL; @@ -85,6 +67,12 @@ struct FqParams { static constexpr uint64_t primitive_root_2 = 0UL; static constexpr uint64_t primitive_root_3 = 0UL; + // Coset generators in Montgomery form for R=2^256 mod Modulus. Used in FFT-based proving systems + static constexpr uint64_t coset_generator_0 = 0x300000b73ULL; + static constexpr uint64_t coset_generator_1 = 0; + static constexpr uint64_t coset_generator_2 = 0; + static constexpr uint64_t coset_generator_3 = 0; + // A little-endian representation of the modulus split into 9 29-bit limbs // This is used in wasm because we can only do multiplication with 64-bit result instead of 128-bit like in x86_64 static constexpr uint64_t modulus_wasm_0 = 0x1ffffc2f; @@ -104,6 +92,20 @@ struct FqParams { static constexpr uint64_t r_squared_wasm_2 = 0x0000000000000000UL; static constexpr uint64_t r_squared_wasm_3 = 0x0000000000000000UL; + // 2^(-29) mod Modulus + // Used in the reduction mechanism, see field_docs.md + // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. + // This saves us from having to compute k + static constexpr uint64_t r_inv_wasm_0 = 0xed6544e; + static constexpr uint64_t r_inv_wasm_1 = 0x1ffffffb; + static constexpr uint64_t r_inv_wasm_2 = 0x1fffffff; + static constexpr uint64_t r_inv_wasm_3 = 0x1fffffff; + static constexpr uint64_t r_inv_wasm_4 = 0x1fffffff; + static constexpr uint64_t r_inv_wasm_5 = 0x1fffffff; + static constexpr uint64_t r_inv_wasm_6 = 0x1fffffff; + static constexpr uint64_t r_inv_wasm_7 = 0x10ffffff; + static constexpr uint64_t r_inv_wasm_8 = 0x9129a9; + // A little-endian representation of the cube root of 1 in Fq in Montgomery form for wasm (R=2^261 mod modulus) // split into 4 64-bit words static constexpr uint64_t cube_root_wasm_0 = 0x1486c3a0d03162ffUL; @@ -119,22 +121,10 @@ struct FqParams { // Coset generators in Montgomery form for R=2^261 mod Modulus. Used in FFT-based proving systems, don't really need // them here - static constexpr uint64_t coset_generators_wasm_0[8] = { 0x0000006000016e60ULL, 0x000000800001e880ULL, - 0x000000a0000262a0ULL, 0x000000c00002dcc0ULL, - 0x000000e0000356e0ULL, 0x000001000003d100ULL, - 0x0000012000044b20ULL, 0x000001400004c540ULL }; - static constexpr uint64_t coset_generators_wasm_1[8] = { 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL }; - static constexpr uint64_t coset_generators_wasm_2[8] = { 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL }; - static constexpr uint64_t coset_generators_wasm_3[8] = { 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL }; + static constexpr uint64_t coset_generator_wasm_0 = 0x0000006000016e60ULL; + static constexpr uint64_t coset_generator_wasm_1 = 0; + static constexpr uint64_t coset_generator_wasm_2 = 0; + static constexpr uint64_t coset_generator_wasm_3 = 0; // For consistency with bb::fq, if we ever represent an element of bb::secp256k1::fq in the public inputs, we do so // as a bigfield element, so with 4 public inputs @@ -144,6 +134,15 @@ struct FqParams { }; using fq = field; +/** + * @brief Parameters defining the scalar field of the secp256k1 curve. + * + * @details When split into 4 64-bit words, the parameters are represented in little-endian, i.e. the least significant + * bit comes first. For example, to recover the modulus from the 64-bit words we concatenate its limbs to obtain: + * 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 + * + * @note These parameters can be extracted by running the script parameter_helper.py in ecc/fields + */ struct FrParams { // A little-endian representation of the modulus split into 4 64-bit words @@ -165,7 +164,7 @@ struct FrParams { static constexpr uint64_t r_inv = 5408259542528602431ULL; // 2^(-64) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery + // Used in the reduction mechanism, see field_docs.md // Instead of computing k, we multiply the lowest limb by this value and then add to the following 5 limbs. // This saves us from having to compute k static constexpr uint64_t r_inv_0 = 0x9d4ad302583de6dcUL; @@ -173,65 +172,25 @@ struct FrParams { static constexpr uint64_t r_inv_2 = 0xffffffffffffffffUL; static constexpr uint64_t r_inv_3 = 0x4b0dff665588b13eUL; - // 2^(-29) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery - // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. - // This saves us from having to compute k - static constexpr uint64_t r_inv_wasm_0 = 0x3d864e; - static constexpr uint64_t r_inv_wasm_1 = 0x8b9f61c; - static constexpr uint64_t r_inv_wasm_2 = 0x3df60c0; - static constexpr uint64_t r_inv_wasm_3 = 0xa3c71eb; - static constexpr uint64_t r_inv_wasm_4 = 0x1ffff251; - static constexpr uint64_t r_inv_wasm_5 = 0x1fffffff; - static constexpr uint64_t r_inv_wasm_6 = 0x1fffffff; - static constexpr uint64_t r_inv_wasm_7 = 0x1effffff; - static constexpr uint64_t r_inv_wasm_8 = 0xac4589; - - // Coset generators in Montgomery form for R=2^261 mod Modulus. Used in FFT-based proving systems, don't really need - // them here - static constexpr uint64_t coset_generators_0[8]{ - 0x40e4273feef0b9bbULL, 0x8111c8b31eba787aULL, 0xc13f6a264e843739ULL, 0x16d0b997e4df5f8ULL, - 0x419aad0cae17b4b7ULL, 0x81c84e7fdde17376ULL, 0xc1f5eff30dab3235ULL, 0x22391663d74f0f4ULL, - }; - static constexpr uint64_t coset_generators_1[8]{ - 0x5a95af7e9394ded5ULL, 0x9fe6d297e44c3e99ULL, 0xe537f5b135039e5dULL, 0x2a8918ca85bafe22ULL, - 0x6fda3be3d6725de6ULL, 0xb52b5efd2729bdaaULL, 0xfa7c821677e11d6eULL, 0x3fcda52fc8987d33ULL, - }; - static constexpr uint64_t coset_generators_2[8]{ - 0x6ULL, 0x7ULL, 0x8ULL, 0xaULL, 0xbULL, 0xcULL, 0xdULL, 0xfULL, - }; - static constexpr uint64_t coset_generators_3[8]{ - 0, 0, 0, 0, 0, 0, 0, 0, - }; - // A little-endian representation of the cubic root of 1 in Fr in Montgomery form split into 4 64-bit words static constexpr uint64_t cube_root_0 = 0xf07deb3dc9926c9eULL; static constexpr uint64_t cube_root_1 = 0x2c93e7ad83c6944cULL; static constexpr uint64_t cube_root_2 = 0x73a9660652697d91ULL; static constexpr uint64_t cube_root_3 = 0x532840178558d639ULL; - // Not needed, since there is no endomorphism for secp256k1 - static constexpr uint64_t endo_minus_b1_lo = 0x6F547FA90ABFE4C3ULL; - static constexpr uint64_t endo_minus_b1_mid = 0xE4437ED6010E8828ULL; - - static constexpr uint64_t endo_b2_lo = 0xe86c90e49284eb15ULL; - static constexpr uint64_t endo_b2_mid = 0x3086d221a7d46bcdULL; - - // 256-bit-shift constants: g1 = floor((-b1) * 2^256 / r), g2 = floor(b2 * 2^256 / r) - // See endomorphism_scalars.py compute_splitting_constants() for derivation. - static constexpr uint64_t endo_g1_lo = 0x6F547FA90ABFE4C4ULL; - static constexpr uint64_t endo_g1_mid = 0xE4437ED6010E8828ULL; - static constexpr uint64_t endo_g1_hi = 0x0ULL; - - static constexpr uint64_t endo_g2_lo = 0xE86C90E49284EB15ULL; - static constexpr uint64_t endo_g2_mid = 0x3086D221A7D46BCDULL; - // Not used in secp256k1 static constexpr uint64_t primitive_root_0 = 0UL; static constexpr uint64_t primitive_root_1 = 0UL; static constexpr uint64_t primitive_root_2 = 0UL; static constexpr uint64_t primitive_root_3 = 0UL; + // Coset generators in Montgomery form for R=2^261 mod Modulus. Used in FFT-based proving systems, don't really need + // them here + static constexpr uint64_t coset_generator_0 = 0x40e4273feef0b9bbULL; + static constexpr uint64_t coset_generator_1 = 0x5a95af7e9394ded5ULL; + static constexpr uint64_t coset_generator_2 = 0x6ULL; + static constexpr uint64_t coset_generator_3 = 0x0ULL; + // A little-endian representation of the modulus split into 9 29-bit limbs // This is used in wasm because we can only do multiplication with 64-bit result instead of 128-bit like in x86_64 static constexpr uint64_t modulus_wasm_0 = 0x10364141; @@ -251,6 +210,20 @@ struct FrParams { static constexpr uint64_t r_squared_wasm_2 = 0x5fd7916f341f1cefUL; static constexpr uint64_t r_squared_wasm_3 = 0x9c7356071a6f179aUL; + // 2^(-29) mod Modulus + // Used in the reduction mechanism, see field_docs.md + // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. + // This saves us from having to compute k + static constexpr uint64_t r_inv_wasm_0 = 0x3d864e; + static constexpr uint64_t r_inv_wasm_1 = 0x8b9f61c; + static constexpr uint64_t r_inv_wasm_2 = 0x3df60c0; + static constexpr uint64_t r_inv_wasm_3 = 0xa3c71eb; + static constexpr uint64_t r_inv_wasm_4 = 0x1ffff251; + static constexpr uint64_t r_inv_wasm_5 = 0x1fffffff; + static constexpr uint64_t r_inv_wasm_6 = 0x1fffffff; + static constexpr uint64_t r_inv_wasm_7 = 0x1effffff; + static constexpr uint64_t r_inv_wasm_8 = 0xac4589; + // A little-endian representation of the cube root of 1 in Fr in Montgomery form for wasm (R=2^261 mod modulus) // split into 4 64-bit words static constexpr uint64_t cube_root_wasm_0 = 0x9185b639102f0736UL; @@ -264,24 +237,28 @@ struct FrParams { static constexpr uint64_t primitive_root_wasm_2 = 0x0000000000000000UL; static constexpr uint64_t primitive_root_wasm_3 = 0x0000000000000000UL; + // Not needed, since there is no endomorphism for secp256k1 + static constexpr uint64_t endo_minus_b1_lo = 0x6F547FA90ABFE4C3ULL; + static constexpr uint64_t endo_minus_b1_mid = 0xE4437ED6010E8828ULL; + + static constexpr uint64_t endo_b2_lo = 0xe86c90e49284eb15ULL; + static constexpr uint64_t endo_b2_mid = 0x3086d221a7d46bcdULL; + + // 256-bit-shift constants: g1 = floor((-b1) * 2^256 / r), g2 = floor(b2 * 2^256 / r) + // See endomorphism_scalars.py compute_splitting_constants() for derivation. + static constexpr uint64_t endo_g1_lo = 0x6F547FA90ABFE4C4ULL; + static constexpr uint64_t endo_g1_mid = 0xE4437ED6010E8828ULL; + static constexpr uint64_t endo_g1_hi = 0x0ULL; + + static constexpr uint64_t endo_g2_lo = 0xE86C90E49284EB15ULL; + static constexpr uint64_t endo_g2_mid = 0x3086D221A7D46BCDULL; + // Coset generators in Montgomery form for R=2^261 mod Modulus. Used in FFT-based proving systems, don't really need // them here - static constexpr uint64_t coset_generators_wasm_0[8] = { 0x1c84e7fdde173760ULL, 0x22391663d74f0f40ULL, - 0x27ed44c9d086e720ULL, 0x2da1732fc9bebf00ULL, - 0x3355a195c2f696e0ULL, 0x3909cffbbc2e6ec0ULL, - 0x3ebdfe61b56646a0ULL, 0x44722cc7ae9e1e80ULL }; - static constexpr uint64_t coset_generators_wasm_1[8] = { 0x52b5efd2729bdaa8ULL, 0xfcda52fc8987d330ULL, - 0xa6feb626a073cbb8ULL, 0x51231950b75fc440ULL, - 0xfb477c7ace4bbcc8ULL, 0xa56bdfa4e537b550ULL, - 0x4f9042cefc23add8ULL, 0xf9b4a5f9130fa660ULL }; - static constexpr uint64_t coset_generators_wasm_2[8] = { 0x00000000000000cbULL, 0x00000000000000f3ULL, - 0x000000000000011cULL, 0x0000000000000145ULL, - 0x000000000000016dULL, 0x0000000000000196ULL, - 0x00000000000001bfULL, 0x00000000000001e7ULL }; - static constexpr uint64_t coset_generators_wasm_3[8] = { 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL, - 0x0000000000000000ULL, 0x0000000000000000ULL }; + static constexpr uint64_t coset_generator_wasm_0 = 0x1c84e7fdde173760ULL; + static constexpr uint64_t coset_generator_wasm_1 = 0x52b5efd2729bdaa8ULL; + static constexpr uint64_t coset_generator_wasm_2 = 0x00000000000000cbULL; + static constexpr uint64_t coset_generator_wasm_3 = 0x0000000000000000ULL; // For consistency with bb::fq, if we ever represent an element of bb::secp256k1::fr in the public inputs, we do so // as a bigfield element, so with 4 public inputs diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.test.cpp index 2af8a939dbc0..40bbc71bc852 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.test.cpp @@ -9,10 +9,23 @@ using namespace bb; // - barretenberg/ecc/fields/field.test.cpp (generic field tests) // - barretenberg/ecc/fields/prime_field.test.cpp (prime field specific tests) // The tests below are for the secp256k1 elliptic curve group operations. +TEST(secp256k1, CurveCoefficients) +{ + secp256k1::fq expected_a = secp256k1::fq(0); + secp256k1::fq expected_b = secp256k1::fq(7); + + EXPECT_EQ(secp256k1::G1Params::a, expected_a); + EXPECT_EQ(secp256k1::G1Params::b, expected_b); +} TEST(secp256k1, GeneratorOnCurve) { secp256k1::g1::element result = secp256k1::g1::one; + secp256k1::fq expected_x = secp256k1::fq("0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"); + secp256k1::fq expected_y = secp256k1::fq("0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"); + + EXPECT_EQ(result.x, expected_x); + EXPECT_EQ(result.y, expected_y); EXPECT_EQ(result.on_curve(), true); } diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256r1/secp256r1.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256r1/secp256r1.hpp index f3507361eb61..de51a97b0eb8 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256r1/secp256r1.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256r1/secp256r1.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -9,8 +9,18 @@ #include "../../fields/field.hpp" #include "../../groups/group.hpp" -namespace bb::secp256r1 { // NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays) +namespace bb::secp256r1 { + +/** + * @brief Parameters defining the base field of the secp256r1 curve. + * + * @details When split into 4 64-bit words, the parameters are represented in little-endian, i.e. the least significant + * bit comes first. For example, to recover the modulus from the 64-bit words we concatenate its limbs to obtain: + * 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff + * + * @note These parameters can be extracted by running the script parameter_helper.py in ecc/fields + */ struct FqParams { static constexpr const char* schema_name = "secp256r1_fq"; @@ -27,13 +37,14 @@ struct FqParams { static constexpr uint64_t r_squared_3 = 21474836477ULL; // -(Modulus^-1) mod 2^64 - // This is used to compute k = r_inv * lower_limb(scalar), such that scalar + k*modulus in integers would have 0 in - // the lowest limb By performing this sequentially for 4 limbs, we get an 8-limb representation of the scalar, where - // the lowest 4 limbs are zeros. Then we can immediately divide by 2^256 by simply getting rid of the lowest 4 limbs + // This constant is used during multiplication: given an 8-limb representation of the multiplication of two field + // elements, for each of the lowest four limbs we compute: k_i = r_inv * limb_i and we add 2^{64 * i} * k_i * p to + // the result of the multiplication. In this way we zero out the lowest four limbs of the multiplication and we can + // divide by 2^256 by taking the highest four limbs. See field_docs.hpp for more details. static constexpr uint64_t r_inv = 1; // 2^(-64) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery + // Used in the reduction mechanism, see field_docs.md // Instead of computing k, we multiply the lowest limb by this value and then add to the following 5 limbs. // This saves us from having to compute k static constexpr uint64_t r_inv_0 = 0x100000000UL; @@ -41,38 +52,6 @@ struct FqParams { static constexpr uint64_t r_inv_2 = 0xffffffff00000001UL; static constexpr uint64_t r_inv_3 = 0x0UL; - // 2^(-29) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery - // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. - // This saves us from having to compute k - static constexpr uint64_t r_inv_wasm_0 = 0x0; - static constexpr uint64_t r_inv_wasm_1 = 0x0; - static constexpr uint64_t r_inv_wasm_2 = 0x200; - static constexpr uint64_t r_inv_wasm_3 = 0x0; - static constexpr uint64_t r_inv_wasm_4 = 0x0; - static constexpr uint64_t r_inv_wasm_5 = 0x40000; - static constexpr uint64_t r_inv_wasm_6 = 0x1fe00000; - static constexpr uint64_t r_inv_wasm_7 = 0xffffff; - static constexpr uint64_t r_inv_wasm_8 = 0x0; - - // Coset generators in Montgomery form for R=2^256 mod Modulus. Used in FFT-based proving systems, don't really need - // them here - static constexpr uint64_t coset_generators_0[8]{ - 0x3ULL, 0x4ULL, 0x5ULL, 0x6ULL, 0x7ULL, 0x8ULL, 0x9ULL, 0xaULL, - }; - static constexpr uint64_t coset_generators_1[8]{ - 0xfffffffd00000000ULL, 0xfffffffc00000000ULL, 0xfffffffb00000000ULL, 0xfffffffa00000000ULL, - 0xfffffff900000000ULL, 0xfffffff800000000ULL, 0xfffffff700000000ULL, 0xfffffff600000000ULL, - }; - static constexpr uint64_t coset_generators_2[8]{ - 0xffffffffffffffffULL, 0xffffffffffffffffULL, 0xffffffffffffffffULL, 0xffffffffffffffffULL, - 0xffffffffffffffffULL, 0xffffffffffffffffULL, 0xffffffffffffffffULL, 0xffffffffffffffffULL, - }; - static constexpr uint64_t coset_generators_3[8]{ - 0x2fffffffcULL, 0x3fffffffbULL, 0x4fffffffaULL, 0x5fffffff9ULL, - 0x6fffffff8ULL, 0x7fffffff7ULL, 0x8fffffff6ULL, 0x9fffffff5ULL, - }; - // Not used for secp256r1 static constexpr uint64_t cube_root_0 = 0UL; static constexpr uint64_t cube_root_1 = 0UL; @@ -85,6 +64,13 @@ struct FqParams { static constexpr uint64_t primitive_root_2 = 0UL; static constexpr uint64_t primitive_root_3 = 0UL; + // Coset generators in Montgomery form for R=2^256 mod Modulus. Used in FFT-based proving systems, don't really need + // them here + static constexpr uint64_t coset_generator_0 = 0x3ULL; + static constexpr uint64_t coset_generator_1 = 0xfffffffd00000000ULL; + static constexpr uint64_t coset_generator_2 = 0xffffffffffffffffULL; + static constexpr uint64_t coset_generator_3 = 0x2fffffffcULL; + // A little-endian representation of the modulus split into 9 29-bit limbs // This is used in wasm because we can only do multiplication with 64-bit result instead of 128-bit like in x86_64 static constexpr uint64_t modulus_wasm_0 = 0x1fffffff; @@ -104,6 +90,20 @@ struct FqParams { static constexpr uint64_t r_squared_wasm_2 = 0xfffffffffffffbffUL; static constexpr uint64_t r_squared_wasm_3 = 0x000013fffffff7ffUL; + // 2^(-29) mod Modulus + // Used in the reduction mechanism, see field_docs.md + // Instead of computing k, we multiply the lowest limb by this value and then add to the following 10 limbs. + // This saves us from having to compute k + static constexpr uint64_t r_inv_wasm_0 = 0x0; + static constexpr uint64_t r_inv_wasm_1 = 0x0; + static constexpr uint64_t r_inv_wasm_2 = 0x200; + static constexpr uint64_t r_inv_wasm_3 = 0x0; + static constexpr uint64_t r_inv_wasm_4 = 0x0; + static constexpr uint64_t r_inv_wasm_5 = 0x40000; + static constexpr uint64_t r_inv_wasm_6 = 0x1fe00000; + static constexpr uint64_t r_inv_wasm_7 = 0xffffff; + static constexpr uint64_t r_inv_wasm_8 = 0x0; + // Not used for secp256r1 static constexpr uint64_t cube_root_wasm_0 = 0x0000000000000000UL; static constexpr uint64_t cube_root_wasm_1 = 0x0000000000000000UL; @@ -118,22 +118,10 @@ struct FqParams { // Coset generators in Montgomery form for R=2^261 mod Modulus. Used in FFT-based proving systems, don't really need // them here - static constexpr uint64_t coset_generators_wasm_0[8] = { 0x0000000000000060ULL, 0x0000000000000080ULL, - 0x00000000000000a0ULL, 0x00000000000000c0ULL, - 0x00000000000000e0ULL, 0x0000000000000100ULL, - 0x0000000000000120ULL, 0x0000000000000140ULL }; - static constexpr uint64_t coset_generators_wasm_1[8] = { 0xffffffa000000000ULL, 0xffffff8000000000ULL, - 0xffffff6000000000ULL, 0xffffff4000000000ULL, - 0xffffff2000000000ULL, 0xffffff0000000000ULL, - 0xfffffee000000000ULL, 0xfffffec000000000ULL }; - static constexpr uint64_t coset_generators_wasm_2[8] = { 0xffffffffffffffffULL, 0xffffffffffffffffULL, - 0xffffffffffffffffULL, 0xffffffffffffffffULL, - 0xffffffffffffffffULL, 0xffffffffffffffffULL, - 0xffffffffffffffffULL, 0xffffffffffffffffULL }; - static constexpr uint64_t coset_generators_wasm_3[8] = { 0x0000005fffffff9fULL, 0x0000007fffffff7fULL, - 0x0000009fffffff5fULL, 0x000000bfffffff3fULL, - 0x000000dfffffff1fULL, 0x000000fffffffeffULL, - 0x0000011ffffffedfULL, 0x0000013ffffffebfULL }; + static constexpr uint64_t coset_generator_wasm_0 = 0x0000000000000060ULL; + static constexpr uint64_t coset_generator_wasm_1 = 0xffffffa000000000ULL; + static constexpr uint64_t coset_generator_wasm_2 = 0xffffffffffffffffULL; + static constexpr uint64_t coset_generator_wasm_3 = 0x0000005fffffff9fULL; // For consistency with bb::fq, if we ever represent an element of bb::secp256r1::fq in the public inputs, we do so // as a bigfield element, so with 4 public inputs @@ -141,6 +129,15 @@ struct FqParams { }; using fq = field; +/** + * @brief Parameters defining the scalar field of the secp256r1 curve. + * + * @details When split into 4 64-bit words, the parameters are represented in little-endian, i.e. the least significant + * bit comes first. For example, to recover the modulus from the 64-bit words we concatenate its limbs to obtain: + * 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 + * + * @note These parameters can be extracted by running the script parameter_helper.py in ecc/fields + */ struct FrParams { static constexpr const char* schema_name = "secp256r1_fr"; @@ -163,7 +160,7 @@ struct FrParams { static constexpr uint64_t r_inv = 14758798090332847183ULL; // 2^(-64) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery + // Used in the reduction mechanism, see field_docs.md // Instead of computing k, we multiply the lowest limb by this value and then add to the following 5 limbs. // This saves us from having to compute k static constexpr uint64_t r_inv_0 = 0x230102a06d6251dcUL; @@ -171,38 +168,6 @@ struct FrParams { static constexpr uint64_t r_inv_2 = 0xded10c5bee00bc4eUL; static constexpr uint64_t r_inv_3 = 0xccd1c8aa212ef3a4UL; - // 2^(-29) mod Modulus - // Used in the reduction mechanism from https://hackmd.io/@Ingonyama/Barret-Montgomery - // Instead of computing k, we multiply the lowest limb by this value and then add to the following 5 limbs. - // This saves us from having to compute k - static constexpr uint64_t r_inv_wasm_0 = 0x8517c79; - static constexpr uint64_t r_inv_wasm_1 = 0x1edc694; - static constexpr uint64_t r_inv_wasm_2 = 0x459ee5c; - static constexpr uint64_t r_inv_wasm_3 = 0x705a6a8; - static constexpr uint64_t r_inv_wasm_4 = 0x1ffffe2a; - static constexpr uint64_t r_inv_wasm_5 = 0x113bffff; - static constexpr uint64_t r_inv_wasm_6 = 0x1621c017; - static constexpr uint64_t r_inv_wasm_7 = 0xef1ff43; - static constexpr uint64_t r_inv_wasm_8 = 0x7005e2; - - // Coset generators in Montgomery form for R=2^256 mod Modulus. Used in FFT-based proving systems, don't really need - // them here - static constexpr uint64_t coset_generators_0[8]{ - 0x55eb74ab1949fac9ULL, 0x6231a9e81ce6d578ULL, 0x6e77df252083b027ULL, 0x7abe146224208ad6ULL, - 0x8704499f27bd6585ULL, 0x934a7edc2b5a4034ULL, 0x9f90b4192ef71ae3ULL, 0xabd6e9563293f592ULL, - }; - static constexpr uint64_t coset_generators_1[8]{ - 0xd5af25406e5aaa5dULL, 0x18c82a92c7430bd8ULL, 0x5be12fe5202b6d53ULL, 0x9efa35377913ceceULL, - 0xe2133a89d1fc3049ULL, 0x252c3fdc2ae491c4ULL, 0x6845452e83ccf33fULL, 0xab5e4a80dcb554baULL, - }; - static constexpr uint64_t coset_generators_2[8]{ - 0x1ULL, 0x2ULL, 0x2ULL, 0x2ULL, 0x2ULL, 0x3ULL, 0x3ULL, 0x3ULL, - }; - static constexpr uint64_t coset_generators_3[8]{ - 0x6fffffff9ULL, 0x7fffffff8ULL, 0x8fffffff7ULL, 0x9fffffff6ULL, - 0xafffffff5ULL, 0xbfffffff4ULL, 0xcfffffff3ULL, 0xdfffffff2ULL, - }; - // Not used for secp256r1 static constexpr uint64_t cube_root_0 = 0UL; static constexpr uint64_t cube_root_1 = 0UL; @@ -215,6 +180,13 @@ struct FrParams { static constexpr uint64_t primitive_root_2 = 0UL; static constexpr uint64_t primitive_root_3 = 0UL; + // Coset generator in Montgomery form for R=2^256 mod Modulus. Used in FFT-based proving systems, don't really need + // them here + static constexpr uint64_t coset_generator_0 = 0x55eb74ab1949fac9ULL; + static constexpr uint64_t coset_generator_1 = 0xd5af25406e5aaa5dULL; + static constexpr uint64_t coset_generator_2 = 0x1ULL; + static constexpr uint64_t coset_generator_3 = 0x6fffffff9ULL; + // A little-endian representation of the modulus split into 9 29-bit limbs // This is used in wasm because we can only do multiplication with 64-bit result instead of 128-bit like in x86_64 static constexpr uint64_t modulus_wasm_0 = 0x1c632551; @@ -234,6 +206,20 @@ struct FrParams { static constexpr uint64_t r_squared_wasm_2 = 0x16c8e4adafb16586UL; static constexpr uint64_t r_squared_wasm_3 = 0x84b6556a65587f06UL; + // 2^(-29) mod Modulus + // Used in the reduction mechanism, see field_docs.md + // Instead of computing k, we multiply the lowest limb by this value and then add to the following 5 limbs. + // This saves us from having to compute k + static constexpr uint64_t r_inv_wasm_0 = 0x8517c79; + static constexpr uint64_t r_inv_wasm_1 = 0x1edc694; + static constexpr uint64_t r_inv_wasm_2 = 0x459ee5c; + static constexpr uint64_t r_inv_wasm_3 = 0x705a6a8; + static constexpr uint64_t r_inv_wasm_4 = 0x1ffffe2a; + static constexpr uint64_t r_inv_wasm_5 = 0x113bffff; + static constexpr uint64_t r_inv_wasm_6 = 0x1621c017; + static constexpr uint64_t r_inv_wasm_7 = 0xef1ff43; + static constexpr uint64_t r_inv_wasm_8 = 0x7005e2; + // Not used for secp256r1 static constexpr uint64_t cube_root_wasm_0 = 0x0000000000000000UL; static constexpr uint64_t cube_root_wasm_1 = 0x0000000000000000UL; @@ -248,22 +234,10 @@ struct FrParams { // Coset generators in Montgomery form for R=2^261 mod Modulus. Used in FFT-based proving systems, don't really need // them here - static constexpr uint64_t coset_generators_wasm_0[8] = { 0xbd6e9563293f5920ULL, 0x46353d039cdaaf00ULL, - 0xcefbe4a4107604e0ULL, 0x57c28c4484115ac0ULL, - 0xe08933e4f7acb0a0ULL, 0x694fdb856b480680ULL, - 0xf2168325dee35c60ULL, 0x7add2ac6527eb240ULL }; - static constexpr uint64_t coset_generators_wasm_1[8] = { 0xb5e4a80dcb554baaULL, 0x19055258e8617b0cULL, - 0x7c25fca4056daa6dULL, 0xdf46a6ef2279d9cfULL, - 0x4267513a3f860930ULL, 0xa587fb855c923892ULL, - 0x08a8a5d0799e67f3ULL, 0x6bc9501b96aa9755ULL }; - static constexpr uint64_t coset_generators_wasm_2[8] = { 0x000000000000003aULL, 0x0000000000000043ULL, - 0x000000000000004bULL, 0x0000000000000053ULL, - 0x000000000000005cULL, 0x0000000000000064ULL, - 0x000000000000006dULL, 0x0000000000000075ULL }; - static constexpr uint64_t coset_generators_wasm_3[8] = { 0x000000dfffffff20ULL, 0x000000ffffffff00ULL, - 0x0000011ffffffee0ULL, 0x0000013ffffffec0ULL, - 0x0000015ffffffea0ULL, 0x0000017ffffffe80ULL, - 0x0000019ffffffe60ULL, 0x000001bffffffe40ULL }; + static constexpr uint64_t coset_generator_wasm_0 = 0xbd6e9563293f5920ULL; + static constexpr uint64_t coset_generator_wasm_1 = 0xb5e4a80dcb554baaULL; + static constexpr uint64_t coset_generator_wasm_2 = 0x000000000000003aULL; + static constexpr uint64_t coset_generator_wasm_3 = 0x000000dfffffff20ULL; // For consistency with bb::fq, if we ever represent an element of bb::secp256r1::fq in the public inputs, we do so // as a bigfield element, so with 4 public inputs diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256r1/secp256r1.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256r1/secp256r1.test.cpp index 95c157a28b73..cb63ad7b0c66 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256r1/secp256r1.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256r1/secp256r1.test.cpp @@ -10,9 +10,23 @@ using namespace bb; // - barretenberg/ecc/fields/prime_field.test.cpp (prime field specific tests) // The tests below are for the secp256r1 elliptic curve group operations. +TEST(secp256r1, CurveCoefficients) +{ + secp256r1::fq expected_a = secp256r1::fq("0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc"); + secp256r1::fq expected_b = secp256r1::fq("0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b"); + + EXPECT_EQ(secp256r1::G1Params::a, expected_a); + EXPECT_EQ(secp256r1::G1Params::b, expected_b); +} + TEST(secp256r1, GeneratorOnCurve) { secp256r1::g1::element result = secp256r1::g1::one; + secp256r1::fq expected_x = secp256r1::fq("0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296"); + secp256r1::fq expected_y = secp256r1::fq("0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5"); + + EXPECT_EQ(result.x, expected_x); + EXPECT_EQ(result.y, expected_y); EXPECT_EQ(result.on_curve(), true); } diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/types.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/types.hpp index ce590f41cefa..e2966d3f5e3c 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/types.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/types.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Completed, auditors: [Federico], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== diff --git a/barretenberg/cpp/src/barretenberg/ecc/fields/field12.hpp b/barretenberg/cpp/src/barretenberg/ecc/fields/field12.hpp index 757c01cf464d..9fc82788945b 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/fields/field12.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/fields/field12.hpp @@ -271,7 +271,7 @@ template cl }; } - constexpr field12 from_montgomery_form() + constexpr field12 from_montgomery_form() const { return { c0.from_montgomery_form(), diff --git a/barretenberg/cpp/src/barretenberg/ecc/fields/field2_declarations.hpp b/barretenberg/cpp/src/barretenberg/ecc/fields/field2_declarations.hpp index 69a2c7ebba57..ae8c62a88f56 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/fields/field2_declarations.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/fields/field2_declarations.hpp @@ -62,17 +62,13 @@ template struct alignas(32) field2 { static constexpr field2 zero() { return field2{ base_field::zero(), base_field::zero() }; } static constexpr field2 one() { return field2{ base_field::one(), base_field::zero() }; } static constexpr field2 twist_coeff_b() { return field2{ Params::twist_coeff_b_0, Params::twist_coeff_b_1 }; } - static constexpr field2 twist_mul_by_q_x() + static constexpr field2 frobenius_on_twisted_curve_x() { - return field2{ Params::twist_mul_by_q_x_0, Params::twist_mul_by_q_x_1 }; + return field2{ Params::frobenius_on_twisted_curve_x_0, Params::frobenius_on_twisted_curve_x_1 }; } - static constexpr field2 twist_mul_by_q_y() + static constexpr field2 frobenius_on_twisted_curve_y() { - return field2{ Params::twist_mul_by_q_y_0, Params::twist_mul_by_q_y_1 }; - } - static constexpr field2 cube_root_of_unity() - { - return field2{ Params::twist_cube_root_0, Params::twist_cube_root_1 }; + return field2{ Params::frobenius_on_twisted_curve_y_0, Params::frobenius_on_twisted_curve_y_1 }; } constexpr field2 operator*(const field2& other) const noexcept; diff --git a/barretenberg/cpp/src/barretenberg/ecc/fields/field_declarations.hpp b/barretenberg/cpp/src/barretenberg/ecc/fields/field_declarations.hpp index 3de47d38bb6d..5cecfce43828 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/fields/field_declarations.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/fields/field_declarations.hpp @@ -278,64 +278,21 @@ template struct alignas(32) field { static constexpr field neg_one() { return -field(1); } static constexpr field one() { return field(1); } - static constexpr field external_coset_generator() + static constexpr field coset_generator() { #if defined(__SIZEOF_INT128__) && !defined(__wasm__) const field result{ - Params::coset_generators_0[7], - Params::coset_generators_1[7], - Params::coset_generators_2[7], - Params::coset_generators_3[7], + Params::coset_generator_0, + Params::coset_generator_1, + Params::coset_generator_2, + Params::coset_generator_3, }; #else const field result{ - Params::coset_generators_wasm_0[7], - Params::coset_generators_wasm_1[7], - Params::coset_generators_wasm_2[7], - Params::coset_generators_wasm_3[7], - }; -#endif - - return result; - } - - static constexpr field tag_coset_generator() - { -#if defined(__SIZEOF_INT128__) && !defined(__wasm__) - const field result{ - Params::coset_generators_0[6], - Params::coset_generators_1[6], - Params::coset_generators_2[6], - Params::coset_generators_3[6], - }; -#else - const field result{ - Params::coset_generators_wasm_0[6], - Params::coset_generators_wasm_1[6], - Params::coset_generators_wasm_2[6], - Params::coset_generators_wasm_3[6], - }; -#endif - - return result; - } - - template static constexpr field coset_generator() - { - static_assert(idx < 7); -#if defined(__SIZEOF_INT128__) && !defined(__wasm__) - const field result{ - Params::coset_generators_0[idx], - Params::coset_generators_1[idx], - Params::coset_generators_2[idx], - Params::coset_generators_3[idx], - }; -#else - const field result{ - Params::coset_generators_wasm_0[idx], - Params::coset_generators_wasm_1[idx], - Params::coset_generators_wasm_2[idx], - Params::coset_generators_wasm_3[idx], + Params::coset_generator_0, + Params::coset_generator_1, + Params::coset_generator_2, + Params::coset_generator_3, }; #endif @@ -575,9 +532,6 @@ template struct alignas(32) field { }; } - // static constexpr auto coset_generators = compute_coset_generators(); - // static constexpr std::array coset_generators = compute_coset_generators((1 << 30U)); - friend std::ostream& operator<<(std::ostream& os, const field& a) { field out = a.from_montgomery_form_reduced(); @@ -729,7 +683,6 @@ template struct alignas(32) field { BB_INLINE static void asm_self_reduce_once(const field& a) noexcept; static constexpr uint64_t zero_reference = 0x00ULL; #endif - static constexpr size_t COSET_GENERATOR_SIZE = 15; constexpr field tonelli_shanks_sqrt() const noexcept; static constexpr size_t primitive_root_log_size() noexcept; diff --git a/barretenberg/cpp/src/barretenberg/ecc/fields/field_docs.md b/barretenberg/cpp/src/barretenberg/ecc/fields/field_docs.md index f28a4e32510e..4fee2327dc28 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/fields/field_docs.md +++ b/barretenberg/cpp/src/barretenberg/ecc/fields/field_docs.md @@ -1,45 +1,45 @@ Prime field documentation {#field_docs} === -Barretenberg has its own implementation of finite field arithmetic. The implementation targets 254-bit (bn254, grumpkin) and 256-bit (secp256k1, secp256r1) fields. Internally the field is represented as a little-endian C-array of 4 uint64_t limbs. For 254-bit fields, the internal representation must be in the range \f$[0, 2p)\f$ (which we refer to as the _coarse representation_), while for 256-bit fields the internal representation is an arbitrary `uint256_t`. +Barretenberg has its own implementation of finite field arithmetic. The implementation targets 254-bit (bn254, grumpkin) and 256-bit (secp256k1, secp256r1) fields. Internally the field is represented as a little-endian C-array of 4 uint64_t limbs. For 254-bit fields, the internal representation must be in the range $[0, 2p)$ (which we refer to as the _coarse representation_), while for 256-bit fields the internal representation is an arbitrary `uint256_t`. ## Field arithmetic ### Introduction to Montgomery form {#field_docs_montgomery_explainer} -We use Montgomery multiplication to speed up field multiplication. For an original element \f$ a \in \mathbb F_p\f$ the element is represented internally as $$ a⋅R\ mod\ p$$ where \f$R = 2^d\ mod\ p\f$. The chosen \f$d\f$ depends on the build configuration: -1. \f$d=29⋅9=261\f$ for builds that don't support the `uint128_t` type, for example, for WASM build -2. \f$d=64⋅4=256\f$ for standard builds (x86_64). +We use Montgomery multiplication to speed up field multiplication. For an original element $ a \in \mathbb F_p$ the element is represented internally as $$ a⋅R\ mod\ p$$ where $R = 2^d\ mod\ p$. The chosen $d$ depends on the build configuration: +1. $d=29⋅9=261$ for builds that don't support the `uint128_t` type, for example, for WASM build +2. $d=64⋅4=256$ for standard builds (x86_64). -The goal of using Montgomery form is to avoid heavy division modulo \f$p\f$. To compute a representative of element $$c = a⋅b\ mod\ p$$ we compute $$c⋅R = (a⋅R)⋅(b⋅R) / R\ mod\ p,$$ but we use an efficient division trick to avoid the naive modular division. Let's look into the standard 4⋅64 case: +The goal of using Montgomery form is to avoid heavy division modulo $p$. To compute a representative of element $$c = a⋅b\ mod\ p$$ we compute $$c⋅R = (a⋅R)⋅(b⋅R) / R\ mod\ p,$$ but we use an efficient division trick to avoid the naive modular division. Let's look into the standard 4⋅64 case: 1. First, we compute the value $$c_r=c⋅R⋅R = aR⋅bR$$ in integers and get a value with 8 64-bit limbs -2. Then we take the lowest limb of \f$c_r\f$ (i.e., \f$c_r[0]\f$) and multiply it by a special _precomputed_ value $$r_{inv} = -1 ⋅ p^{-1}\ mod\ 2^{64}$$ As a result we get $$k = r_{inv}⋅ c_r[0]\ mod\ 2^{64}$$ -3. Next we update \f$c_r\f$ in integers by adding \f$k⋅p\f$: $$c_r += k⋅p$$ You might notice that the value of \f$c_r\ mod\ p\f$ hasn't changed, since we've added a multiple of the modulus. At the same time, if we look at the expression modulo \f$2^{64}\f$: $$c_r + k⋅p = c_r + c_r⋅r_{inv}⋅p = c_r + c_r⋅ (-1)⋅p^{-1}⋅p = c_r - c_r = 0\ mod\ 2^{64}.$$ The result is equivalent modulo \f$p\f$, but we zeroed out the lowest limb -4. We perform the same operation for \f$c_r[1]\f$, but instead of adding \f$k⋅p\f$, we add \f$2^{64}⋅k⋅p\f$. In the implementation, instead of adding \f$k⋅ p\f$ to limbs of \f$c_r\f$ starting with zero, we just start with limb 1. This ensures that \f$c_r[1]=0\f$. We then perform the same operation for 2 more limbs. -5. At this stage the array \f$c_r\f$ has the property that the first 4 limbs of the total 8 limbs are zero. So if we treat the 4 high limbs as a separate integer \f$c_{r.high}\f$, $$c_r = c_{r.high}⋅2^{256}=c_{r.high}⋅R\ mod\ p \Rightarrow c_{r.high} = c\cdot R\ mod\ p$$ and we can get the evaluation simply by taking the 4 high limbs of \f$c_r\f$. -6. The previous step has reduced the intermediate value of \f$cR\f$ to range \f$[0,2p)\f$, so we must check if it is more than \f$p\f$ and subtract the modulus once if it overflows. +2. Then we take the lowest limb of $c_r$ (i.e., $c_r[0]$) and multiply it by a special _precomputed_ value $$r_{inv} = -1 ⋅ p^{-1}\ mod\ 2^{64}$$ As a result we get $$k = r_{inv}⋅ c_r[0]\ mod\ 2^{64}$$ +3. Next we update $c_r$ in integers by adding $k⋅p$: $$c_r += k⋅p$$ You might notice that the value of $c_r\ mod\ p$ hasn't changed, since we've added a multiple of the modulus. At the same time, if we look at the expression modulo $2^{64}$: $$c_r + k⋅p = c_r + c_r⋅r_{inv}⋅p = c_r + c_r⋅ (-1)⋅p^{-1}⋅p = c_r - c_r = 0\ mod\ 2^{64}.$$ The result is equivalent modulo $p$, but we zeroed out the lowest limb +4. We perform the same operation for $c_r[1]$, but instead of adding $k⋅p$, we add $2^{64}⋅k⋅p$. In the implementation, instead of adding $k⋅ p$ to limbs of $c_r$ starting with zero, we just start with limb 1. This ensures that $c_r[1]=0$. We then perform the same operation for 2 more limbs. +5. At this stage the array $c_r$ has the property that the first 4 limbs of the total 8 limbs are zero. So if we treat the 4 high limbs as a separate integer $c_{r.high}$, $$c_r = c_{r.high}⋅2^{256}=c_{r.high}⋅R\ mod\ p \Rightarrow c_{r.high} = c\cdot R\ mod\ p$$ and we can get the evaluation simply by taking the 4 high limbs of $c_r$. +6. The previous step has reduced the intermediate value of $cR$ to range $[0,2p)$, so we must check if it is more than $p$ and subtract the modulus once if it overflows. -On a high level, what we are doing is iteratively adding a multiple of \f$p\f$ until the current bottom limb is zero, then shifting by a limb (amounting to dividing by \f$2^64\f$). +On a high level, what we are doing is iteratively adding a multiple of $p$ until the current bottom limb is zero, then shifting by a limb (amounting to dividing by $2^{64}$). #### Bounds analysis Why does this work? We present several versions of the analysis, for completeness. -* Suppose both \f$aR\f$ and \f$bR\f$ are less than the modulus \f$p\f$ in integers, so $$aR\cdot bR <= (p-1)^2.$$ During each of the \f$k\cdot p\f$ addition rounds we can add at most \f$(2^{64}-1)p\f$ to the corresponding digits, so at most we add \f$(2^{256}-1)p\f$ and the total is $$aR\cdot bR + k_{0,1,2,3}p \le (p-1)^2+(2^{256}-1)p < 2\cdot 2^{256}p \Rightarrow c_{r.high} = \frac{aR\cdot bR + k_{0,1,2,3}p}{2^{256}} < 2p.$$ +* Suppose both $aR$ and $bR$ are less than the modulus $p$ in integers, so $$aR\cdot bR <= (p-1)^2.$$ During each of the $k\cdot p$ addition rounds we can add at most $(2^{64}-1)p$ to the corresponding digits, so at most we add $(2^{256}-1)p$ and the total is $$aR\cdot bR + k_{0,1,2,3}p \le (p-1)^2+(2^{256}-1)p < 2\cdot 2^{256}p \Rightarrow c_{r.high} = \frac{aR\cdot bR + k_{0,1,2,3}p}{2^{256}} < 2p.$$ -* For our 256-bit fields, we _cannot_ assume that \f$aR\f$ and \f$bR\f$ are less than the modulus \f$p\f$; we simply know that they are 256-bit numbers. Nonetheless, the same analysis shows that the output is less than \f$2^{256} + p -1\f$. This means that (conditionally) subtracting one copy of \f$p\f$ is enough to get us to the valid range of \f$[0, 2^{256})\f$. +* For our 256-bit fields, we _cannot_ assume that $aR$ and $bR$ are less than the modulus $p$; we simply know that they are 256-bit numbers. Nonetheless, the same analysis shows that the output is less than $2^{256} + p -1$. This means that (conditionally) subtracting one copy of $p$ is enough to get us to the valid range of $[0, 2^{256})$. -* For 254-bit fields (e.g. the BN-254 base and scalar fields) we can do even better by employing a simple trick. Note that 4 64-bit limbs allow 256 bits of storage. We relax the internal representation to use values in range \f$[0,2p)\f$. The addition, negation and subtraction operation logic doesn't change, we simply replace the modulus \f$p\f$ with \f$2p\f$, but the multiplication becomes more efficient. The multiplicands are in range \f$[0,2p)\f$, but we add multiples of modulus \f$p\f$ to reduce limbs, not \f$2p\f$. If we revisit the \f$c_r\f$ formula: +* For 254-bit fields (e.g. the BN-254 base and scalar fields) we can do even better by employing a simple trick. Note that 4 64-bit limbs allow 256 bits of storage. We relax the internal representation to use values in range $[0,2p)$. The addition, negation and subtraction operation logic doesn't change, we simply replace the modulus $p$ with $2p$, but the multiplication becomes more efficient. The multiplicands are in range $[0,2p)$, but we add multiples of modulus $p$ to reduce limbs, not $2p$. If we revisit the $c_r$ formula: $$aR\cdot bR + k_{0,1,2,3}p \le (2p-1)^2+(2^{256}-1)p = 2^{256}p+4p^2-5p+1 \Rightarrow$$ $$\Rightarrow c_{r.high} = \frac{aR\cdot bR + k_{0,1,2,3}p}{2^{256}} \le \frac{2^{256}p+4p^2-5p+1}{2^{256}}=p +\frac{4p^2 - 5p +1}{2^{256}}, 4p < 2^{256} \Rightarrow$$ $$\Rightarrow p +\frac{4p^2 - 5p +1}{2^{256}} < 2p$$ So we ended in the same range and we don't have to perform additional reductions. -**N.B.** In the code we refer to this form, when the limbs are only constrained to be in the range \f$[0,2p)\f$, as the coarse-representation. +**N.B.** In the code we refer to this form, when the limbs are only constrained to be in the range $[0,2p)$, as the coarse-representation. ### Yuval reduction For our 254-bit multiplication in WASM, we use a reduction technique found by Yuval. For a reference, please see this [hackmd](https://hackmd.io/@Ingonyama/Barret-Montgomery). -Recall that in standard Montgomery reduction, we zero out the lowest limb by adding a carefully chosen multiple of the modulus \f$p\f$. In particular, if we were to use standard Montgomery reduction given our limb-decomposition for WASM: given an accumulator \f$x = \sum_{i=0}^{n} \text{result}_i \cdot 2^{29i}\f$, we compute \f$k = \text{result}_0 \cdot (-p^{-1}) \mod 2^{29}\f$ and add \f$k \cdot p\f$ to \f$x\f$. This makes the lowest 29 bits zero (since \f$\text{result}_0 + k \cdot p_0 \equiv 0 \mod 2^{29}\f$), allowing us to "shift right" by discarding the zeroed limb. +Recall that in standard Montgomery reduction, we zero out the lowest limb by adding a carefully chosen multiple of the modulus $p$. In particular, if we were to use standard Montgomery reduction given our limb-decomposition for WASM: given an accumulator $x = \sum_{i=0}^{n} \text{result}_i \cdot 2^{29i}$, we compute $k = \text{result}_0 \cdot (-p^{-1}) \mod 2^{29}$ and add $k \cdot p$ to $x$. This makes the lowest 29 bits zero (since $\text{result}_0 + k \cdot p_0 \equiv 0 \mod 2^{29}$), allowing us to "shift right" by discarding the zeroed limb. -Yuval's method takes a different approach. Instead of adding a multiple of \f$p\f$ to zero out the low bits, we directly compute the equivalent value after the divide by \f$2^{29}\f$ step. Given the same accumulator \f$x\f$, we want to find \f$x / 2^{29} \mod p\f$. We can rewrite this as: +Yuval's method takes a different approach. Instead of adding a multiple of $p$ to zero out the low bits, we directly compute the equivalent value after the divide by $2^{29}$ step. Given the same accumulator $x$, we want to find $x / 2^{29} \mod p$. We can rewrite this as: $$x / 2^{29} = (x - \text{result}_0) / 2^{29} + \text{result}_0 / 2^{29} \mod p.$$ -The first term \f$(x - \text{result}_0) / 2^{29}\f$ is simply the higher limbs shifted down. The second term requires computing \f$\text{result}_0 \cdot 2^{-29} \mod p\f$, which we precompute as `r_inv_wasm` (stored in 9 limbs). +The first term $(x - \text{result}_0) / 2^{29}$ is simply the higher limbs shifted down. The second term requires computing $\text{result}_0 \cdot 2^{-29} \mod p$, which we precompute as `r_inv_wasm` (stored in 9 limbs). -So instead of computing \f$k = \text{result}_0 \cdot (-p^{-1})\f$ and adding \f$k \cdot p\f$ (9 multiply-accumulates), we compute \f$\text{result}_0 \cdot r\_inv\_wasm\f$ and add it to the higher limbs (also 9 multiply-accumulates). The key insight is that both approaches require the same number of operations, but Yuval's method avoids the need for a separate "zero out and shift" step—the shift is implicit in how we interpret the result. +So instead of computing $k = \text{result}_0 \cdot (-p^{-1})$ and adding $k \cdot p$ (9 multiply-accumulates), we compute $\text{result}_0 \cdot r\_inv\_wasm$ and add it to the higher limbs (also 9 multiply-accumulates). The key insight is that both approaches require the same number of operations, but Yuval's method avoids the need for a separate "zero out and shift" step—the shift is implicit in how we interpret the result. In code, `wasm_reduce_yuval` implements this as: ```cpp @@ -52,41 +52,41 @@ The term `(result_0 >> 29)` handles any overflow bits in `result_0` beyond the l #### Structure of WASM Montgomery multiplication -In 254-bit WASM multiplication, the full Montgomery reduction requires 9 limb-reductions (to divide by \f$2^{261} = 2^{29 \cdot 9}\f$). **We apply Yuval's method for the first 8 reductions, and standard Montgomery reduction for the 9th (final) reduction.** +In 254-bit WASM multiplication, the full Montgomery reduction requires 9 limb-reductions (to divide by $2^{261} = 2^{29 \cdot 9}$). **We apply Yuval's method for the first 8 reductions, and standard Montgomery reduction for the 9th (final) reduction.** Why not use Yuval for all 9? The key issue is that Yuval's method takes a 10-limb input and produces a 10-limb output (the reduced value spans 9 limbs, shifted up by one position). If we used Yuval for the 9th reduction, we would end up with a 10-limb result instead of the desired 9-limb result. The standard Montgomery reduction (`wasm_reduce`), by contrast, takes 9 limbs and produces 9 limbs (with the lowest limb zeroed and discardable), giving us exactly the 9-limb output we need. #### Bounds analysis -We must verify that the output is in \f$[0, 2p)\f$ (the coarse representation) without requiring an additional subtraction of \f$p\f$. +We must verify that the output is in $[0, 2p)$ (the coarse representation) without requiring an additional subtraction of $p$. -After the 9 multiply-adds, we have \f$aR \cdot bR\f$ stored across 17 limbs. Since both \f$aR\f$ and \f$bR\f$ are in \f$[0, 2p)\f$, this product is at most \f$4p^2\f$. +After the 9 multiply-adds, we have $aR \cdot bR$ stored across 17 limbs. Since both $aR$ and $bR$ are in $[0, 2p)$, this product is at most $4p^2$. After 8 Yuval reductions and 1 standard reduction, we have computed: $$\frac{aR \cdot bR + k_0 \cdot r_{inv} + k_1 \cdot r_{inv} + \cdots + k_7 \cdot r_{inv} + k_8 \cdot p}{2^{261}}$$ -where each \f$k_i\f$ is the masked low 29 bits at reduction step \f$i\f$. By construction: -- \f$k_0 < 2^{29}\f$ -- \f$k_1 < 2^{58}\f$ (since it includes carries from the previous step) -- For $i < 8$, we have \f$k_i < 2^{29(i+1)}\f$ -- The sum \f$\sum_{i=0}^{7} k_i < 2^{232}\f$ (geometric series) -- \f$k_8 < 2^{261} - 2^{232}\f$ +where each $k_i$ is the masked low 29 bits at reduction step $i$. By construction: +- $k_0 < 2^{29}$ +- $k_1 < 2^{58}$ (since it includes carries from the previous step) +- For $i < 8$, we have $k_i < 2^{29(i+1)}$ +- The sum $\sum_{i=0}^{7} k_i < 2^{232}$ (geometric series) +- $k_8 < 2^{261} - 2^{232}$ -Since \f$r_{inv} = 2^{-29} \mod p < p\f$, the total added via Yuval reductions is bounded by \f$(2^{232} - 1) \cdot p\f$. The final standard reduction adds at most \f$(2^{261} - 2^{232}) \cdot p\f$. +Since $r_{inv} = 2^{-29} \mod p < p$, the total added via Yuval reductions is bounded by $(2^{232} - 1) \cdot p$. The final standard reduction adds at most $(2^{261} - 2^{232}) \cdot p$. Therefore, the numerator is bounded by: $$4p^2 + (2^{232} - 1) \cdot p + (2^{261} - 2^{232}) \cdot p < 4p^2 + 2^{261} \cdot p$$ -Dividing by \f$2^{261}\f$: +Dividing by $2^{261}$: $$\frac{4p^2 + 2^{261} \cdot p}{2^{261}} = p + \frac{4p^2}{2^{261}}$$ -For 254-bit primes, \f$p < 2^{254}\f$, so \f$4p^2 < 4 \cdot 2^{508} = 2^{510}\f$, and: +For 254-bit primes, $p < 2^{254}$, so $4p^2 < 4 \cdot 2^{508} = 2^{510}$, and: $$\frac{4p^2}{2^{261}} < 1 $$ -Thus the result is less than \f$p + 1\f$, which is of course in the coarse representation range \f$[0, 2p)\f$. No additional reduction is required. +Thus the result is less than $p + 1$, which is of course in the coarse representation range $[0, 2p)$. No additional reduction is required. ### Converting to and from Montgomery form -Obviously we want to avoid using standard form division when converting between forms, so we use Montgomery form to convert to Montgomery form. If we look at a value \f$a\ mod\ p\f$ we can notice that this is the Montgomery form of \f$a\cdot R^{-1}\ mod\ p\f$, so if we want to get \f$aR\f$ from it, we need to multiply it by the Montgomery form of \f$R\ mod\ p\f$, which is \f$R\cdot R\ mod\ p\f$. So using Montgomery multiplication we compute +Obviously we want to avoid using standard form division when converting between forms, so we use Montgomery form to convert to Montgomery form. If we look at a value $a\ mod\ p$ we can notice that this is the Montgomery form of $a\cdot R^{-1}\ mod\ p$, so if we want to get $aR$ from it, we need to multiply it by the Montgomery form of $R\ mod\ p$, which is $R\cdot R\ mod\ p$. So using Montgomery multiplication we compute $$a \cdot R^2 / R = a\cdot R\ mod\ p$$ @@ -131,11 +131,11 @@ Most of the time field is used with uint64_t or uint256_t in our codebase, but t Conversion from field elements exists only to unsigned integers and bools. The value is converted from montgomery and appropriate number of lowest bits is used to initialize the value. -**N.B.** Functions for converting from uint256_t and back are not bijective, since values \f$ \ge p\f$ will be reduced. +**N.B.** Functions for converting from uint256_t and back are not bijective, since values $ \ge p$ will be reduced. ## Field parameters -The field template is instantiated with field parameter classes, for example, class bb::Bn254FqParams. Each such class contains at least the modulus (in 64-bit and 29-bit form), r_inv (used to efficient reductions) and 2 versions of r_squared used for converting to Montgomery form (64-bit and WASM/29-bit version). r_squared and other parameters (such as cube_root, primitive_root and coset_generators) are defined for wasm separately, because the values represent an element already in Montgomery form. +The field template is instantiated with field parameter classes, for example, class bb::Bn254FqParams. Each such class contains at least the modulus (in 64-bit and 29-bit form), r_inv (used to efficient reductions) and 2 versions of r_squared used for converting to Montgomery form (64-bit and WASM/29-bit version). r_squared and other parameters (such as cube_root, primitive_root and coset_generator) are defined for wasm separately, because the values represent an element already in Montgomery form. ## Helpful python snippets diff --git a/barretenberg/cpp/src/barretenberg/ecc/fields/field_impl.hpp b/barretenberg/cpp/src/barretenberg/ecc/fields/field_impl.hpp index 238516e480bf..640da1d26f6f 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/fields/field_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/fields/field_impl.hpp @@ -520,12 +520,12 @@ template constexpr field field::tonelli_shanks_sqrt() const noex // ----------------------------------------------------------------------------------------- // STEP 3: Set up precomputed lookup tables for the discrete log computation // ----------------------------------------------------------------------------------------- - // g = r^Q where r is a quadratic non-residue (coset_generator<0>). + // g = r^Q where r is a quadratic non-residue (coset_generator). // Since r has order (p-1) and Q is the odd part, g has order exactly 2^S. - constexpr field g = coset_generator<0>().pow(Q); + constexpr field g = coset_generator().pow(Q); // g_inv = g^{-1} = r^{-Q} = r^{p-1-Q} - constexpr field g_inv = coset_generator<0>().pow(modulus - 1 - Q); + constexpr field g_inv = coset_generator().pow(modulus - 1 - Q); // S = primitive_root_log_size() is the 2-adic valuation of (p-1), i.e., the largest power of 2 dividing (p-1). constexpr size_t root_bits = primitive_root_log_size(); diff --git a/barretenberg/cpp/src/barretenberg/ecc/fields/general_field.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/fields/general_field.test.cpp index b318cbd19676..2d1f6497d745 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/fields/general_field.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/fields/general_field.test.cpp @@ -6,6 +6,7 @@ * @note: Prime-field-specific tests are in prime_field.test.cpp */ +#include "barretenberg/common/type_traits.hpp" #include "barretenberg/ecc/curves/bn254/fq.hpp" #include "barretenberg/ecc/curves/bn254/fq12.hpp" #include "barretenberg/ecc/curves/bn254/fq2.hpp" @@ -20,6 +21,15 @@ using namespace bb; template class FieldTest : public ::testing::Test {}; +template +concept HasPow = IsAnyOf; + +template +concept HasSqrt = IsAnyOf; + +template +concept IsExtensionField = IsAnyOf; + using AllFieldTypes = ::testing:: Types; @@ -245,6 +255,39 @@ TYPED_TEST(FieldTest, DoubleInverse) EXPECT_EQ(a_inv_inv, a); } +// ================================ +// Exponentation +// ================================ + +TYPED_TEST(FieldTest, PowRegressionCheck) +{ + if constexpr (HasPow) { + using FF = TypeParam; + + FF zero = FF::zero(); + FF one = FF::one(); + + EXPECT_EQ(zero.pow(uint256_t(0)), one); + } +} + +TYPED_TEST(FieldTest, Sqrt) +{ + if constexpr (HasSqrt) { + using FF = TypeParam; + + FF input = FF::random_element(); + auto [is_sqr, root] = input.sqrt(); + FF result = root.sqr(); + + if (is_sqr) { + EXPECT_EQ(result, input); + } else { + EXPECT_EQ(result, FF::zero()); + } + } +} + // ================================ // Self-Modifying Operations // ================================ @@ -341,3 +384,194 @@ TYPED_TEST(FieldTest, SubMulConsistency) EXPECT_EQ(result, expected); } + +TYPED_TEST(FieldTest, MulSqrConsistency) +{ + using FF = TypeParam; + + // Check that (a - b) * (a + b) = a^2 - b^2 + FF a = FF::random_element(); + FF b = FF::random_element(); + FF t1; + FF t2; + FF mul_result; + FF sqr_result; + + t1 = a - b; + t2 = a + b; + mul_result = t1 * t2; + + t1 = a.sqr(); + t2 = b.sqr(); + sqr_result = t1 - t2; + + EXPECT_EQ(mul_result, sqr_result); +} + +// ================================ +// Montgomery Form and Reduction +// ================================ + +TYPED_TEST(FieldTest, FromMontgomeryForm) +{ + using FF = TypeParam; + + constexpr FF t0 = FF::one(); + constexpr FF result = t0.from_montgomery_form(); + constexpr uint256_t expected = 0x01; + uint256_t to_be_compared; + + if constexpr (!IsExtensionField) { + to_be_compared = { result.data[0], result.data[1], result.data[2], result.data[3] }; + } else if constexpr (std::is_same_v) { + EXPECT_EQ(result.c1, bb::fq::zero()); + to_be_compared = { result.c0.data[0], result.c0.data[1], result.c0.data[2], result.c0.data[3] }; + } else if constexpr (std::is_same_v) { + EXPECT_EQ(result.c0.c1, bb::fq::zero()); + EXPECT_EQ(result.c1, bb::fq2::zero()); + EXPECT_EQ(result.c2, bb::fq2::zero()); + to_be_compared = { result.c0.c0.data[0], result.c0.c0.data[1], result.c0.c0.data[2], result.c0.c0.data[3] }; + } else { + EXPECT_EQ(result.c0.c0.c1, bb::fq::zero()); + EXPECT_EQ(result.c0.c1, bb::fq2::zero()); + EXPECT_EQ(result.c0.c2, bb::fq2::zero()); + EXPECT_EQ(result.c1, bb::fq6::zero()); + to_be_compared = { + result.c0.c0.c0.data[0], result.c0.c0.c0.data[1], result.c0.c0.c0.data[2], result.c0.c0.c0.data[3] + }; + } + + EXPECT_EQ(to_be_compared, expected); +} + +TYPED_TEST(FieldTest, MontgomeryConsistencyCheck) +{ + using FF = TypeParam; + + FF a = FF::random_element(); + FF b = FF::random_element(); + FF aR; + FF bR; + FF aRR; + FF bRR; + FF bRRR; + FF result_a; + FF result_b; + FF result_c; + FF result_d; + + aR = a.to_montgomery_form(); + aRR = aR.to_montgomery_form(); + bR = b.to_montgomery_form(); + bRR = bR.to_montgomery_form(); + bRRR = bRR.to_montgomery_form(); + result_a = aRR * bRR; // abRRR + result_b = aR * bRRR; // abRRR + result_c = aR * bR; // abR + result_d = a * b; // abR^-1 + + EXPECT_EQ((result_a == result_b), true); + + if constexpr (!IsExtensionField || std::is_same_v) { + result_a.self_from_montgomery_form(); // abRR + result_a.self_from_montgomery_form(); // abR + result_a.self_from_montgomery_form(); // ab + result_c.self_from_montgomery_form(); // ab + result_d.self_to_montgomery_form(); // ab + + EXPECT_EQ((result_a == result_c), true); + EXPECT_EQ((result_a == result_d), true); + } +} + +// ================================ +// Other tests +// ================================ + +TYPED_TEST(FieldTest, Copy) +{ + if constexpr (!IsExtensionField) { + using FF = TypeParam; + + FF result = FF::random_element(); + FF expected; + FF::__copy(result, expected); + + EXPECT_EQ((result == expected), true); + } +} + +TYPED_TEST(FieldTest, SerializeToBuffer) +{ + if constexpr (!IsExtensionField) { + using FF = TypeParam; + + std::array buffer; + FF a{ 0x1234567876543210, 0x2345678987654321, 0x3456789a98765432, 0x006789abcba98765 }; + a = a.to_montgomery_form(); + + FF::serialize_to_buffer(a, &buffer[0]); + + EXPECT_EQ(buffer[31], 0x10); + EXPECT_EQ(buffer[30], 0x32); + EXPECT_EQ(buffer[29], 0x54); + EXPECT_EQ(buffer[28], 0x76); + EXPECT_EQ(buffer[27], 0x78); + EXPECT_EQ(buffer[26], 0x56); + EXPECT_EQ(buffer[25], 0x34); + EXPECT_EQ(buffer[24], 0x12); + + EXPECT_EQ(buffer[23], 0x21); + EXPECT_EQ(buffer[22], 0x43); + EXPECT_EQ(buffer[21], 0x65); + EXPECT_EQ(buffer[20], 0x87); + EXPECT_EQ(buffer[19], 0x89); + EXPECT_EQ(buffer[18], 0x67); + EXPECT_EQ(buffer[17], 0x45); + EXPECT_EQ(buffer[16], 0x23); + + EXPECT_EQ(buffer[15], 0x32); + EXPECT_EQ(buffer[14], 0x54); + EXPECT_EQ(buffer[13], 0x76); + EXPECT_EQ(buffer[12], 0x98); + EXPECT_EQ(buffer[11], 0x9a); + EXPECT_EQ(buffer[10], 0x78); + EXPECT_EQ(buffer[9], 0x56); + EXPECT_EQ(buffer[8], 0x34); + + EXPECT_EQ(buffer[7], 0x65); + EXPECT_EQ(buffer[6], 0x87); + EXPECT_EQ(buffer[5], 0xa9); + EXPECT_EQ(buffer[4], 0xcb); + EXPECT_EQ(buffer[3], 0xab); + EXPECT_EQ(buffer[2], 0x89); + EXPECT_EQ(buffer[1], 0x67); + EXPECT_EQ(buffer[0], 0x00); + } +} + +TYPED_TEST(FieldTest, SerializeFromBuffer) +{ + if constexpr (!IsExtensionField) { + using FF = TypeParam; + + std::array buffer; + FF expected{ 0x1234567876543210, 0x2345678987654321, 0x3456789a98765432, 0x006789abcba98765 }; + + FF::serialize_to_buffer(expected, &buffer[0]); + FF result = FF::serialize_from_buffer(&buffer[0]); + + EXPECT_EQ(result, expected); + } else if constexpr (std::is_same_v) { + std::array buffer; + fq expected_c0 = { 0x1234567876543210, 0x2345678987654321, 0x3456789a98765432, 0x006789abcba98765 }; + fq expected_c1 = { 0x12a4e67f76b43210, 0x23e56f898a65cc21, 0x005678add98e5432, 0x1f6789a2cba98700 }; + fq2 expected{ expected_c0, expected_c1 }; + + fq2::serialize_to_buffer(expected, &buffer[0]); + + fq2 result = fq2::serialize_from_buffer(&buffer[0]); + + EXPECT_EQ(result, expected); + } +} diff --git a/barretenberg/cpp/src/barretenberg/ecc/fields/parameter_helper.py b/barretenberg/cpp/src/barretenberg/ecc/fields/parameter_helper.py index 7713a18d61b6..e960a71889f8 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/fields/parameter_helper.py +++ b/barretenberg/cpp/src/barretenberg/ecc/fields/parameter_helper.py @@ -2,6 +2,8 @@ # A helper script to parse the field parameters from the source code import os +import re +import json parameter_files = ['src/barretenberg/ecc/curves/bn254/fq.hpp', 'src/barretenberg/ecc/curves/bn254/fr.hpp', 'src/barretenberg/ecc/curves/secp256k1/secp256k1.hpp','src/barretenberg/ecc/curves/secp256r1/secp256r1.hpp'] @@ -10,16 +12,14 @@ def get_file_location(): """ Returns the current file's location relative to the execution folder. - + Returns: str: The path to the current file relative to the execution folder. """ return os.path.abspath(__file__) full_paths=[os.path.join(os.path.join(os.path.dirname(get_file_location()),'../../../../'), file) for file in parameter_files] -print(full_paths) -import re def parse_field_params(s): def parse_number(line): """Expects a string without whitespaces""" @@ -29,7 +29,7 @@ def parse_number(line): else: value = int(line) return value - + def recover_single_value(name): """Extract a single value from the source code by finding its name, equals sign, and semicolon""" nonlocal s @@ -38,8 +38,8 @@ def recover_single_value(name): raise ValueError("Couldn't find value with name "+name) eq_position=s[index:].find('=') line_end=s[index:].find(';') - return parse_number(s[index+eq_position+1:index+line_end]) - + return parse_number(s[index+eq_position+1:index+line_end]) + def recover_single_value_if_present(name): """Same as recover_single_value but returns None if the value is not found""" nonlocal s @@ -48,8 +48,8 @@ def recover_single_value_if_present(name): return None eq_position=s[index:].find('=') line_end=s[index:].find(';') - return parse_number(s[index+eq_position+1:index+line_end]) - + return parse_number(s[index+eq_position+1:index+line_end]) + def recover_array(name): """Extract an array of values from the source code by finding its name and contents between braces""" nonlocal s @@ -60,7 +60,7 @@ def recover_array(name): all_values=s[index+start_index+1:index+end_index] result=[parse_number(x) for (i,x) in enumerate(all_values.split(',')) if i::EvaluationDomain(const size_t domain_size, const size_t ta , generator_size(target_generator_size ? target_generator_size : domain_size) , domain(Fr{ size, 0, 0, 0 }.to_montgomery_form()) , domain_inverse(domain.invert()) - , generator(Fr::template coset_generator<0>()) - , generator_inverse(Fr::template coset_generator<0>().invert()) + , generator(Fr::coset_generator()) + , generator_inverse(Fr::coset_generator().invert()) , roots(nullptr) { // Grumpkin does not have many roots of unity and, given these are not used for Honk, we set it to one. diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp index da581bd33ed8..0c93fd4d2b99 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp @@ -36,7 +36,7 @@ template class stdlib_field : public testing::Test { field_ct c = a + b; EXPECT_TRUE(field_ct::witness_indices_match(c, a)); EXPECT_TRUE(builder.get_num_finalized_gates_inefficient() == num_gates); - field_ct d(&builder, fr::coset_generator<0>()); // like b, d is just a constant and not a wire value + field_ct d(&builder, fr::coset_generator()); // like b, d is just a constant and not a wire value // by this point, we shouldn't have added any constraints in our circuit for (size_t i = 0; i < 17; ++i) {