diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index 5ece3c031d48..736990478529 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -89,6 +89,11 @@ void build_constraints(Builder& builder, AcirFormat const& constraint_system, bo create_fixed_base_constraint(builder, constraint); } + // Add variable base scalar mul constraints + for (const auto& constraint : constraint_system.variable_base_scalar_mul_constraints) { + create_variable_base_constraint(builder, constraint); + } + // Add ec add constraints for (const auto& constraint : constraint_system.ec_add_constraints) { create_ec_add_constraint(builder, constraint, has_valid_witness_assignments); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp index ec82362e2d8b..a7f5b4757375 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp @@ -17,6 +17,7 @@ #include "recursion_constraint.hpp" #include "schnorr_verify.hpp" #include "sha256_constraint.hpp" +#include "variable_base_scalar_mul.hpp" #include namespace acir_format { @@ -48,6 +49,7 @@ struct AcirFormat { std::vector pedersen_hash_constraints; std::vector poseidon2_constraints; std::vector fixed_base_scalar_mul_constraints; + std::vector variable_base_scalar_mul_constraints; std::vector ec_add_constraints; std::vector recursion_constraints; std::vector bigint_from_le_bytes_constraints; @@ -82,6 +84,7 @@ struct AcirFormat { pedersen_hash_constraints, poseidon2_constraints, fixed_base_scalar_mul_constraints, + variable_base_scalar_mul_constraints, ec_add_constraints, recursion_constraints, poly_triple_constraints, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp index c7d44e319413..7de0f847b1f7 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp @@ -48,6 +48,7 @@ TEST_F(AcirFormatTests, TestASingleConstraintNoPubInputs) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, @@ -164,6 +165,7 @@ TEST_F(AcirFormatTests, TestLogicGateFromNoirCircuit) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, @@ -232,6 +234,7 @@ TEST_F(AcirFormatTests, TestSchnorrVerifyPass) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, @@ -327,6 +330,7 @@ TEST_F(AcirFormatTests, TestSchnorrVerifySmallRange) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, @@ -441,6 +445,7 @@ TEST_F(AcirFormatTests, TestVarKeccak) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, @@ -488,6 +493,7 @@ TEST_F(AcirFormatTests, TestKeccakPermutation) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp index 778363f3258c..f31fc73c806a 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp @@ -317,6 +317,15 @@ void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, Aci .pub_key_x = arg.outputs[0].value, .pub_key_y = arg.outputs[1].value, }); + } else if constexpr (std::is_same_v) { + af.variable_base_scalar_mul_constraints.push_back(VariableBaseScalarMul{ + .point_x = arg.point_x.witness.value, + .point_y = arg.point_y.witness.value, + .scalar_low = arg.scalar_low.witness.value, + .scalar_high = arg.scalar_high.witness.value, + .out_point_x = arg.outputs[0].value, + .out_point_y = arg.outputs[1].value, + }); } else if constexpr (std::is_same_v) { af.ec_add_constraints.push_back(EcAdd{ .input1_x = arg.input1_x.witness.value, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/bigint_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/bigint_constraint.test.cpp index 584f7ef62a56..3b32c7f26950 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/bigint_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/bigint_constraint.test.cpp @@ -185,6 +185,7 @@ TEST_F(BigIntTests, TestBigIntConstraintMultiple) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, @@ -253,6 +254,7 @@ TEST_F(BigIntTests, TestBigIntConstraintSimple) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = { from_le_bytes_constraint_bigint1 }, @@ -306,6 +308,7 @@ TEST_F(BigIntTests, TestBigIntConstraintReuse) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, @@ -363,6 +366,7 @@ TEST_F(BigIntTests, TestBigIntConstraintReuse2) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, @@ -441,6 +445,7 @@ TEST_F(BigIntTests, TestBigIntDIV) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = { from_le_bytes_constraint_bigint1, from_le_bytes_constraint_bigint2 }, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp index 20f9e8072bba..75b9150d335c 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp @@ -127,6 +127,7 @@ TEST_F(UltraPlonkRAM, TestBlockConstraint) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ec_operations.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ec_operations.test.cpp index 92c76e3d7a37..bdda21409a17 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ec_operations.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ec_operations.test.cpp @@ -67,6 +67,7 @@ TEST_F(EcOperations, TestECOperations) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = { ec_add_constraint }, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp index 0a11adb97be2..c494cc13e798 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp @@ -107,6 +107,7 @@ TEST_F(ECDSASecp256k1, TestECDSAConstraintSucceed) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, @@ -156,6 +157,7 @@ TEST_F(ECDSASecp256k1, TestECDSACompilesForVerifier) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, @@ -200,6 +202,7 @@ TEST_F(ECDSASecp256k1, TestECDSAConstraintFail) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp index 6cf542bc2d6e..6728445d2371 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp @@ -141,6 +141,7 @@ TEST(ECDSASecp256r1, test_hardcoded) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, @@ -192,6 +193,7 @@ TEST(ECDSASecp256r1, TestECDSAConstraintSucceed) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, @@ -241,6 +243,7 @@ TEST(ECDSASecp256r1, TestECDSACompilesForVerifier) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, @@ -285,6 +288,7 @@ TEST(ECDSASecp256r1, TestECDSAConstraintFail) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/poseidon2_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/poseidon2_constraint.test.cpp index f509c262782a..f672505b4d70 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/poseidon2_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/poseidon2_constraint.test.cpp @@ -47,6 +47,7 @@ TEST_F(Poseidon2Tests, TestPoseidon2Permutation) .pedersen_hash_constraints = {}, .poseidon2_constraints = { poseidon2_constraint }, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index bbf7768abc91..97e53d30c627 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -99,6 +99,7 @@ Builder create_inner_circuit() .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, @@ -256,6 +257,7 @@ Builder create_outer_circuit(std::vector& inner_circuits) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = recursion_constraints, .bigint_from_le_bytes_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp index e2c9d020d6bc..bdfb6605ad24 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp @@ -145,6 +145,18 @@ struct BlackBoxFuncCall { static FixedBaseScalarMul bincodeDeserialize(std::vector); }; + struct VariableBaseScalarMul { + Program::FunctionInput point_x; + Program::FunctionInput point_y; + Program::FunctionInput scalar_low; + Program::FunctionInput scalar_high; + std::array outputs; + + friend bool operator==(const VariableBaseScalarMul&, const VariableBaseScalarMul&); + std::vector bincodeSerialize() const; + static VariableBaseScalarMul bincodeDeserialize(std::vector); + }; + struct EmbeddedCurveAdd { Program::FunctionInput input1_x; Program::FunctionInput input1_y; @@ -278,6 +290,7 @@ struct BlackBoxFuncCall { EcdsaSecp256k1, EcdsaSecp256r1, FixedBaseScalarMul, + VariableBaseScalarMul, EmbeddedCurveAdd, Keccak256, Keccakf1600, @@ -753,6 +766,18 @@ struct BlackBoxOp { static FixedBaseScalarMul bincodeDeserialize(std::vector); }; + struct VariableBaseScalarMul { + Program::MemoryAddress point_x; + Program::MemoryAddress point_y; + Program::MemoryAddress scalar_low; + Program::MemoryAddress scalar_high; + Program::HeapArray result; + + friend bool operator==(const VariableBaseScalarMul&, const VariableBaseScalarMul&); + std::vector bincodeSerialize() const; + static VariableBaseScalarMul bincodeDeserialize(std::vector); + }; + struct EmbeddedCurveAdd { Program::MemoryAddress input1_x; Program::MemoryAddress input1_y; @@ -855,6 +880,7 @@ struct BlackBoxOp { PedersenCommitment, PedersenHash, FixedBaseScalarMul, + VariableBaseScalarMul, EmbeddedCurveAdd, BigIntAdd, BigIntSub, @@ -3068,6 +3094,75 @@ Program::BlackBoxFuncCall::FixedBaseScalarMul serde::Deserializable< namespace Program { +inline bool operator==(const BlackBoxFuncCall::VariableBaseScalarMul& lhs, + const BlackBoxFuncCall::VariableBaseScalarMul& rhs) +{ + if (!(lhs.point_x == rhs.point_x)) { + return false; + } + if (!(lhs.point_y == rhs.point_y)) { + return false; + } + if (!(lhs.scalar_low == rhs.scalar_low)) { + return false; + } + if (!(lhs.scalar_high == rhs.scalar_high)) { + return false; + } + if (!(lhs.outputs == rhs.outputs)) { + return false; + } + return true; +} + +inline std::vector BlackBoxFuncCall::VariableBaseScalarMul::bincodeSerialize() const +{ + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); +} + +inline BlackBoxFuncCall::VariableBaseScalarMul BlackBoxFuncCall::VariableBaseScalarMul::bincodeDeserialize( + std::vector input) +{ + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw_or_abort("Some input bytes were not read"); + } + return value; +} + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize( + const Program::BlackBoxFuncCall::VariableBaseScalarMul& obj, Serializer& serializer) +{ + serde::Serializable::serialize(obj.point_x, serializer); + serde::Serializable::serialize(obj.point_y, serializer); + serde::Serializable::serialize(obj.scalar_low, serializer); + serde::Serializable::serialize(obj.scalar_high, serializer); + serde::Serializable::serialize(obj.outputs, serializer); +} + +template <> +template +Program::BlackBoxFuncCall::VariableBaseScalarMul serde::Deserializable< + Program::BlackBoxFuncCall::VariableBaseScalarMul>::deserialize(Deserializer& deserializer) +{ + Program::BlackBoxFuncCall::VariableBaseScalarMul obj; + obj.point_x = serde::Deserializable::deserialize(deserializer); + obj.point_y = serde::Deserializable::deserialize(deserializer); + obj.scalar_low = serde::Deserializable::deserialize(deserializer); + obj.scalar_high = serde::Deserializable::deserialize(deserializer); + obj.outputs = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Program { + inline bool operator==(const BlackBoxFuncCall::EmbeddedCurveAdd& lhs, const BlackBoxFuncCall::EmbeddedCurveAdd& rhs) { if (!(lhs.input1_x == rhs.input1_x)) { @@ -4444,6 +4539,74 @@ Program::BlackBoxOp::FixedBaseScalarMul serde::Deserializable BlackBoxOp::VariableBaseScalarMul::bincodeSerialize() const +{ + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); +} + +inline BlackBoxOp::VariableBaseScalarMul BlackBoxOp::VariableBaseScalarMul::bincodeDeserialize( + std::vector input) +{ + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw_or_abort("Some input bytes were not read"); + } + return value; +} + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize( + const Program::BlackBoxOp::VariableBaseScalarMul& obj, Serializer& serializer) +{ + serde::Serializable::serialize(obj.point_x, serializer); + serde::Serializable::serialize(obj.point_y, serializer); + serde::Serializable::serialize(obj.scalar_low, serializer); + serde::Serializable::serialize(obj.scalar_high, serializer); + serde::Serializable::serialize(obj.result, serializer); +} + +template <> +template +Program::BlackBoxOp::VariableBaseScalarMul serde::Deserializable< + Program::BlackBoxOp::VariableBaseScalarMul>::deserialize(Deserializer& deserializer) +{ + Program::BlackBoxOp::VariableBaseScalarMul obj; + obj.point_x = serde::Deserializable::deserialize(deserializer); + obj.point_y = serde::Deserializable::deserialize(deserializer); + obj.scalar_low = serde::Deserializable::deserialize(deserializer); + obj.scalar_high = serde::Deserializable::deserialize(deserializer); + obj.result = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Program { + inline bool operator==(const BlackBoxOp::EmbeddedCurveAdd& lhs, const BlackBoxOp::EmbeddedCurveAdd& rhs) { if (!(lhs.input1_x == rhs.input1_x)) { diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/sha256_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/sha256_constraint.test.cpp index 6266253ee552..aa520806b2b7 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/sha256_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/sha256_constraint.test.cpp @@ -49,6 +49,7 @@ TEST_F(Sha256Tests, TestSha256Compression) .pedersen_hash_constraints = {}, .poseidon2_constraints = {}, .fixed_base_scalar_mul_constraints = {}, + .variable_base_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, .bigint_from_le_bytes_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/variable_base_scalar_mul.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/variable_base_scalar_mul.cpp new file mode 100644 index 000000000000..6446b68158c4 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/variable_base_scalar_mul.cpp @@ -0,0 +1,38 @@ +#include "variable_base_scalar_mul.hpp" +#include "barretenberg/dsl/types.hpp" +#include "barretenberg/ecc/curves/bn254/fr.hpp" +#include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp" +#include "barretenberg/plonk_honk_shared/arithmetization/gate_data.hpp" + +namespace acir_format { + +template void create_variable_base_constraint(Builder& builder, const VariableBaseScalarMul& input) +{ + using cycle_group_ct = bb::stdlib::cycle_group; + using cycle_scalar_ct = typename bb::stdlib::cycle_group::cycle_scalar; + using field_ct = bb::stdlib::field_t; + + // We instantiate the input point/variable base as `cycle_group_ct` + auto point_x = field_ct::from_witness_index(&builder, input.point_x); + auto point_y = field_ct::from_witness_index(&builder, input.point_y); + cycle_group_ct input_point(point_x, point_y, false); + + // We reconstruct the scalar from the low and high limbs + field_ct scalar_low_as_field = field_ct::from_witness_index(&builder, input.scalar_low); + field_ct scalar_high_as_field = field_ct::from_witness_index(&builder, input.scalar_high); + cycle_scalar_ct scalar(scalar_low_as_field, scalar_high_as_field); + + // We multiply the scalar with input point/variable base to get the result + auto result = input_point * scalar; + + // Finally we add the constraints + builder.assert_equal(result.x.get_witness_index(), input.out_point_x); + builder.assert_equal(result.y.get_witness_index(), input.out_point_y); +} + +template void create_variable_base_constraint(UltraCircuitBuilder& builder, + const VariableBaseScalarMul& input); +template void create_variable_base_constraint(GoblinUltraCircuitBuilder& builder, + const VariableBaseScalarMul& input); + +} // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/variable_base_scalar_mul.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/variable_base_scalar_mul.hpp new file mode 100644 index 000000000000..d903df2cb322 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/variable_base_scalar_mul.hpp @@ -0,0 +1,23 @@ +#pragma once +#include "barretenberg/dsl/types.hpp" +#include "barretenberg/serialize/msgpack.hpp" +#include + +namespace acir_format { + +struct VariableBaseScalarMul { + uint32_t point_x; + uint32_t point_y; + uint32_t scalar_low; + uint32_t scalar_high; + uint32_t out_point_x; + uint32_t out_point_y; + + // for serialization, update with any new fields + MSGPACK_FIELDS(point_x, point_y, scalar_low, scalar_high, out_point_x, out_point_y); + friend bool operator==(VariableBaseScalarMul const& lhs, VariableBaseScalarMul const& rhs) = default; +}; + +template void create_variable_base_constraint(Builder& builder, const VariableBaseScalarMul& input); + +} // namespace acir_format diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/grumpkin_point.nr b/noir-projects/noir-protocol-circuits/crates/types/src/grumpkin_point.nr index 965654ac144e..ec334d78b50f 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/grumpkin_point.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/grumpkin_point.nr @@ -3,6 +3,7 @@ use dep::std::cmp::Eq; global GRUMPKIN_POINT_SERIALIZED_LEN: Field = 2; +// TODO(https://github.com/noir-lang/noir/issues/4931) struct GrumpkinPoint { x: Field, y: Field, diff --git a/noir/noir-repo/acvm-repo/acir/README.md b/noir/noir-repo/acvm-repo/acir/README.md index 801aeac1140a..e72f7ea178d8 100644 --- a/noir/noir-repo/acvm-repo/acir/README.md +++ b/noir/noir-repo/acvm-repo/acir/README.md @@ -146,6 +146,15 @@ Inputs and outputs are similar to SchnorrVerify, except that because we use a di Because the Grumpkin scalar field is bigger than the ACIR field, we provide 2 ACIR fields representing the low and high parts of the Grumpkin scalar $a$: $a=low+high*2^{128},$ with $low, high < 2^{128}$ +**VariableBaseScalarMul**: scalar multiplication with a variable base/input point (P) of the embedded curve +- input: + point_x, point_y representing x and y coordinates of input point P + scalar_low, scalar_high are 2 (field , 254), representing the low and high part of the input scalar. For Barretenberg, they must both be less than 128 bits. +- output: x and y coordinates of $low*P+high*2^{128}*P$, where P is the input point P + +Because the Grumpkin scalar field is bigger than the ACIR field, we provide 2 ACIR fields representing the low and high parts of the Grumpkin scalar $a$: +$a=low+high*2^{128},$ with $low, high < 2^{128}$ + **Keccak256**: Computes the Keccak-256 (Ethereum version) of the inputs. - inputs: Vector of bytes (FieldElement, 8) - outputs: Vector of 32 bytes (FieldElement, 8) diff --git a/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp b/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp index 0ad193fedf65..1e5207c01cbb 100644 --- a/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp +++ b/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp @@ -145,6 +145,18 @@ namespace Program { static FixedBaseScalarMul bincodeDeserialize(std::vector); }; + struct VariableBaseScalarMul { + Program::FunctionInput point_x; + Program::FunctionInput point_y; + Program::FunctionInput scalar_low; + Program::FunctionInput scalar_high; + std::array outputs; + + friend bool operator==(const VariableBaseScalarMul&, const VariableBaseScalarMul&); + std::vector bincodeSerialize() const; + static VariableBaseScalarMul bincodeDeserialize(std::vector); + }; + struct EmbeddedCurveAdd { Program::FunctionInput input1_x; Program::FunctionInput input1_y; @@ -266,7 +278,7 @@ namespace Program { static Sha256Compression bincodeDeserialize(std::vector); }; - std::variant value; + std::variant value; friend bool operator==(const BlackBoxFuncCall&, const BlackBoxFuncCall&); std::vector bincodeSerialize() const; @@ -729,6 +741,18 @@ namespace Program { static FixedBaseScalarMul bincodeDeserialize(std::vector); }; + struct VariableBaseScalarMul { + Program::MemoryAddress point_x; + Program::MemoryAddress point_y; + Program::MemoryAddress scalar_low; + Program::MemoryAddress scalar_high; + Program::HeapArray result; + + friend bool operator==(const VariableBaseScalarMul&, const VariableBaseScalarMul&); + std::vector bincodeSerialize() const; + static VariableBaseScalarMul bincodeDeserialize(std::vector); + }; + struct EmbeddedCurveAdd { Program::MemoryAddress input1_x; Program::MemoryAddress input1_y; @@ -820,7 +844,7 @@ namespace Program { static Sha256Compression bincodeDeserialize(std::vector); }; - std::variant value; + std::variant value; friend bool operator==(const BlackBoxOp&, const BlackBoxOp&); std::vector bincodeSerialize() const; @@ -2690,6 +2714,56 @@ Program::BlackBoxFuncCall::FixedBaseScalarMul serde::Deserializable BlackBoxFuncCall::VariableBaseScalarMul::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxFuncCall::VariableBaseScalarMul BlackBoxFuncCall::VariableBaseScalarMul::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize(const Program::BlackBoxFuncCall::VariableBaseScalarMul &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.point_x, serializer); + serde::Serializable::serialize(obj.point_y, serializer); + serde::Serializable::serialize(obj.scalar_low, serializer); + serde::Serializable::serialize(obj.scalar_high, serializer); + serde::Serializable::serialize(obj.outputs, serializer); +} + +template <> +template +Program::BlackBoxFuncCall::VariableBaseScalarMul serde::Deserializable::deserialize(Deserializer &deserializer) { + Program::BlackBoxFuncCall::VariableBaseScalarMul obj; + obj.point_x = serde::Deserializable::deserialize(deserializer); + obj.point_y = serde::Deserializable::deserialize(deserializer); + obj.scalar_low = serde::Deserializable::deserialize(deserializer); + obj.scalar_high = serde::Deserializable::deserialize(deserializer); + obj.outputs = serde::Deserializable::deserialize(deserializer); + return obj; +} + namespace Program { inline bool operator==(const BlackBoxFuncCall::EmbeddedCurveAdd &lhs, const BlackBoxFuncCall::EmbeddedCurveAdd &rhs) { @@ -3750,6 +3824,56 @@ Program::BlackBoxOp::FixedBaseScalarMul serde::Deserializable BlackBoxOp::VariableBaseScalarMul::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxOp::VariableBaseScalarMul BlackBoxOp::VariableBaseScalarMul::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize(const Program::BlackBoxOp::VariableBaseScalarMul &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.point_x, serializer); + serde::Serializable::serialize(obj.point_y, serializer); + serde::Serializable::serialize(obj.scalar_low, serializer); + serde::Serializable::serialize(obj.scalar_high, serializer); + serde::Serializable::serialize(obj.result, serializer); +} + +template <> +template +Program::BlackBoxOp::VariableBaseScalarMul serde::Deserializable::deserialize(Deserializer &deserializer) { + Program::BlackBoxOp::VariableBaseScalarMul obj; + obj.point_x = serde::Deserializable::deserialize(deserializer); + obj.point_y = serde::Deserializable::deserialize(deserializer); + obj.scalar_low = serde::Deserializable::deserialize(deserializer); + obj.scalar_high = serde::Deserializable::deserialize(deserializer); + obj.result = serde::Deserializable::deserialize(deserializer); + return obj; +} + namespace Program { inline bool operator==(const BlackBoxOp::EmbeddedCurveAdd &lhs, const BlackBoxOp::EmbeddedCurveAdd &rhs) { diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/black_box_functions.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/black_box_functions.rs index 0a7ee244a5ee..9a43702a408c 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/black_box_functions.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/black_box_functions.rs @@ -36,8 +36,10 @@ pub enum BlackBoxFunc { EcdsaSecp256k1, /// Verifies a ECDSA signature over the secp256r1 curve. EcdsaSecp256r1, - /// Performs scalar multiplication over the embedded curve on which [`FieldElement`][acir_field::FieldElement] is defined. + /// Performs scalar multiplication over the embedded curve on which [`FieldElement`][acir_field::FieldElement] is defined and a fixed base/generator point G1. FixedBaseScalarMul, + /// Performs scalar multiplication over the embedded curve on which [`FieldElement`][acir_field::FieldElement] is defined and a variable base/input point P. + VariableBaseScalarMul, /// Calculates the Keccak256 hash of the inputs. Keccak256, /// Keccak Permutation function of 1600 width @@ -82,6 +84,7 @@ impl BlackBoxFunc { BlackBoxFunc::PedersenHash => "pedersen_hash", BlackBoxFunc::EcdsaSecp256k1 => "ecdsa_secp256k1", BlackBoxFunc::FixedBaseScalarMul => "fixed_base_scalar_mul", + BlackBoxFunc::VariableBaseScalarMul => "variable_base_scalar_mul", BlackBoxFunc::EmbeddedCurveAdd => "embedded_curve_add", BlackBoxFunc::AND => "and", BlackBoxFunc::XOR => "xor", @@ -112,6 +115,7 @@ impl BlackBoxFunc { "ecdsa_secp256k1" => Some(BlackBoxFunc::EcdsaSecp256k1), "ecdsa_secp256r1" => Some(BlackBoxFunc::EcdsaSecp256r1), "fixed_base_scalar_mul" => Some(BlackBoxFunc::FixedBaseScalarMul), + "variable_base_scalar_mul" => Some(BlackBoxFunc::VariableBaseScalarMul), "embedded_curve_add" => Some(BlackBoxFunc::EmbeddedCurveAdd), "and" => Some(BlackBoxFunc::AND), "xor" => Some(BlackBoxFunc::XOR), diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs index 405cd0cef007..5715019937c2 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs @@ -85,6 +85,13 @@ pub enum BlackBoxFuncCall { high: FunctionInput, outputs: (Witness, Witness), }, + VariableBaseScalarMul { + point_x: FunctionInput, + point_y: FunctionInput, + scalar_low: FunctionInput, + scalar_high: FunctionInput, + outputs: (Witness, Witness), + }, EmbeddedCurveAdd { input1_x: FunctionInput, input1_y: FunctionInput, @@ -189,6 +196,7 @@ impl BlackBoxFuncCall { BlackBoxFuncCall::EcdsaSecp256k1 { .. } => BlackBoxFunc::EcdsaSecp256k1, BlackBoxFuncCall::EcdsaSecp256r1 { .. } => BlackBoxFunc::EcdsaSecp256r1, BlackBoxFuncCall::FixedBaseScalarMul { .. } => BlackBoxFunc::FixedBaseScalarMul, + BlackBoxFuncCall::VariableBaseScalarMul { .. } => BlackBoxFunc::VariableBaseScalarMul, BlackBoxFuncCall::EmbeddedCurveAdd { .. } => BlackBoxFunc::EmbeddedCurveAdd, BlackBoxFuncCall::Keccak256 { .. } => BlackBoxFunc::Keccak256, BlackBoxFuncCall::Keccakf1600 { .. } => BlackBoxFunc::Keccakf1600, @@ -232,6 +240,15 @@ impl BlackBoxFuncCall { | BlackBoxFuncCall::BigIntDiv { .. } | BlackBoxFuncCall::BigIntToLeBytes { .. } => Vec::new(), BlackBoxFuncCall::FixedBaseScalarMul { low, high, .. } => vec![*low, *high], + BlackBoxFuncCall::VariableBaseScalarMul { + point_x, + point_y, + scalar_low, + scalar_high, + .. + } => { + vec![*point_x, *point_y, *scalar_low, *scalar_high] + } BlackBoxFuncCall::EmbeddedCurveAdd { input1_x, input1_y, input2_x, input2_y, .. } => vec![*input1_x, *input1_y, *input2_x, *input2_y], @@ -329,6 +346,7 @@ impl BlackBoxFuncCall { | BlackBoxFuncCall::PedersenHash { output, .. } | BlackBoxFuncCall::EcdsaSecp256r1 { output, .. } => vec![*output], BlackBoxFuncCall::FixedBaseScalarMul { outputs, .. } + | BlackBoxFuncCall::VariableBaseScalarMul { outputs, .. } | BlackBoxFuncCall::PedersenCommitment { outputs, .. } | BlackBoxFuncCall::EmbeddedCurveAdd { outputs, .. } => vec![outputs.0, outputs.1], BlackBoxFuncCall::RANGE { .. } diff --git a/noir/noir-repo/acvm-repo/acir/tests/test_program_serialization.rs b/noir/noir-repo/acvm-repo/acir/tests/test_program_serialization.rs index c5912b61cf15..2ad082410a1b 100644 --- a/noir/noir-repo/acvm-repo/acir/tests/test_program_serialization.rs +++ b/noir/noir-repo/acvm-repo/acir/tests/test_program_serialization.rs @@ -85,6 +85,38 @@ fn fixed_base_scalar_mul_circuit() { assert_eq!(bytes, expected_serialization) } +#[test] +fn variable_base_scalar_mul_circuit() { + let variable_base_scalar_mul = + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::VariableBaseScalarMul { + point_x: FunctionInput { witness: Witness(1), num_bits: 128 }, + point_y: FunctionInput { witness: Witness(2), num_bits: 128 }, + scalar_low: FunctionInput { witness: Witness(3), num_bits: 128 }, + scalar_high: FunctionInput { witness: Witness(4), num_bits: 128 }, + outputs: (Witness(5), Witness(6)), + }); + + let circuit = Circuit { + current_witness_index: 7, + opcodes: vec![variable_base_scalar_mul], + private_parameters: BTreeSet::from([Witness(1), Witness(2), Witness(3), Witness(4)]), + return_values: PublicInputs(BTreeSet::from_iter(vec![Witness(5), Witness(6)])), + ..Circuit::default() + }; + let program = Program { functions: vec![circuit], unconstrained_functions: vec![] }; + + let bytes = Program::serialize_program(&program); + + let expected_serialization: Vec = vec![ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 93, 139, 65, 10, 0, 32, 8, 4, 213, 172, 46, 61, 186, + 167, 103, 52, 65, 185, 176, 140, 44, 142, 202, 73, 143, 42, 247, 230, 128, 51, 106, 176, + 64, 135, 53, 218, 112, 252, 113, 141, 223, 187, 9, 155, 36, 231, 203, 2, 176, 218, 19, 62, + 137, 0, 0, 0, + ]; + + assert_eq!(bytes, expected_serialization) +} + #[test] fn pedersen_circuit() { let pedersen = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::PedersenCommitment { diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs index c5bfd1d5646d..79e33ae8de53 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs @@ -1,3 +1,4 @@ +// TODO(https://github.com/noir-lang/noir/issues/4932): rename this file to something more generic use acir::{ circuit::opcodes::FunctionInput, native_types::{Witness, WitnessMap}, @@ -24,6 +25,29 @@ pub(super) fn fixed_base_scalar_mul( Ok(()) } +pub(super) fn variable_base_scalar_mul( + backend: &impl BlackBoxFunctionSolver, + initial_witness: &mut WitnessMap, + point_x: FunctionInput, + point_y: FunctionInput, + scalar_low: FunctionInput, + scalar_high: FunctionInput, + outputs: (Witness, Witness), +) -> Result<(), OpcodeResolutionError> { + let point_x = witness_to_value(initial_witness, point_x.witness)?; + let point_y = witness_to_value(initial_witness, point_y.witness)?; + let scalar_low = witness_to_value(initial_witness, scalar_low.witness)?; + let scalar_high = witness_to_value(initial_witness, scalar_high.witness)?; + + let (out_point_x, out_point_y) = + backend.variable_base_scalar_mul(point_x, point_y, scalar_low, scalar_high)?; + + insert_value(&outputs.0, out_point_x, initial_witness)?; + insert_value(&outputs.1, out_point_y, initial_witness)?; + + Ok(()) +} + pub(super) fn embedded_curve_add( backend: &impl BlackBoxFunctionSolver, initial_witness: &mut WitnessMap, diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/mod.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/mod.rs index 2753c7baaaa9..2487d511b502 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/mod.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/mod.rs @@ -20,7 +20,7 @@ mod pedersen; mod range; mod signature; -use fixed_base_scalar_mul::{embedded_curve_add, fixed_base_scalar_mul}; +use fixed_base_scalar_mul::{embedded_curve_add, fixed_base_scalar_mul, variable_base_scalar_mul}; // Hash functions should eventually be exposed for external consumers. use hash::{solve_generic_256_hash_opcode, solve_sha_256_permutation_opcode}; use logic::{and, xor}; @@ -158,6 +158,21 @@ pub(crate) fn solve( BlackBoxFuncCall::FixedBaseScalarMul { low, high, outputs } => { fixed_base_scalar_mul(backend, initial_witness, *low, *high, *outputs) } + BlackBoxFuncCall::VariableBaseScalarMul { + point_x, + point_y, + scalar_low, + scalar_high, + outputs, + } => variable_base_scalar_mul( + backend, + initial_witness, + *point_x, + *point_y, + *scalar_low, + *scalar_high, + *outputs, + ), BlackBoxFuncCall::EmbeddedCurveAdd { input1_x, input1_y, input2_x, input2_y, outputs } => { embedded_curve_add( backend, diff --git a/noir/noir-repo/acvm-repo/acvm_js/test/browser/execute_circuit.test.ts b/noir/noir-repo/acvm-repo/acvm_js/test/browser/execute_circuit.test.ts index 259c51ed1c62..f6287c2ae8a1 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/test/browser/execute_circuit.test.ts +++ b/noir/noir-repo/acvm-repo/acvm_js/test/browser/execute_circuit.test.ts @@ -103,6 +103,16 @@ it('successfully executes a FixedBaseScalarMul opcode', async () => { expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); }); +it('successfully executes a VariableBaseScalarMul opcode', async () => { + const { bytecode, initialWitnessMap, expectedWitnessMap } = await import('../shared/variable_base_scalar_mul'); + + const solvedWitness: WitnessMap = await executeCircuit(bytecode, initialWitnessMap, () => { + throw Error('unexpected oracle'); + }); + + expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); +}); + it('successfully executes a SchnorrVerify opcode', async () => { const { bytecode, initialWitnessMap, expectedWitnessMap } = await import('../shared/schnorr_verify'); diff --git a/noir/noir-repo/acvm-repo/acvm_js/test/node/execute_circuit.test.ts b/noir/noir-repo/acvm-repo/acvm_js/test/node/execute_circuit.test.ts index 32487f8bbbac..f9fd5c10b3ee 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/test/node/execute_circuit.test.ts +++ b/noir/noir-repo/acvm-repo/acvm_js/test/node/execute_circuit.test.ts @@ -100,6 +100,16 @@ it('successfully executes a FixedBaseScalarMul opcode', async () => { expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); }); +it('successfully executes a VariableBaseScalarMul opcode', async () => { + const { bytecode, initialWitnessMap, expectedWitnessMap } = await import('../shared/variable_base_scalar_mul'); + + const solvedWitness: WitnessMap = await executeCircuit(bytecode, initialWitnessMap, () => { + throw Error('unexpected oracle'); + }); + + expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); +}); + it('successfully executes a SchnorrVerify opcode', async () => { const { bytecode, initialWitnessMap, expectedWitnessMap } = await import('../shared/schnorr_verify'); diff --git a/noir/noir-repo/acvm-repo/acvm_js/test/shared/variable_base_scalar_mul.ts b/noir/noir-repo/acvm-repo/acvm_js/test/shared/variable_base_scalar_mul.ts new file mode 100644 index 000000000000..400f7bf4e614 --- /dev/null +++ b/noir/noir-repo/acvm-repo/acvm_js/test/shared/variable_base_scalar_mul.ts @@ -0,0 +1,21 @@ +// See `variable_base_scalar_mul_circuit` integration test in `acir/tests/test_program_serialization.rs`. +export const bytecode = Uint8Array.from([ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 93, 139, 65, 10, 0, 32, 8, 4, 213, 172, 46, 61, 186, 167, 103, 52, 65, 185, 176, + 140, 44, 142, 202, 73, 143, 42, 247, 230, 128, 51, 106, 176, 64, 135, 53, 218, 112, 252, 113, 141, 223, 187, 9, 155, + 36, 231, 203, 2, 176, 218, 19, 62, 137, 0, 0, 0, +]); +export const initialWitnessMap = new Map([ + [1, '0x0000000000000000000000000000000000000000000000000000000000000001'], + [2, '0x0000000000000002cf135e7506a45d632d270d45f1181294833fc48d823f272c'], + [3, '0x0000000000000000000000000000000000000000000000000000000000000001'], + [4, '0x0000000000000000000000000000000000000000000000000000000000000000'], +]); + +export const expectedWitnessMap = new Map([ + [1, '0x0000000000000000000000000000000000000000000000000000000000000001'], + [2, '0x0000000000000002cf135e7506a45d632d270d45f1181294833fc48d823f272c'], + [3, '0x0000000000000000000000000000000000000000000000000000000000000001'], + [4, '0x0000000000000000000000000000000000000000000000000000000000000000'], + [5, '0x0000000000000000000000000000000000000000000000000000000000000001'], + [6, '0x0000000000000002cf135e7506a45d632d270d45f1181294833fc48d823f272c'], +]); diff --git a/noir/noir-repo/acvm-repo/blackbox_solver/src/curve_specific_solver.rs b/noir/noir-repo/acvm-repo/blackbox_solver/src/curve_specific_solver.rs index fab67467d9ab..a809e21e2ca9 100644 --- a/noir/noir-repo/acvm-repo/blackbox_solver/src/curve_specific_solver.rs +++ b/noir/noir-repo/acvm-repo/blackbox_solver/src/curve_specific_solver.rs @@ -29,6 +29,13 @@ pub trait BlackBoxFunctionSolver { low: &FieldElement, high: &FieldElement, ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError>; + fn variable_base_scalar_mul( + &self, + point_x: &FieldElement, + point_y: &FieldElement, + scalar_low: &FieldElement, + scalar_high: &FieldElement, + ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError>; fn ec_add( &self, input1_x: &FieldElement, @@ -85,6 +92,15 @@ impl BlackBoxFunctionSolver for StubbedBlackBoxSolver { ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { Err(Self::fail(BlackBoxFunc::FixedBaseScalarMul)) } + fn variable_base_scalar_mul( + &self, + _point_x: &FieldElement, + _point_y: &FieldElement, + _scalar_low: &FieldElement, + _scalar_high: &FieldElement, + ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { + Err(Self::fail(BlackBoxFunc::VariableBaseScalarMul)) + } fn ec_add( &self, _input1_x: &FieldElement, diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/fixed_base_scalar_mul.rs b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/fixed_base_scalar_mul.rs index cd91c290f494..2d7ffe1cf1c8 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/fixed_base_scalar_mul.rs +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/fixed_base_scalar_mul.rs @@ -1,3 +1,4 @@ +// TODO(https://github.com/noir-lang/noir/issues/4932): rename this file to something more generic use ark_ec::AffineRepr; use ark_ff::MontConfig; use num_bigint::BigUint; @@ -6,40 +7,59 @@ use acir::{BlackBoxFunc, FieldElement}; use crate::BlackBoxResolutionError; +/// Performs fixed-base scalar multiplication using the curve's generator point. pub fn fixed_base_scalar_mul( low: &FieldElement, high: &FieldElement, ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { - let low: u128 = low.try_into_u128().ok_or_else(|| { + let generator = grumpkin::SWAffine::generator(); + let generator_x = FieldElement::from_repr(*generator.x().unwrap()); + let generator_y = FieldElement::from_repr(*generator.y().unwrap()); + + variable_base_scalar_mul(&generator_x, &generator_y, low, high).map_err(|err| match err { + BlackBoxResolutionError::Failed(_, message) => { + BlackBoxResolutionError::Failed(BlackBoxFunc::FixedBaseScalarMul, message) + } + }) +} + +pub fn variable_base_scalar_mul( + point_x: &FieldElement, + point_y: &FieldElement, + scalar_low: &FieldElement, + scalar_high: &FieldElement, +) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { + let point1 = create_point(*point_x, *point_y) + .map_err(|e| BlackBoxResolutionError::Failed(BlackBoxFunc::VariableBaseScalarMul, e))?; + + let scalar_low: u128 = scalar_low.try_into_u128().ok_or_else(|| { BlackBoxResolutionError::Failed( - BlackBoxFunc::FixedBaseScalarMul, - format!("Limb {} is not less than 2^128", low.to_hex()), + BlackBoxFunc::VariableBaseScalarMul, + format!("Limb {} is not less than 2^128", scalar_low.to_hex()), ) })?; - let high: u128 = high.try_into_u128().ok_or_else(|| { + let scalar_high: u128 = scalar_high.try_into_u128().ok_or_else(|| { BlackBoxResolutionError::Failed( - BlackBoxFunc::FixedBaseScalarMul, - format!("Limb {} is not less than 2^128", high.to_hex()), + BlackBoxFunc::VariableBaseScalarMul, + format!("Limb {} is not less than 2^128", scalar_high.to_hex()), ) })?; - let mut bytes = high.to_be_bytes().to_vec(); - bytes.extend_from_slice(&low.to_be_bytes()); + let mut bytes = scalar_high.to_be_bytes().to_vec(); + bytes.extend_from_slice(&scalar_low.to_be_bytes()); // Check if this is smaller than the grumpkin modulus let grumpkin_integer = BigUint::from_bytes_be(&bytes); if grumpkin_integer >= grumpkin::FrConfig::MODULUS.into() { return Err(BlackBoxResolutionError::Failed( - BlackBoxFunc::FixedBaseScalarMul, + BlackBoxFunc::VariableBaseScalarMul, format!("{} is not a valid grumpkin scalar", grumpkin_integer.to_str_radix(16)), )); } - let result = grumpkin::SWAffine::from( - grumpkin::SWAffine::generator().mul_bigint(grumpkin_integer.to_u64_digits()), - ); + let result = grumpkin::SWAffine::from(point1.mul_bigint(grumpkin_integer.to_u64_digits())); if let Some((res_x, res_y)) = result.xy() { Ok((FieldElement::from_repr(*res_x), FieldElement::from_repr(*res_y))) } else { @@ -47,17 +67,6 @@ pub fn fixed_base_scalar_mul( } } -fn create_point(x: FieldElement, y: FieldElement) -> Result { - let point = grumpkin::SWAffine::new_unchecked(x.into_repr(), y.into_repr()); - if !point.is_on_curve() { - return Err(format!("Point ({}, {}) is not on curve", x.to_hex(), y.to_hex())); - }; - if !point.is_in_correct_subgroup_assuming_on_curve() { - return Err(format!("Point ({}, {}) is not in correct subgroup", x.to_hex(), y.to_hex())); - }; - Ok(point) -} - pub fn embedded_curve_add( input1_x: FieldElement, input1_y: FieldElement, @@ -79,6 +88,17 @@ pub fn embedded_curve_add( } } +fn create_point(x: FieldElement, y: FieldElement) -> Result { + let point = grumpkin::SWAffine::new_unchecked(x.into_repr(), y.into_repr()); + if !point.is_on_curve() { + return Err(format!("Point ({}, {}) is not on curve", x.to_hex(), y.to_hex())); + }; + if !point.is_in_correct_subgroup_assuming_on_curve() { + return Err(format!("Point ({}, {}) is not in correct subgroup", x.to_hex(), y.to_hex())); + }; + Ok(point) +} + #[cfg(test)] mod grumpkin_fixed_base_scalar_mul { use ark_ff::BigInteger; @@ -147,6 +167,46 @@ mod grumpkin_fixed_base_scalar_mul { ); } + #[test] + fn variable_base_matches_fixed_base_for_generator_on_input( + ) -> Result<(), BlackBoxResolutionError> { + let low = FieldElement::one(); + let high = FieldElement::from(2u128); + + let generator = grumpkin::SWAffine::generator(); + let generator_x = FieldElement::from_repr(*generator.x().unwrap()); + let generator_y = FieldElement::from_repr(*generator.y().unwrap()); + + let fixed_res = fixed_base_scalar_mul(&low, &high)?; + let variable_res = variable_base_scalar_mul(&generator_x, &generator_y, &low, &high)?; + + assert_eq!(fixed_res, variable_res); + Ok(()) + } + + #[test] + fn variable_base_scalar_mul_rejects_invalid_point() { + let invalid_point_x = FieldElement::one(); + let invalid_point_y = FieldElement::one(); + let valid_scalar_low = FieldElement::zero(); + let valid_scalar_high = FieldElement::zero(); + + let res = variable_base_scalar_mul( + &invalid_point_x, + &invalid_point_y, + &valid_scalar_low, + &valid_scalar_high, + ); + + assert_eq!( + res, + Err(BlackBoxResolutionError::Failed( + BlackBoxFunc::VariableBaseScalarMul, + "Point (0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000000000000000000000001) is not on curve".into(), + )) + ); + } + #[test] fn rejects_addition_of_points_not_in_curve() { let x = FieldElement::from(1u128); diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs index 25b10252a784..9395260fe36a 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs @@ -9,7 +9,9 @@ mod fixed_base_scalar_mul; mod poseidon2; mod wasm; -pub use fixed_base_scalar_mul::{embedded_curve_add, fixed_base_scalar_mul}; +pub use fixed_base_scalar_mul::{ + embedded_curve_add, fixed_base_scalar_mul, variable_base_scalar_mul, +}; pub use poseidon2::poseidon2_permutation; use wasm::Barretenberg; @@ -97,6 +99,16 @@ impl BlackBoxFunctionSolver for Bn254BlackBoxSolver { fixed_base_scalar_mul(low, high) } + fn variable_base_scalar_mul( + &self, + point_x: &FieldElement, + point_y: &FieldElement, + low: &FieldElement, + high: &FieldElement, + ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { + variable_base_scalar_mul(point_x, point_y, low, high) + } + fn ec_add( &self, input1_x: &FieldElement, diff --git a/noir/noir-repo/acvm-repo/brillig/src/black_box.rs b/noir/noir-repo/acvm-repo/brillig/src/black_box.rs index 29861d0fd841..f31a434c7725 100644 --- a/noir/noir-repo/acvm-repo/brillig/src/black_box.rs +++ b/noir/noir-repo/acvm-repo/brillig/src/black_box.rs @@ -72,6 +72,14 @@ pub enum BlackBoxOp { high: MemoryAddress, result: HeapArray, }, + /// Performs scalar multiplication over the embedded curve with variable base point. + VariableBaseScalarMul { + point_x: MemoryAddress, + point_y: MemoryAddress, + scalar_low: MemoryAddress, + scalar_high: MemoryAddress, + result: HeapArray, + }, /// Performs addition over the embedded curve. EmbeddedCurveAdd { input1_x: MemoryAddress, diff --git a/noir/noir-repo/acvm-repo/brillig_vm/src/black_box.rs b/noir/noir-repo/acvm-repo/brillig_vm/src/black_box.rs index 19407da52dbe..9557cdae7b91 100644 --- a/noir/noir-repo/acvm-repo/brillig_vm/src/black_box.rs +++ b/noir/noir-repo/acvm-repo/brillig_vm/src/black_box.rs @@ -143,6 +143,19 @@ pub(crate) fn evaluate_black_box( memory.write_slice(memory.read_ref(result.pointer), &[x.into(), y.into()]); Ok(()) } + BlackBoxOp::VariableBaseScalarMul { point_x, point_y, scalar_low, scalar_high, result } => { + let point_x = memory.read(*point_x).try_into().unwrap(); + let point_y = memory.read(*point_y).try_into().unwrap(); + let scalar_low = memory.read(*scalar_low).try_into().unwrap(); + let scalar_high = memory.read(*scalar_high).try_into().unwrap(); + let (out_point_x, out_point_y) = + solver.variable_base_scalar_mul(&point_x, &point_y, &scalar_low, &scalar_high)?; + memory.write_slice( + memory.read_ref(result.pointer), + &[out_point_x.into(), out_point_y.into()], + ); + Ok(()) + } BlackBoxOp::EmbeddedCurveAdd { input1_x, input1_y, input2_x, input2_y, result } => { let input1_x = memory.read(*input1_x).try_into().unwrap(); let input1_y = memory.read(*input1_y).try_into().unwrap(); @@ -289,6 +302,7 @@ fn black_box_function_from_op(op: &BlackBoxOp) -> BlackBoxFunc { BlackBoxOp::PedersenCommitment { .. } => BlackBoxFunc::PedersenCommitment, BlackBoxOp::PedersenHash { .. } => BlackBoxFunc::PedersenHash, BlackBoxOp::FixedBaseScalarMul { .. } => BlackBoxFunc::FixedBaseScalarMul, + BlackBoxOp::VariableBaseScalarMul { .. } => BlackBoxFunc::VariableBaseScalarMul, BlackBoxOp::EmbeddedCurveAdd { .. } => BlackBoxFunc::EmbeddedCurveAdd, BlackBoxOp::BigIntAdd { .. } => BlackBoxFunc::BigIntAdd, BlackBoxOp::BigIntSub { .. } => BlackBoxFunc::BigIntSub, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs index ee047903743d..210e56b2ecba 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs @@ -201,7 +201,26 @@ pub(crate) fn convert_black_box_call( }); } else { unreachable!( - "ICE: FixedBaseScalarMul expects one register argument and one array result" + "ICE: FixedBaseScalarMul expects two register arguments and one array result" + ) + } + } + BlackBoxFunc::VariableBaseScalarMul => { + if let ( + [BrilligVariable::SingleAddr(point_x), BrilligVariable::SingleAddr(point_y), BrilligVariable::SingleAddr(scalar_low), BrilligVariable::SingleAddr(scalar_high)], + [BrilligVariable::BrilligArray(result_array)], + ) = (function_arguments, function_results) + { + brillig_context.black_box_op_instruction(BlackBoxOp::VariableBaseScalarMul { + point_x: point_x.address, + point_y: point_y.address, + scalar_low: scalar_low.address, + scalar_high: scalar_high.address, + result: result_array.to_heap_array(), + }); + } else { + unreachable!( + "ICE: VariableBaseScalarMul expects four register arguments and one array result" ) } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir.rs index ded41e02bdae..b4ed59de59d9 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir.rs @@ -175,6 +175,16 @@ pub(crate) mod tests { Ok((4_u128.into(), 5_u128.into())) } + fn variable_base_scalar_mul( + &self, + _point_x: &FieldElement, + _point_y: &FieldElement, + _scalar_low: &FieldElement, + _scalar_high: &FieldElement, + ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { + Ok((7_u128.into(), 8_u128.into())) + } + fn ec_add( &self, _input1_x: &FieldElement, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs index 5601bbde8772..8b00939b3a71 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs @@ -324,6 +324,23 @@ impl DebugShow { result ); } + BlackBoxOp::VariableBaseScalarMul { + point_x, + point_y, + scalar_low, + scalar_high, + result, + } => { + debug_println!( + self.enable_debug_trace, + " VARIABLE_BASE_SCALAR_MUL ({} {}) ({} {}) -> {}", + point_x, + point_y, + scalar_low, + scalar_high, + result + ); + } BlackBoxOp::EmbeddedCurveAdd { input1_x, input1_y, input2_x, input2_y, result } => { debug_println!( self.enable_debug_trace, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index c084ba37fee6..2f4f4f9f6cc7 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -283,6 +283,13 @@ impl GeneratedAcir { high: inputs[1][0], outputs: (outputs[0], outputs[1]), }, + BlackBoxFunc::VariableBaseScalarMul => BlackBoxFuncCall::VariableBaseScalarMul { + point_x: inputs[0][0], + point_y: inputs[1][0], + scalar_low: inputs[2][0], + scalar_high: inputs[3][0], + outputs: (outputs[0], outputs[1]), + }, BlackBoxFunc::EmbeddedCurveAdd => BlackBoxFuncCall::EmbeddedCurveAdd { input1_x: inputs[0][0], input1_y: inputs[1][0], @@ -669,6 +676,10 @@ fn black_box_func_expected_input_size(name: BlackBoxFunc) -> Option { // is the low and high limbs of the scalar BlackBoxFunc::FixedBaseScalarMul => Some(2), + // Inputs for variable based scalar multiplication are the x and y coordinates of the base point and low + // and high limbs of the scalar + BlackBoxFunc::VariableBaseScalarMul => Some(4), + // Recursive aggregation has a variable number of inputs BlackBoxFunc::RecursiveAggregation => None, @@ -723,7 +734,9 @@ fn black_box_expected_output_size(name: BlackBoxFunc) -> Option { // Output of operations over the embedded curve // will be 2 field elements representing the point. - BlackBoxFunc::FixedBaseScalarMul | BlackBoxFunc::EmbeddedCurveAdd => Some(2), + BlackBoxFunc::FixedBaseScalarMul + | BlackBoxFunc::VariableBaseScalarMul + | BlackBoxFunc::EmbeddedCurveAdd => Some(2), // Big integer operations return a big integer BlackBoxFunc::BigIntAdd diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index 1187ea8cb07c..a8365ffef39b 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -453,6 +453,7 @@ fn simplify_black_box_func( } BlackBoxFunc::FixedBaseScalarMul + | BlackBoxFunc::VariableBaseScalarMul | BlackBoxFunc::SchnorrVerify | BlackBoxFunc::PedersenCommitment | BlackBoxFunc::PedersenHash diff --git a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/scalar.mdx b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/scalar.mdx index c2946b2b73b3..b835236a03e4 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/scalar.mdx +++ b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/scalar.mdx @@ -1,6 +1,6 @@ --- title: Scalar multiplication -description: See how you can perform scalar multiplications over a fixed base in Noir +description: See how you can perform scalar multiplications over a fixed and variable bases in Noir keywords: [cryptographic primitives, Noir project, scalar multiplication] sidebar_position: 1 --- @@ -9,17 +9,35 @@ import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; ## scalar_mul::fixed_base_embedded_curve -Performs scalar multiplication over the embedded curve whose coordinates are defined by the -configured noir field. For the BN254 scalar field, this is BabyJubJub or Grumpkin. +Performs scalar multiplication of a fixed base/generator over the embedded curve whose coordinates are defined +by the configured noir field. For the BN254 scalar field, this is BabyJubJub or Grumpkin. Suffixes `_low` and +`_high` denote low and high limbs of the input scalar. #include_code fixed_base_embedded_curve noir_stdlib/src/scalar_mul.nr rust example ```rust -fn main(x : Field) { - let scal = std::scalar_mul::fixed_base_embedded_curve(x); - println(scal); +fn main(scalar_low: Field, scalar_high: Field) { + let point = std::scalar_mul::fixed_base_embedded_curve(scalar_low, scalar_high); + println(point); +} +``` + +## scalar_mul::variable_base_embedded_curve + +Performs scalar multiplication of a variable base/input point over the embedded curve whose coordinates are defined +by the configured noir field. For the BN254 scalar field, this is BabyJubJub or Grumpkin. Suffixes `_low` and +`_high` denote low and high limbs of the input scalar. + +#include_code variable_base_embedded_curve noir_stdlib/src/scalar_mul.nr rust + +example + +```rust +fn main(point_x: Field, point_y: Field, scalar_low: Field, scalar_high: Field) { + let resulting_point = std::scalar_mul::fixed_base_embedded_curve(point_x, point_y, scalar_low, scalar_high); + println(resulting_point); } ``` diff --git a/noir/noir-repo/noir_stdlib/src/grumpkin_scalar_mul.nr b/noir/noir-repo/noir_stdlib/src/grumpkin_scalar_mul.nr index 06d30d623321..c1195073ef6d 100644 --- a/noir/noir-repo/noir_stdlib/src/grumpkin_scalar_mul.nr +++ b/noir/noir-repo/noir_stdlib/src/grumpkin_scalar_mul.nr @@ -1,7 +1,6 @@ use crate::grumpkin_scalar::GrumpkinScalar; -use crate::scalar_mul::fixed_base_embedded_curve; +use crate::scalar_mul::{fixed_base_embedded_curve, variable_base_embedded_curve}; pub fn grumpkin_fixed_base(scalar: GrumpkinScalar) -> [Field; 2] { - // TODO: this should use both the low and high limbs to do the scalar multiplication fixed_base_embedded_curve(scalar.low, scalar.high) -} +} \ No newline at end of file diff --git a/noir/noir-repo/noir_stdlib/src/scalar_mul.nr b/noir/noir-repo/noir_stdlib/src/scalar_mul.nr index eee7aac39f2e..457b7b7791c8 100644 --- a/noir/noir-repo/noir_stdlib/src/scalar_mul.nr +++ b/noir/noir-repo/noir_stdlib/src/scalar_mul.nr @@ -1,5 +1,6 @@ use crate::ops::Add; +// TODO(https://github.com/noir-lang/noir/issues/4931) struct EmbeddedCurvePoint { x: Field, y: Field, @@ -26,12 +27,30 @@ impl Add for EmbeddedCurvePoint { #[foreign(fixed_base_scalar_mul)] // docs:start:fixed_base_embedded_curve pub fn fixed_base_embedded_curve( - low: Field, - high: Field + low: Field, // low limb of the scalar + high: Field // high limb of the scalar ) -> [Field; 2] // docs:end:fixed_base_embedded_curve {} +// Computes a variable base scalar multiplication over the embedded curve. +// For bn254, We have Grumpkin and Baby JubJub. +// For bls12-381, we have JubJub and Bandersnatch. +// +// The embedded curve being used is decided by the +// underlying proof system. +// TODO(https://github.com/noir-lang/noir/issues/4931): use a point struct instead of two fields +#[foreign(variable_base_scalar_mul)] +// docs:start:variable_base_embedded_curve +pub fn variable_base_embedded_curve( + point_x: Field, // x coordinate of a point to multiply the scalar with + point_y: Field, // y coordinate of a point to multiply the scalar with + scalar_low: Field, // low limb of the scalar + scalar_high: Field // high limb of the scalar +) -> [Field; 2] +// docs:end:variable_base_embedded_curve +{} + // This is a hack as returning an `EmbeddedCurvePoint` from a foreign function in brillig returns a [BrilligVariable::SingleAddr; 2] rather than BrilligVariable::BrilligArray // as is defined in the brillig bytecode format. This is a workaround which allows us to fix this without modifying the serialization format. fn embedded_curve_add(point1: EmbeddedCurvePoint, point2: EmbeddedCurvePoint) -> EmbeddedCurvePoint { diff --git a/noir/noir-repo/test_programs/execution_success/scalar_mul/Nargo.toml b/noir/noir-repo/test_programs/execution_success/fixed_base_scalar_mul/Nargo.toml similarity index 63% rename from noir/noir-repo/test_programs/execution_success/scalar_mul/Nargo.toml rename to noir/noir-repo/test_programs/execution_success/fixed_base_scalar_mul/Nargo.toml index 926114ec3745..a8e45c9b5ade 100644 --- a/noir/noir-repo/test_programs/execution_success/scalar_mul/Nargo.toml +++ b/noir/noir-repo/test_programs/execution_success/fixed_base_scalar_mul/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "scalar_mul" +name = "fixed_base_scalar_mul" type = "bin" authors = [""] diff --git a/noir/noir-repo/test_programs/execution_success/scalar_mul/Prover.toml b/noir/noir-repo/test_programs/execution_success/fixed_base_scalar_mul/Prover.toml similarity index 100% rename from noir/noir-repo/test_programs/execution_success/scalar_mul/Prover.toml rename to noir/noir-repo/test_programs/execution_success/fixed_base_scalar_mul/Prover.toml diff --git a/noir/noir-repo/test_programs/execution_success/scalar_mul/src/main.nr b/noir/noir-repo/test_programs/execution_success/fixed_base_scalar_mul/src/main.nr similarity index 100% rename from noir/noir-repo/test_programs/execution_success/scalar_mul/src/main.nr rename to noir/noir-repo/test_programs/execution_success/fixed_base_scalar_mul/src/main.nr diff --git a/noir/noir-repo/test_programs/execution_success/variable_base_scalar_mul/Nargo.toml b/noir/noir-repo/test_programs/execution_success/variable_base_scalar_mul/Nargo.toml new file mode 100644 index 000000000000..66712ab503cb --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/variable_base_scalar_mul/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "variable_base_scalar_mul" +type = "bin" +authors = [""] + +[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/variable_base_scalar_mul/Prover.toml b/noir/noir-repo/test_programs/execution_success/variable_base_scalar_mul/Prover.toml new file mode 100644 index 000000000000..51d6fc9b96c5 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/variable_base_scalar_mul/Prover.toml @@ -0,0 +1,4 @@ +point_x = "0x0000000000000000000000000000000000000000000000000000000000000001" +point_y = "0x0000000000000002cf135e7506a45d632d270d45f1181294833fc48d823f272c" +scalar_low = "0x0000000000000000000000000000000000000000000000000000000000000003" +scalar_high = "0x0000000000000000000000000000000000000000000000000000000000000000" diff --git a/noir/noir-repo/test_programs/execution_success/variable_base_scalar_mul/src/main.nr b/noir/noir-repo/test_programs/execution_success/variable_base_scalar_mul/src/main.nr new file mode 100644 index 000000000000..4914ad017771 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/variable_base_scalar_mul/src/main.nr @@ -0,0 +1,33 @@ +use dep::std; + +fn main(point_x: pub Field, point_y: pub Field, scalar_low: pub Field, scalar_high: pub Field) { + // We multiply the point by 3 and check it matches result out of embedded_curve_add func + let res = std::scalar_mul::variable_base_embedded_curve(point_x, point_y, scalar_low, scalar_high); + + let point = std::scalar_mul::EmbeddedCurvePoint { x: point_x, y: point_y }; + + let double = point.double(); + let triple = point + double; + + assert(triple.x == res[0]); + assert(triple.y == res[1]); + + // We test that brillig gives us the same result + let brillig_res = get_brillig_result(point_x, point_y, scalar_low, scalar_high); + assert(res[0] == brillig_res[0]); + assert(res[1] == brillig_res[1]); + + // Multiplying the point by 1 should return the same point + let res = std::scalar_mul::variable_base_embedded_curve(point_x, point_y, 1, 0); + assert(point_x == res[0]); + assert(point_y == res[1]); +} + +unconstrained fn get_brillig_result( + point_x: Field, + point_y: Field, + scalar_low: Field, + scalar_high: Field +) -> [Field; 2] { + std::scalar_mul::variable_base_embedded_curve(point_x, point_y, scalar_low, scalar_high) +} diff --git a/noir/noir-repo/tooling/lsp/src/solver.rs b/noir/noir-repo/tooling/lsp/src/solver.rs index 0fea9b16b54a..b47c30af5f68 100644 --- a/noir/noir-repo/tooling/lsp/src/solver.rs +++ b/noir/noir-repo/tooling/lsp/src/solver.rs @@ -32,6 +32,16 @@ impl BlackBoxFunctionSolver for WrapperSolver { self.0.fixed_base_scalar_mul(low, high) } + fn variable_base_scalar_mul( + &self, + point_x: &acvm::FieldElement, + point_y: &acvm::FieldElement, + scalar_low: &acvm::FieldElement, + scalar_high: &acvm::FieldElement, + ) -> Result<(acvm::FieldElement, acvm::FieldElement), acvm::BlackBoxResolutionError> { + self.0.variable_base_scalar_mul(point_x, point_y, scalar_low, scalar_high) + } + fn pedersen_hash( &self, inputs: &[acvm::FieldElement], diff --git a/yarn-project/end-to-end/src/e2e_state_vars.test.ts b/yarn-project/end-to-end/src/e2e_state_vars.test.ts index 8a6ed6dc23ea..8d63cafea4f9 100644 --- a/yarn-project/end-to-end/src/e2e_state_vars.test.ts +++ b/yarn-project/end-to-end/src/e2e_state_vars.test.ts @@ -22,7 +22,7 @@ describe('e2e_state_vars', () => { beforeAll(async () => { ({ teardown, wallet, pxe } = await setup(2)); contract = await DocsExampleContract.deploy(wallet).send().deployed(); - }, 30_000); + }, 60_000); afterAll(() => teardown());