diff --git a/.aztec-sync-commit b/.aztec-sync-commit index a159698f8a8..af0214b9f78 100644 --- a/.aztec-sync-commit +++ b/.aztec-sync-commit @@ -1 +1 @@ -2e64428af9525bd8c390931061505f7b48d729a4 +f95de6b498d34e138cd55f88340917c6881eec6b diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d2553b003f8..c02d24e91b8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -196,7 +196,7 @@ yarn docusaurus docs:version This should create a new version by copying the docs folder and the sidebars.js file to the relevant folders, as well as adding this version to versions.json. -You can then open a Pull Request according to the the [PR section](#pull-requests) +You can then open a Pull Request according to the [PR section](#pull-requests) ## Changelog diff --git a/Cargo.lock b/Cargo.lock index eaed1c08bec..2e31dee9a60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3068,6 +3068,7 @@ dependencies = [ "acvm", "iter-extended", "noirc_frontend", + "noirc_printable_type", "num-bigint", "num-traits", "serde", diff --git a/Cargo.toml b/Cargo.toml index ed0aef0d35b..6fe7f099e82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -111,7 +111,7 @@ chumsky = { git = "https://github.com/jfecher/chumsky", rev = "ad9d312", default criterion = "0.5.0" # Note that using the "frame-pointer" feature breaks framegraphs on linux # https://github.com/tikv/pprof-rs/pull/172 -pprof = { version = "0.13", features = ["flamegraph","criterion"] } +pprof = { version = "0.13", features = ["flamegraph", "criterion"] } dirs = "4" diff --git a/acvm-repo/acir/README.md b/acvm-repo/acir/README.md index 801aeac1140..e72f7ea178d 100644 --- a/acvm-repo/acir/README.md +++ b/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/acvm-repo/acir/benches/serialization.rs b/acvm-repo/acir/benches/serialization.rs index 73e3916a73b..e51726e3901 100644 --- a/acvm-repo/acir/benches/serialization.rs +++ b/acvm-repo/acir/benches/serialization.rs @@ -40,6 +40,7 @@ fn sample_program(num_opcodes: usize) -> Program { assert_messages: Vec::new(), recursive: false, }], + unconstrained_functions: Vec::new(), } } diff --git a/acvm-repo/acir/codegen/acir.cpp b/acvm-repo/acir/codegen/acir.cpp index 6c7bd347e5d..1e5207c01cb 100644 --- a/acvm-repo/acir/codegen/acir.cpp +++ b/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; @@ -324,6 +336,134 @@ namespace Program { static BrilligInputs bincodeDeserialize(std::vector); }; + struct BrilligOutputs { + + struct Simple { + Program::Witness value; + + friend bool operator==(const Simple&, const Simple&); + std::vector bincodeSerialize() const; + static Simple bincodeDeserialize(std::vector); + }; + + struct Array { + std::vector value; + + friend bool operator==(const Array&, const Array&); + std::vector bincodeSerialize() const; + static Array bincodeDeserialize(std::vector); + }; + + std::variant value; + + friend bool operator==(const BrilligOutputs&, const BrilligOutputs&); + std::vector bincodeSerialize() const; + static BrilligOutputs bincodeDeserialize(std::vector); + }; + + struct Directive { + + struct ToLeRadix { + Program::Expression a; + std::vector b; + uint32_t radix; + + friend bool operator==(const ToLeRadix&, const ToLeRadix&); + std::vector bincodeSerialize() const; + static ToLeRadix bincodeDeserialize(std::vector); + }; + + std::variant value; + + friend bool operator==(const Directive&, const Directive&); + std::vector bincodeSerialize() const; + static Directive bincodeDeserialize(std::vector); + }; + + struct MemOp { + Program::Expression operation; + Program::Expression index; + Program::Expression value; + + friend bool operator==(const MemOp&, const MemOp&); + std::vector bincodeSerialize() const; + static MemOp bincodeDeserialize(std::vector); + }; + + struct Opcode { + + struct AssertZero { + Program::Expression value; + + friend bool operator==(const AssertZero&, const AssertZero&); + std::vector bincodeSerialize() const; + static AssertZero bincodeDeserialize(std::vector); + }; + + struct BlackBoxFuncCall { + Program::BlackBoxFuncCall value; + + friend bool operator==(const BlackBoxFuncCall&, const BlackBoxFuncCall&); + std::vector bincodeSerialize() const; + static BlackBoxFuncCall bincodeDeserialize(std::vector); + }; + + struct Directive { + Program::Directive value; + + friend bool operator==(const Directive&, const Directive&); + std::vector bincodeSerialize() const; + static Directive bincodeDeserialize(std::vector); + }; + + struct MemoryOp { + Program::BlockId block_id; + Program::MemOp op; + std::optional predicate; + + friend bool operator==(const MemoryOp&, const MemoryOp&); + std::vector bincodeSerialize() const; + static MemoryOp bincodeDeserialize(std::vector); + }; + + struct MemoryInit { + Program::BlockId block_id; + std::vector init; + + friend bool operator==(const MemoryInit&, const MemoryInit&); + std::vector bincodeSerialize() const; + static MemoryInit bincodeDeserialize(std::vector); + }; + + struct BrilligCall { + uint32_t id; + std::vector inputs; + std::vector outputs; + std::optional predicate; + + friend bool operator==(const BrilligCall&, const BrilligCall&); + std::vector bincodeSerialize() const; + static BrilligCall bincodeDeserialize(std::vector); + }; + + struct Call { + uint32_t id; + std::vector inputs; + std::vector outputs; + std::optional predicate; + + friend bool operator==(const Call&, const Call&); + std::vector bincodeSerialize() const; + static Call bincodeDeserialize(std::vector); + }; + + std::variant value; + + friend bool operator==(const Opcode&, const Opcode&); + std::vector bincodeSerialize() const; + static Opcode bincodeDeserialize(std::vector); + }; + struct BinaryFieldOp { struct Add { @@ -601,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; @@ -692,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; @@ -922,8 +1074,7 @@ namespace Program { }; struct Trap { - uint64_t revert_data_offset; - uint64_t revert_data_size; + Program::HeapArray revert_data; friend bool operator==(const Trap&, const Trap&); std::vector bincodeSerialize() const; @@ -946,151 +1097,54 @@ namespace Program { static BrilligOpcode bincodeDeserialize(std::vector); }; - struct BrilligOutputs { - - struct Simple { - Program::Witness value; - - friend bool operator==(const Simple&, const Simple&); - std::vector bincodeSerialize() const; - static Simple bincodeDeserialize(std::vector); - }; + struct ExpressionOrMemory { - struct Array { - std::vector value; + struct Expression { + Program::Expression value; - friend bool operator==(const Array&, const Array&); + friend bool operator==(const Expression&, const Expression&); std::vector bincodeSerialize() const; - static Array bincodeDeserialize(std::vector); + static Expression bincodeDeserialize(std::vector); }; - std::variant value; - - friend bool operator==(const BrilligOutputs&, const BrilligOutputs&); - std::vector bincodeSerialize() const; - static BrilligOutputs bincodeDeserialize(std::vector); - }; - - struct Brillig { - std::vector inputs; - std::vector outputs; - std::vector bytecode; - std::optional predicate; - - friend bool operator==(const Brillig&, const Brillig&); - std::vector bincodeSerialize() const; - static Brillig bincodeDeserialize(std::vector); - }; - - struct Directive { - - struct ToLeRadix { - Program::Expression a; - std::vector b; - uint32_t radix; + struct Memory { + Program::BlockId value; - friend bool operator==(const ToLeRadix&, const ToLeRadix&); + friend bool operator==(const Memory&, const Memory&); std::vector bincodeSerialize() const; - static ToLeRadix bincodeDeserialize(std::vector); + static Memory bincodeDeserialize(std::vector); }; - std::variant value; + std::variant value; - friend bool operator==(const Directive&, const Directive&); + friend bool operator==(const ExpressionOrMemory&, const ExpressionOrMemory&); std::vector bincodeSerialize() const; - static Directive bincodeDeserialize(std::vector); + static ExpressionOrMemory bincodeDeserialize(std::vector); }; - struct MemOp { - Program::Expression operation; - Program::Expression index; - Program::Expression value; - - friend bool operator==(const MemOp&, const MemOp&); - std::vector bincodeSerialize() const; - static MemOp bincodeDeserialize(std::vector); - }; - - struct Opcode { - - struct AssertZero { - Program::Expression value; + struct AssertionPayload { - friend bool operator==(const AssertZero&, const AssertZero&); - std::vector bincodeSerialize() const; - static AssertZero bincodeDeserialize(std::vector); - }; - - struct BlackBoxFuncCall { - Program::BlackBoxFuncCall value; - - friend bool operator==(const BlackBoxFuncCall&, const BlackBoxFuncCall&); - std::vector bincodeSerialize() const; - static BlackBoxFuncCall bincodeDeserialize(std::vector); - }; - - struct Directive { - Program::Directive value; - - friend bool operator==(const Directive&, const Directive&); - std::vector bincodeSerialize() const; - static Directive bincodeDeserialize(std::vector); - }; - - struct Brillig { - Program::Brillig value; - - friend bool operator==(const Brillig&, const Brillig&); - std::vector bincodeSerialize() const; - static Brillig bincodeDeserialize(std::vector); - }; - - struct MemoryOp { - Program::BlockId block_id; - Program::MemOp op; - std::optional predicate; - - friend bool operator==(const MemoryOp&, const MemoryOp&); - std::vector bincodeSerialize() const; - static MemoryOp bincodeDeserialize(std::vector); - }; - - struct MemoryInit { - Program::BlockId block_id; - std::vector init; - - friend bool operator==(const MemoryInit&, const MemoryInit&); - std::vector bincodeSerialize() const; - static MemoryInit bincodeDeserialize(std::vector); - }; - - struct BrilligCall { - uint32_t id; - std::vector inputs; - std::vector outputs; - std::optional predicate; + struct StaticString { + std::string value; - friend bool operator==(const BrilligCall&, const BrilligCall&); + friend bool operator==(const StaticString&, const StaticString&); std::vector bincodeSerialize() const; - static BrilligCall bincodeDeserialize(std::vector); + static StaticString bincodeDeserialize(std::vector); }; - struct Call { - uint32_t id; - std::vector inputs; - std::vector outputs; - std::optional predicate; + struct Dynamic { + std::tuple> value; - friend bool operator==(const Call&, const Call&); + friend bool operator==(const Dynamic&, const Dynamic&); std::vector bincodeSerialize() const; - static Call bincodeDeserialize(std::vector); + static Dynamic bincodeDeserialize(std::vector); }; - std::variant value; + std::variant value; - friend bool operator==(const Opcode&, const Opcode&); + friend bool operator==(const AssertionPayload&, const AssertionPayload&); std::vector bincodeSerialize() const; - static Opcode bincodeDeserialize(std::vector); + static AssertionPayload bincodeDeserialize(std::vector); }; struct ExpressionWidth { @@ -1157,7 +1211,7 @@ namespace Program { std::vector private_parameters; Program::PublicInputs public_parameters; Program::PublicInputs return_values; - std::vector> assert_messages; + std::vector> assert_messages; bool recursive; friend bool operator==(const Circuit&, const Circuit&); @@ -1173,17 +1227,135 @@ namespace Program { static BrilligBytecode bincodeDeserialize(std::vector); }; - struct Program { - std::vector functions; - std::vector unconstrained_functions; + struct Program { + std::vector functions; + std::vector unconstrained_functions; + + friend bool operator==(const Program&, const Program&); + std::vector bincodeSerialize() const; + static Program bincodeDeserialize(std::vector); + }; + +} // end of namespace Program + + +namespace Program { + + inline bool operator==(const AssertionPayload &lhs, const AssertionPayload &rhs) { + if (!(lhs.value == rhs.value)) { return false; } + return true; + } + + inline std::vector AssertionPayload::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline AssertionPayload AssertionPayload::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::AssertionPayload &obj, Serializer &serializer) { + serializer.increase_container_depth(); + serde::Serializable::serialize(obj.value, serializer); + serializer.decrease_container_depth(); +} + +template <> +template +Program::AssertionPayload serde::Deserializable::deserialize(Deserializer &deserializer) { + deserializer.increase_container_depth(); + Program::AssertionPayload obj; + obj.value = serde::Deserializable::deserialize(deserializer); + deserializer.decrease_container_depth(); + return obj; +} + +namespace Program { + + inline bool operator==(const AssertionPayload::StaticString &lhs, const AssertionPayload::StaticString &rhs) { + if (!(lhs.value == rhs.value)) { return false; } + return true; + } + + inline std::vector AssertionPayload::StaticString::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline AssertionPayload::StaticString AssertionPayload::StaticString::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::AssertionPayload::StaticString &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.value, serializer); +} + +template <> +template +Program::AssertionPayload::StaticString serde::Deserializable::deserialize(Deserializer &deserializer) { + Program::AssertionPayload::StaticString obj; + obj.value = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Program { + + inline bool operator==(const AssertionPayload::Dynamic &lhs, const AssertionPayload::Dynamic &rhs) { + if (!(lhs.value == rhs.value)) { return false; } + return true; + } + + inline std::vector AssertionPayload::Dynamic::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } - friend bool operator==(const Program&, const Program&); - std::vector bincodeSerialize() const; - static Program bincodeDeserialize(std::vector); - }; + inline AssertionPayload::Dynamic AssertionPayload::Dynamic::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::AssertionPayload::Dynamic &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.value, serializer); +} + +template <> +template +Program::AssertionPayload::Dynamic serde::Deserializable::deserialize(Deserializer &deserializer) { + Program::AssertionPayload::Dynamic obj; + obj.value = serde::Deserializable::deserialize(deserializer); + return obj; +} namespace Program { @@ -2542,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) { @@ -3602,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) { @@ -4043,57 +4315,6 @@ Program::BlockId serde::Deserializable::deserialize(Deserializ return obj; } -namespace Program { - - inline bool operator==(const Brillig &lhs, const Brillig &rhs) { - if (!(lhs.inputs == rhs.inputs)) { return false; } - if (!(lhs.outputs == rhs.outputs)) { return false; } - if (!(lhs.bytecode == rhs.bytecode)) { return false; } - if (!(lhs.predicate == rhs.predicate)) { return false; } - return true; - } - - inline std::vector Brillig::bincodeSerialize() const { - auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); - return std::move(serializer).bytes(); - } - - inline Brillig Brillig::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::Brillig &obj, Serializer &serializer) { - serializer.increase_container_depth(); - serde::Serializable::serialize(obj.inputs, serializer); - serde::Serializable::serialize(obj.outputs, serializer); - serde::Serializable::serialize(obj.bytecode, serializer); - serde::Serializable::serialize(obj.predicate, serializer); - serializer.decrease_container_depth(); -} - -template <> -template -Program::Brillig serde::Deserializable::deserialize(Deserializer &deserializer) { - deserializer.increase_container_depth(); - Program::Brillig obj; - obj.inputs = serde::Deserializable::deserialize(deserializer); - obj.outputs = serde::Deserializable::deserialize(deserializer); - obj.bytecode = serde::Deserializable::deserialize(deserializer); - obj.predicate = serde::Deserializable::deserialize(deserializer); - deserializer.decrease_container_depth(); - return obj; -} - namespace Program { inline bool operator==(const BrilligBytecode &lhs, const BrilligBytecode &rhs) { @@ -5017,8 +5238,7 @@ Program::BrilligOpcode::BlackBox serde::Deserializable template void serde::Serializable::serialize(const Program::BrilligOpcode::Trap &obj, Serializer &serializer) { - serde::Serializable::serialize(obj.revert_data_offset, serializer); - serde::Serializable::serialize(obj.revert_data_size, serializer); + serde::Serializable::serialize(obj.revert_data, serializer); } template <> template Program::BrilligOpcode::Trap serde::Deserializable::deserialize(Deserializer &deserializer) { Program::BrilligOpcode::Trap obj; - obj.revert_data_offset = serde::Deserializable::deserialize(deserializer); - obj.revert_data_size = serde::Deserializable::deserialize(deserializer); + obj.revert_data = serde::Deserializable::deserialize(deserializer); return obj; } @@ -5411,6 +5629,124 @@ Program::Expression serde::Deserializable::deserialize(Dese return obj; } +namespace Program { + + inline bool operator==(const ExpressionOrMemory &lhs, const ExpressionOrMemory &rhs) { + if (!(lhs.value == rhs.value)) { return false; } + return true; + } + + inline std::vector ExpressionOrMemory::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline ExpressionOrMemory ExpressionOrMemory::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::ExpressionOrMemory &obj, Serializer &serializer) { + serializer.increase_container_depth(); + serde::Serializable::serialize(obj.value, serializer); + serializer.decrease_container_depth(); +} + +template <> +template +Program::ExpressionOrMemory serde::Deserializable::deserialize(Deserializer &deserializer) { + deserializer.increase_container_depth(); + Program::ExpressionOrMemory obj; + obj.value = serde::Deserializable::deserialize(deserializer); + deserializer.decrease_container_depth(); + return obj; +} + +namespace Program { + + inline bool operator==(const ExpressionOrMemory::Expression &lhs, const ExpressionOrMemory::Expression &rhs) { + if (!(lhs.value == rhs.value)) { return false; } + return true; + } + + inline std::vector ExpressionOrMemory::Expression::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline ExpressionOrMemory::Expression ExpressionOrMemory::Expression::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::ExpressionOrMemory::Expression &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.value, serializer); +} + +template <> +template +Program::ExpressionOrMemory::Expression serde::Deserializable::deserialize(Deserializer &deserializer) { + Program::ExpressionOrMemory::Expression obj; + obj.value = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Program { + + inline bool operator==(const ExpressionOrMemory::Memory &lhs, const ExpressionOrMemory::Memory &rhs) { + if (!(lhs.value == rhs.value)) { return false; } + return true; + } + + inline std::vector ExpressionOrMemory::Memory::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline ExpressionOrMemory::Memory ExpressionOrMemory::Memory::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::ExpressionOrMemory::Memory &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.value, serializer); +} + +template <> +template +Program::ExpressionOrMemory::Memory serde::Deserializable::deserialize(Deserializer &deserializer) { + Program::ExpressionOrMemory::Memory obj; + obj.value = serde::Deserializable::deserialize(deserializer); + return obj; +} + namespace Program { inline bool operator==(const ExpressionWidth &lhs, const ExpressionWidth &rhs) { @@ -6066,44 +6402,6 @@ Program::Opcode::Directive serde::Deserializable::de return obj; } -namespace Program { - - inline bool operator==(const Opcode::Brillig &lhs, const Opcode::Brillig &rhs) { - if (!(lhs.value == rhs.value)) { return false; } - return true; - } - - inline std::vector Opcode::Brillig::bincodeSerialize() const { - auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); - return std::move(serializer).bytes(); - } - - inline Opcode::Brillig Opcode::Brillig::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::Opcode::Brillig &obj, Serializer &serializer) { - serde::Serializable::serialize(obj.value, serializer); -} - -template <> -template -Program::Opcode::Brillig serde::Deserializable::deserialize(Deserializer &deserializer) { - Program::Opcode::Brillig obj; - obj.value = serde::Deserializable::deserialize(deserializer); - return obj; -} - namespace Program { inline bool operator==(const Opcode::MemoryOp &lhs, const Opcode::MemoryOp &rhs) { diff --git a/acvm-repo/acir/src/circuit/black_box_functions.rs b/acvm-repo/acir/src/circuit/black_box_functions.rs index 0a7ee244a5e..9a43702a408 100644 --- a/acvm-repo/acir/src/circuit/black_box_functions.rs +++ b/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/acvm-repo/acir/src/circuit/brillig.rs b/acvm-repo/acir/src/circuit/brillig.rs index 7f87aabf9d5..ecf6f7a9761 100644 --- a/acvm-repo/acir/src/circuit/brillig.rs +++ b/acvm-repo/acir/src/circuit/brillig.rs @@ -20,16 +20,6 @@ pub enum BrilligOutputs { Array(Vec), } -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] -pub struct Brillig { - pub inputs: Vec, - pub outputs: Vec, - /// The Brillig VM bytecode to be executed by this ACIR opcode. - pub bytecode: Vec, - /// Predicate of the Brillig execution - indicates if it should be skipped - pub predicate: Option, -} - /// This is purely a wrapper struct around a list of Brillig opcode's which represents /// a full Brillig function to be executed by the Brillig VM. /// This is stored separately on a program and accessed through a [BrilligPointer]. diff --git a/acvm-repo/acir/src/circuit/mod.rs b/acvm-repo/acir/src/circuit/mod.rs index d655d136bc8..2cdb59f1b2a 100644 --- a/acvm-repo/acir/src/circuit/mod.rs +++ b/acvm-repo/acir/src/circuit/mod.rs @@ -3,7 +3,8 @@ pub mod brillig; pub mod directives; pub mod opcodes; -use crate::native_types::Witness; +use crate::native_types::{Expression, Witness}; +use acir_field::FieldElement; pub use opcodes::Opcode; use thiserror::Error; @@ -15,7 +16,7 @@ use serde::{de::Error as DeserializationError, Deserialize, Deserializer, Serial use std::collections::BTreeSet; -use self::brillig::BrilligBytecode; +use self::{brillig::BrilligBytecode, opcodes::BlockId}; /// Specifies the maximum width of the expressions which will be constrained. /// @@ -59,18 +60,14 @@ pub struct Circuit { pub public_parameters: PublicInputs, /// The set of public inputs calculated within the circuit. pub return_values: PublicInputs, - /// Maps opcode locations to failed assertion messages. - /// These messages are embedded in the circuit to provide useful feedback to users + /// Maps opcode locations to failed assertion payloads. + /// The data in the payload is embedded in the circuit to provide useful feedback to users /// when a constraint in the circuit is not satisfied. /// // Note: This should be a BTreeMap, but serde-reflect is creating invalid // c++ code at the moment when it is, due to OpcodeLocation needing a comparison // implementation which is never generated. - // - // TODO: These are only used for constraints that are explicitly created during code generation (such as index out of bounds on slices) - // TODO: We should move towards having all the checks being evaluated in the same manner - // TODO: as runtime assert messages specified by the user. This will also be a breaking change as the `Circuit` structure will change. - pub assert_messages: Vec<(OpcodeLocation, String)>, + pub assert_messages: Vec<(OpcodeLocation, AssertionPayload)>, /// States whether the backend should use a SNARK recursion friendly prover. /// If implemented by a backend, this means that proofs generated with this circuit @@ -78,15 +75,28 @@ pub struct Circuit { pub recursive: bool, } -impl Circuit { - /// Returns the assert message associated with the provided [`OpcodeLocation`]. - /// Returns `None` if no such assert message exists. - pub fn get_assert_message(&self, opcode_location: OpcodeLocation) -> Option<&str> { - self.assert_messages - .iter() - .find(|(loc, _)| *loc == opcode_location) - .map(|(_, message)| message.as_str()) - } +/// This selector indicates that the payload is a string. +/// This is used to parse any error with a string payload directly, +/// to avoid users having to parse the error externally to the ACVM. +/// Only non-string errors need to be parsed externally to the ACVM using the circuit ABI. +pub const STRING_ERROR_SELECTOR: u64 = 0; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum ExpressionOrMemory { + Expression(Expression), + Memory(BlockId), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum AssertionPayload { + StaticString(String), + Dynamic(/* error_selector */ u64, Vec), +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum ResolvedAssertionPayload { + String(String), + Raw(/*error_selector:*/ u64, Vec), } #[derive(Debug, Copy, Clone)] diff --git a/acvm-repo/acir/src/circuit/opcodes.rs b/acvm-repo/acir/src/circuit/opcodes.rs index b0b8e286e0c..7db317c41ab 100644 --- a/acvm-repo/acir/src/circuit/opcodes.rs +++ b/acvm-repo/acir/src/circuit/opcodes.rs @@ -1,5 +1,5 @@ use super::{ - brillig::{Brillig, BrilligInputs, BrilligOutputs}, + brillig::{BrilligInputs, BrilligOutputs}, directives::Directive, }; use crate::native_types::{Expression, Witness}; @@ -20,7 +20,6 @@ pub enum Opcode { /// Often used for exposing more efficient implementations of SNARK-unfriendly computations. BlackBoxFuncCall(BlackBoxFuncCall), Directive(Directive), - Brillig(Brillig), /// Atomic operation on a block of memory MemoryOp { block_id: BlockId, @@ -88,12 +87,6 @@ impl std::fmt::Display for Opcode { b.last().unwrap().witness_index(), ) } - Opcode::Brillig(brillig) => { - write!(f, "BRILLIG: ")?; - writeln!(f, "inputs: {:?}", brillig.inputs)?; - writeln!(f, "outputs: {:?}", brillig.outputs)?; - writeln!(f, "{:?}", brillig.bytecode) - } Opcode::MemoryOp { block_id, op, predicate } => { write!(f, "MEM ")?; if let Some(pred) = predicate { diff --git a/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs b/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs index 405cd0cef00..5715019937c 100644 --- a/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs +++ b/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/acvm-repo/acir/src/lib.rs b/acvm-repo/acir/src/lib.rs index d14159f34a1..24f27aae06f 100644 --- a/acvm-repo/acir/src/lib.rs +++ b/acvm-repo/acir/src/lib.rs @@ -42,7 +42,8 @@ mod reflection { brillig::{BrilligInputs, BrilligOutputs}, directives::Directive, opcodes::BlackBoxFuncCall, - Circuit, ExpressionWidth, Opcode, OpcodeLocation, Program, + AssertionPayload, Circuit, ExpressionOrMemory, ExpressionWidth, Opcode, OpcodeLocation, + Program, }, native_types::{Witness, WitnessMap, WitnessStack}, }; @@ -74,6 +75,8 @@ mod reflection { tracer.trace_simple_type::().unwrap(); tracer.trace_simple_type::().unwrap(); tracer.trace_simple_type::().unwrap(); + tracer.trace_simple_type::().unwrap(); + tracer.trace_simple_type::().unwrap(); let registry = tracer.registry().unwrap(); diff --git a/acvm-repo/acir/tests/test_program_serialization.rs b/acvm-repo/acir/tests/test_program_serialization.rs index fb924a7437d..2ad082410a1 100644 --- a/acvm-repo/acir/tests/test_program_serialization.rs +++ b/acvm-repo/acir/tests/test_program_serialization.rs @@ -13,7 +13,7 @@ use std::collections::BTreeSet; use acir::{ circuit::{ - brillig::{Brillig, BrilligInputs, BrilligOutputs}, + brillig::{BrilligBytecode, BrilligInputs, BrilligOutputs}, opcodes::{BlackBoxFuncCall, BlockId, FunctionInput, MemOp}, Circuit, Opcode, Program, PublicInputs, }, @@ -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 { @@ -176,14 +208,7 @@ fn simple_brillig_foreign_call() { let w_input = Witness(1); let w_inverted = Witness(2); - let brillig_data = Brillig { - inputs: vec![ - BrilligInputs::Single(w_input.into()), // Input Register 0, - ], - // This tells the BrilligSolver which witnesses its output values correspond to - outputs: vec![ - BrilligOutputs::Simple(w_inverted), // Output Register 1 - ], + let brillig_bytecode = BrilligBytecode { bytecode: vec![ brillig::Opcode::CalldataCopy { destination_address: MemoryAddress(0), @@ -199,27 +224,38 @@ fn simple_brillig_foreign_call() { }, brillig::Opcode::Stop { return_data_offset: 0, return_data_size: 1 }, ], - predicate: None, }; - let opcodes = vec![Opcode::Brillig(brillig_data)]; + let opcodes = vec![Opcode::BrilligCall { + id: 0, + inputs: vec![ + BrilligInputs::Single(w_input.into()), // Input Register 0, + ], + // This tells the BrilligSolver which witnesses its output values correspond to + outputs: vec![ + BrilligOutputs::Simple(w_inverted), // Output Register 1 + ], + predicate: None, + }]; + let circuit = Circuit { current_witness_index: 8, opcodes, private_parameters: BTreeSet::from([Witness(1), Witness(2)]), ..Circuit::default() }; - let program = Program { functions: vec![circuit], unconstrained_functions: vec![] }; + let program = + Program { functions: vec![circuit], unconstrained_functions: vec![brillig_bytecode] }; let bytes = Program::serialize_program(&program); let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 144, 61, 10, 192, 32, 12, 133, 19, 11, 165, 116, - 235, 77, 236, 13, 122, 153, 14, 93, 58, 136, 120, 124, 241, 47, 129, 12, 42, 130, 126, 16, - 18, 146, 16, 222, 11, 66, 225, 136, 129, 84, 111, 162, 150, 112, 239, 161, 172, 231, 184, - 113, 221, 45, 45, 245, 42, 242, 144, 216, 43, 250, 153, 83, 204, 191, 223, 189, 198, 246, - 92, 39, 60, 244, 63, 195, 59, 87, 99, 150, 165, 113, 83, 193, 0, 1, 19, 247, 29, 5, 160, 1, - 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 144, 193, 10, 192, 32, 8, 134, 117, 99, 99, 236, + 182, 55, 105, 111, 176, 151, 217, 161, 75, 135, 136, 30, 63, 42, 82, 144, 8, 47, 245, 65, + 252, 230, 47, 162, 34, 52, 174, 242, 144, 226, 131, 148, 255, 18, 206, 125, 164, 102, 142, + 23, 215, 245, 50, 114, 222, 173, 15, 80, 38, 65, 217, 108, 39, 61, 7, 30, 115, 11, 223, + 186, 248, 251, 160, 221, 170, 146, 64, 191, 39, 215, 60, 3, 47, 3, 99, 171, 188, 84, 164, + 1, 0, 0, ]; assert_eq!(bytes, expected_serialization) @@ -239,27 +275,7 @@ fn complex_brillig_foreign_call() { let a_plus_b_plus_c = Witness(7); let a_plus_b_plus_c_times_2 = Witness(8); - let brillig_data = Brillig { - inputs: vec![ - // Input 0,1,2 - BrilligInputs::Array(vec![ - Expression::from(a), - Expression::from(b), - Expression::from(c), - ]), - // Input 3 - BrilligInputs::Single(Expression { - mul_terms: vec![], - linear_combinations: vec![(fe_1, a), (fe_1, b), (fe_1, c)], - q_c: fe_0, - }), - ], - // This tells the BrilligSolver which witnesses its output values correspond to - outputs: vec![ - BrilligOutputs::Array(vec![a_times_2, b_times_3, c_times_4]), // Output 0,1,2 - BrilligOutputs::Simple(a_plus_b_plus_c), // Output 3 - BrilligOutputs::Simple(a_plus_b_plus_c_times_2), // Output 4 - ], + let brillig_bytecode = BrilligBytecode { bytecode: vec![ brillig::Opcode::CalldataCopy { destination_address: MemoryAddress(32), @@ -300,30 +316,54 @@ fn complex_brillig_foreign_call() { }, brillig::Opcode::Stop { return_data_offset: 32, return_data_size: 5 }, ], - predicate: None, }; - let opcodes = vec![Opcode::Brillig(brillig_data)]; + let opcodes = vec![Opcode::BrilligCall { + id: 0, + inputs: vec![ + // Input 0,1,2 + BrilligInputs::Array(vec![ + Expression::from(a), + Expression::from(b), + Expression::from(c), + ]), + // Input 3 + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, a), (fe_1, b), (fe_1, c)], + q_c: fe_0, + }), + ], + // This tells the BrilligSolver which witnesses its output values correspond to + outputs: vec![ + BrilligOutputs::Array(vec![a_times_2, b_times_3, c_times_4]), // Output 0,1,2 + BrilligOutputs::Simple(a_plus_b_plus_c), // Output 3 + BrilligOutputs::Simple(a_plus_b_plus_c_times_2), // Output 4 + ], + predicate: None, + }]; + let circuit = Circuit { current_witness_index: 8, opcodes, private_parameters: BTreeSet::from([Witness(1), Witness(2), Witness(3)]), ..Circuit::default() }; - let program = Program { functions: vec![circuit], unconstrained_functions: vec![] }; + let program = + Program { functions: vec![circuit], unconstrained_functions: vec![brillig_bytecode] }; let bytes = Program::serialize_program(&program); let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 84, 75, 10, 132, 48, 12, 77, 218, 209, 145, 217, - 205, 13, 6, 198, 3, 84, 79, 224, 93, 196, 157, 162, 75, 79, 47, 22, 124, 197, 16, 186, 17, - 43, 104, 32, 36, 109, 126, 143, 36, 45, 211, 70, 133, 103, 134, 110, 61, 27, 232, 140, 179, - 164, 224, 215, 64, 186, 115, 84, 113, 186, 92, 238, 42, 140, 230, 1, 24, 237, 5, 24, 195, - 62, 220, 116, 222, 41, 231, 146, 180, 127, 54, 242, 126, 94, 158, 51, 207, 57, 206, 111, - 200, 2, 247, 4, 219, 79, 245, 157, 132, 31, 137, 89, 52, 73, 176, 214, 46, 167, 125, 23, - 89, 213, 254, 8, 156, 237, 56, 76, 125, 55, 91, 229, 170, 161, 254, 133, 94, 42, 59, 171, - 184, 69, 197, 46, 66, 202, 47, 40, 86, 39, 220, 155, 3, 185, 191, 180, 183, 55, 163, 72, - 98, 70, 66, 221, 251, 40, 173, 255, 35, 68, 62, 61, 5, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 84, 75, 10, 131, 64, 12, 77, 102, 90, 43, 221, 245, + 6, 133, 246, 0, 211, 158, 192, 187, 136, 59, 69, 151, 158, 94, 116, 48, 131, 241, 233, 70, + 28, 65, 3, 195, 155, 79, 62, 47, 9, 25, 166, 81, 210, 97, 177, 236, 239, 130, 70, 208, 223, + 91, 154, 75, 208, 205, 4, 221, 62, 249, 113, 60, 95, 238, 40, 142, 230, 2, 28, 237, 1, 28, + 73, 245, 255, 132, 253, 142, 217, 151, 168, 245, 179, 43, 243, 115, 163, 113, 190, 18, 57, + 63, 4, 83, 44, 180, 55, 50, 180, 28, 188, 153, 224, 196, 122, 175, 111, 112, 68, 24, 65, + 50, 204, 162, 100, 249, 119, 137, 226, 193, 16, 251, 169, 50, 204, 235, 170, 41, 139, 214, + 130, 42, 82, 253, 168, 253, 23, 222, 25, 236, 58, 176, 237, 20, 234, 207, 107, 45, 78, 184, + 55, 27, 124, 191, 104, 42, 111, 40, 121, 15, 94, 163, 77, 128, 65, 5, 0, 0, ]; assert_eq!(bytes, expected_serialization) @@ -357,11 +397,11 @@ fn memory_op_circuit() { let bytes = Program::serialize_program(&program); let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 81, 57, 14, 0, 32, 8, 147, 195, 255, 224, 15, 252, - 255, 171, 212, 200, 208, 129, 77, 24, 108, 66, 90, 150, 166, 20, 106, 23, 125, 143, 128, - 62, 96, 103, 114, 173, 45, 198, 116, 182, 55, 140, 106, 95, 74, 246, 149, 60, 47, 171, 46, - 215, 126, 43, 87, 179, 111, 23, 8, 202, 176, 99, 248, 240, 9, 11, 137, 33, 212, 110, 35, 3, - 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 82, 65, 10, 0, 32, 8, 203, 180, 255, 216, 15, 250, + 255, 171, 10, 154, 16, 210, 45, 61, 52, 144, 13, 132, 49, 135, 84, 54, 218, 26, 134, 22, + 112, 5, 19, 180, 237, 61, 6, 88, 223, 208, 179, 125, 41, 216, 151, 227, 188, 52, 187, 92, + 253, 173, 92, 137, 190, 157, 143, 160, 254, 155, 45, 188, 148, 11, 38, 213, 237, 188, 16, + 35, 3, 0, 0, ]; assert_eq!(bytes, expected_serialization) @@ -460,15 +500,15 @@ fn nested_acir_call_circuit() { let bytes = Program::serialize_program(&program); let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 205, 146, 65, 10, 3, 33, 12, 69, 163, 46, 230, 58, 137, - 209, 49, 238, 122, 149, 74, 157, 251, 31, 161, 83, 154, 161, 86, 132, 89, 212, 194, 124, - 248, 24, 36, 132, 228, 241, 29, 188, 229, 212, 47, 45, 187, 205, 110, 11, 31, 25, 53, 28, - 255, 103, 77, 14, 58, 29, 141, 55, 125, 241, 55, 145, 109, 102, 49, 174, 33, 212, 228, 43, - 49, 221, 209, 231, 34, 17, 67, 44, 171, 144, 80, 148, 248, 240, 194, 92, 37, 72, 202, 37, - 39, 204, 20, 184, 210, 22, 51, 111, 58, 204, 205, 219, 11, 161, 129, 208, 214, 6, 6, 114, - 29, 193, 127, 193, 130, 137, 176, 236, 188, 189, 252, 162, 183, 218, 230, 238, 97, 138, - 250, 152, 245, 245, 87, 220, 12, 140, 113, 95, 153, 170, 129, 185, 17, 60, 3, 54, 212, 19, - 104, 145, 195, 151, 14, 4, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 205, 146, 97, 10, 195, 32, 12, 133, 163, 66, 207, 147, + 24, 173, 241, 223, 174, 50, 153, 189, 255, 17, 214, 177, 148, 57, 17, 250, 99, 14, 250, + 224, 97, 144, 16, 146, 143, 231, 224, 45, 167, 126, 105, 217, 109, 118, 91, 248, 200, 168, + 225, 248, 63, 107, 114, 208, 233, 104, 188, 233, 139, 191, 137, 108, 51, 139, 113, 13, 161, + 38, 95, 137, 233, 142, 62, 23, 137, 24, 98, 89, 133, 132, 162, 196, 135, 23, 230, 42, 65, + 82, 46, 57, 97, 166, 192, 149, 182, 152, 121, 211, 97, 110, 222, 94, 8, 13, 132, 182, 54, + 48, 144, 235, 8, 254, 11, 22, 76, 132, 101, 231, 237, 229, 23, 189, 213, 54, 119, 15, 83, + 212, 199, 172, 175, 191, 226, 102, 96, 140, 251, 202, 84, 13, 204, 141, 224, 25, 176, 161, + 158, 53, 121, 144, 73, 14, 4, 0, 0, ]; assert_eq!(bytes, expected_serialization); } diff --git a/acvm-repo/acvm/src/compiler/mod.rs b/acvm-repo/acvm/src/compiler/mod.rs index 6543c70958b..436db648ea8 100644 --- a/acvm-repo/acvm/src/compiler/mod.rs +++ b/acvm-repo/acvm/src/compiler/mod.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use acir::circuit::{Circuit, ExpressionWidth, OpcodeLocation}; +use acir::circuit::{AssertionPayload, Circuit, ExpressionWidth, OpcodeLocation}; // The various passes that we can use over ACIR mod optimizers; @@ -54,9 +54,9 @@ impl AcirTransformationMap { } fn transform_assert_messages( - assert_messages: Vec<(OpcodeLocation, String)>, + assert_messages: Vec<(OpcodeLocation, AssertionPayload)>, map: &AcirTransformationMap, -) -> Vec<(OpcodeLocation, String)> { +) -> Vec<(OpcodeLocation, AssertionPayload)> { assert_messages .into_iter() .flat_map(|(location, message)| { diff --git a/acvm-repo/acvm/src/compiler/optimizers/mod.rs b/acvm-repo/acvm/src/compiler/optimizers/mod.rs index 04d3f99a408..dfe348d4ff5 100644 --- a/acvm-repo/acvm/src/compiler/optimizers/mod.rs +++ b/acvm-repo/acvm/src/compiler/optimizers/mod.rs @@ -32,7 +32,7 @@ pub(super) fn optimize_internal(acir: Circuit) -> (Circuit, Vec) { // by applying the modifications done to the circuit opcodes and also to the opcode_positions (delete and insert) let acir_opcode_positions = (0..acir.opcodes.len()).collect(); - if acir.opcodes.len() == 1 && matches!(acir.opcodes[0], Opcode::Brillig(_)) { + if acir.opcodes.len() == 1 && matches!(acir.opcodes[0], Opcode::BrilligCall { .. }) { info!("Program is fully unconstrained, skipping optimization pass"); return (acir, acir_opcode_positions); } diff --git a/acvm-repo/acvm/src/compiler/transformers/mod.rs b/acvm-repo/acvm/src/compiler/transformers/mod.rs index d13fac1672a..0099519e4b6 100644 --- a/acvm-repo/acvm/src/compiler/transformers/mod.rs +++ b/acvm-repo/acvm/src/compiler/transformers/mod.rs @@ -128,20 +128,6 @@ pub(super) fn transform_internal( new_acir_opcode_positions.push(acir_opcode_positions[index]); transformed_opcodes.push(opcode); } - Opcode::Brillig(ref brillig) => { - for output in &brillig.outputs { - match output { - BrilligOutputs::Simple(w) => transformer.mark_solvable(*w), - BrilligOutputs::Array(v) => { - for witness in v { - transformer.mark_solvable(*witness); - } - } - } - } - new_acir_opcode_positions.push(acir_opcode_positions[index]); - transformed_opcodes.push(opcode); - } Opcode::BrilligCall { ref outputs, .. } => { for output in outputs { match output { diff --git a/acvm-repo/acvm/src/pwg/arithmetic.rs b/acvm-repo/acvm/src/pwg/arithmetic.rs index dc9e13d44b6..b971e4a0efb 100644 --- a/acvm-repo/acvm/src/pwg/arithmetic.rs +++ b/acvm-repo/acvm/src/pwg/arithmetic.rs @@ -53,6 +53,7 @@ impl ExpressionSolver { if !total_sum.is_zero() { Err(OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Unresolved, + payload: None, }) } else { Ok(()) @@ -81,6 +82,7 @@ impl ExpressionSolver { if !total_sum.is_zero() { Err(OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Unresolved, + payload: None, }) } else { Ok(()) @@ -96,6 +98,7 @@ impl ExpressionSolver { if !(a + b + opcode.q_c).is_zero() { Err(OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Unresolved, + payload: None, }) } else { Ok(()) @@ -113,6 +116,7 @@ impl ExpressionSolver { if !total_sum.is_zero() { Err(OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Unresolved, + payload: None, }) } else { Ok(()) diff --git a/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs b/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs index c5bfd1d5646..79e33ae8de5 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs +++ b/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/acvm-repo/acvm/src/pwg/blackbox/mod.rs b/acvm-repo/acvm/src/pwg/blackbox/mod.rs index 2753c7baaaa..2487d511b50 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/mod.rs +++ b/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/acvm-repo/acvm/src/pwg/blackbox/range.rs b/acvm-repo/acvm/src/pwg/blackbox/range.rs index 2afe820b636..aac50b32fc8 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/range.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/range.rs @@ -12,6 +12,7 @@ pub(crate) fn solve_range_opcode( if w_value.num_bits() > input.num_bits { return Err(OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Unresolved, + payload: None, }); } Ok(()) diff --git a/acvm-repo/acvm/src/pwg/brillig.rs b/acvm-repo/acvm/src/pwg/brillig.rs index 10178465d58..9cf87455acb 100644 --- a/acvm-repo/acvm/src/pwg/brillig.rs +++ b/acvm-repo/acvm/src/pwg/brillig.rs @@ -3,9 +3,9 @@ use std::collections::HashMap; use acir::{ brillig::{ForeignCallParam, ForeignCallResult, Opcode as BrilligOpcode}, circuit::{ - brillig::{Brillig, BrilligInputs, BrilligOutputs}, + brillig::{BrilligInputs, BrilligOutputs}, opcodes::BlockId, - OpcodeLocation, + OpcodeLocation, ResolvedAssertionPayload, STRING_ERROR_SELECTOR, }, native_types::WitnessMap, FieldElement, @@ -50,26 +50,6 @@ impl<'b, B: BlackBoxFunctionSolver> BrilligSolver<'b, B> { Ok(()) } - // TODO: Delete this old method once `Brillig` is deleted - /// Constructs a solver for a Brillig block given the bytecode and initial - /// witness. - pub(crate) fn new( - initial_witness: &WitnessMap, - memory: &HashMap, - brillig: &'b Brillig, - bb_solver: &'b B, - acir_index: usize, - ) -> Result { - let vm = Self::setup_brillig_vm( - initial_witness, - memory, - &brillig.inputs, - &brillig.bytecode, - bb_solver, - )?; - Ok(Self { vm, acir_index }) - } - /// Constructs a solver for a Brillig block given the bytecode and initial /// witness. pub(crate) fn new_call( @@ -180,32 +160,51 @@ impl<'b, B: BlackBoxFunctionSolver> BrilligSolver<'b, B> { VMStatus::Finished { .. } => Ok(BrilligSolverStatus::Finished), VMStatus::InProgress => Ok(BrilligSolverStatus::InProgress), VMStatus::Failure { reason, call_stack } => { - let message = match reason { - FailureReason::RuntimeError { message } => Some(message), + let payload = match reason { + FailureReason::RuntimeError { message } => { + Some(ResolvedAssertionPayload::String(message)) + } FailureReason::Trap { revert_data_offset, revert_data_size } => { // Since noir can only revert with strings currently, we can parse return data as a string if revert_data_size == 0 { None } else { let memory = self.vm.get_memory(); - let bytes = memory + let mut revert_values_iter = memory [revert_data_offset..(revert_data_offset + revert_data_size)] - .iter() - .map(|memory_value| { - memory_value - .try_into() - .expect("Assert message character is not a byte") - }) - .collect(); - Some( - String::from_utf8(bytes) - .expect("Assert message is not valid UTF-8"), - ) + .iter(); + let error_selector = revert_values_iter + .next() + .expect("Incorrect revert data size") + .try_into() + .expect("Error selector is not u64"); + + match error_selector { + STRING_ERROR_SELECTOR => { + // If the error selector is 0, it means the error is a string + let string = revert_values_iter + .map(|memory_value| { + let as_u8: u8 = memory_value + .try_into() + .expect("String item is not u8"); + as_u8 as char + }) + .collect(); + Some(ResolvedAssertionPayload::String(string)) + } + _ => { + // If the error selector is not 0, it means the error is a custom error + Some(ResolvedAssertionPayload::Raw( + error_selector, + revert_values_iter.map(|value| value.to_field()).collect(), + )) + } + } } } }; Err(OpcodeResolutionError::BrilligFunctionFailed { - message, + payload, call_stack: call_stack .iter() .map(|brillig_index| OpcodeLocation::Brillig { diff --git a/acvm-repo/acvm/src/pwg/directives/mod.rs b/acvm-repo/acvm/src/pwg/directives/mod.rs index ee544521fc7..db79379a374 100644 --- a/acvm-repo/acvm/src/pwg/directives/mod.rs +++ b/acvm-repo/acvm/src/pwg/directives/mod.rs @@ -26,6 +26,7 @@ pub(crate) fn solve_directives( if b.len() < decomposed_integer.len() { return Err(OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Unresolved, + payload: None, }); } diff --git a/acvm-repo/acvm/src/pwg/mod.rs b/acvm-repo/acvm/src/pwg/mod.rs index 3d3c52c661b..1f4867f7656 100644 --- a/acvm-repo/acvm/src/pwg/mod.rs +++ b/acvm-repo/acvm/src/pwg/mod.rs @@ -4,7 +4,10 @@ use std::collections::HashMap; use acir::{ brillig::ForeignCallResult, - circuit::{brillig::BrilligBytecode, opcodes::BlockId, Opcode, OpcodeLocation}, + circuit::{ + brillig::BrilligBytecode, opcodes::BlockId, AssertionPayload, ExpressionOrMemory, Opcode, + OpcodeLocation, ResolvedAssertionPayload, STRING_ERROR_SELECTOR, + }, native_types::{Expression, Witness, WitnessMap}, BlackBoxFunc, FieldElement, }; @@ -117,13 +120,19 @@ pub enum OpcodeResolutionError { #[error("Cannot solve opcode: {0}")] OpcodeNotSolvable(#[from] OpcodeNotSolvable), #[error("Cannot satisfy constraint")] - UnsatisfiedConstrain { opcode_location: ErrorLocation }, + UnsatisfiedConstrain { + opcode_location: ErrorLocation, + payload: Option, + }, #[error("Index out of bounds, array has size {array_size:?}, but index was {index:?}")] IndexOutOfBounds { opcode_location: ErrorLocation, index: u32, array_size: u32 }, #[error("Failed to solve blackbox function: {0}, reason: {1}")] BlackBoxFunctionFailed(BlackBoxFunc, String), - #[error("Failed to solve brillig function{}", .message.as_ref().map(|m| format!(", reason: {}", m)).unwrap_or_default())] - BrilligFunctionFailed { message: Option, call_stack: Vec }, + #[error("Failed to solve brillig function")] + BrilligFunctionFailed { + call_stack: Vec, + payload: Option, + }, #[error("Attempted to call `main` with a `Call` opcode")] AcirMainCallAttempted { opcode_location: ErrorLocation }, #[error("{results_size:?} result values were provided for {outputs_size:?} call output witnesses, most likely due to bad ACIR codegen")] @@ -168,6 +177,8 @@ pub struct ACVM<'a, B: BlackBoxFunctionSolver> { // Each unconstrained function referenced in the program unconstrained_functions: &'a [BrilligBytecode], + + assertion_payloads: &'a [(OpcodeLocation, AssertionPayload)], } impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { @@ -176,6 +187,7 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { opcodes: &'a [Opcode], initial_witness: WitnessMap, unconstrained_functions: &'a [BrilligBytecode], + assertion_payloads: &'a [(OpcodeLocation, AssertionPayload)], ) -> Self { let status = if opcodes.is_empty() { ACVMStatus::Solved } else { ACVMStatus::InProgress }; ACVM { @@ -190,6 +202,7 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { acir_call_counter: 0, acir_call_results: Vec::default(), unconstrained_functions, + assertion_payloads, } } @@ -329,10 +342,6 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { let solver = self.block_solvers.entry(*block_id).or_default(); solver.solve_memory_op(op, &mut self.witness_map, predicate) } - Opcode::Brillig(_) => match self.solve_brillig_opcode() { - Ok(Some(foreign_call)) => return self.wait_for_foreign_call(foreign_call), - res => res.map(|_| ()), - }, Opcode::BrilligCall { .. } => match self.solve_brillig_call_opcode() { Ok(Some(foreign_call)) => return self.wait_for_foreign_call(foreign_call), res => res.map(|_| ()), @@ -366,14 +375,19 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { OpcodeResolutionError::IndexOutOfBounds { opcode_location: opcode_index, .. - } - | OpcodeResolutionError::UnsatisfiedConstrain { - opcode_location: opcode_index, } => { *opcode_index = ErrorLocation::Resolved(OpcodeLocation::Acir( self.instruction_pointer(), )); } + OpcodeResolutionError::UnsatisfiedConstrain { + opcode_location: opcode_index, + payload: assertion_payload, + } => { + let location = OpcodeLocation::Acir(self.instruction_pointer()); + *opcode_index = ErrorLocation::Resolved(location); + *assertion_payload = self.extract_assertion_payload(location); + } // All other errors are thrown normally. _ => (), }; @@ -382,44 +396,57 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { } } - fn solve_brillig_opcode( - &mut self, - ) -> Result, OpcodeResolutionError> { - let Opcode::Brillig(brillig) = &self.opcodes[self.instruction_pointer] else { - unreachable!("Not executing a Brillig opcode"); - }; - - let witness = &mut self.witness_map; - if is_predicate_false(witness, &brillig.predicate)? { - return BrilligSolver::::zero_out_brillig_outputs(witness, &brillig.outputs) - .map(|_| None); - } - - // If we're resuming execution after resolving a foreign call then - // there will be a cached `BrilligSolver` to avoid recomputation. - let mut solver: BrilligSolver<'_, B> = match self.brillig_solver.take() { - Some(solver) => solver, - None => BrilligSolver::new( - witness, - &self.block_solvers, - brillig, - self.backend, - self.instruction_pointer, - )?, - }; - match solver.solve()? { - BrilligSolverStatus::ForeignCallWait(foreign_call) => { - // Cache the current state of the solver - self.brillig_solver = Some(solver); - Ok(Some(foreign_call)) - } - BrilligSolverStatus::InProgress => { - unreachable!("Brillig solver still in progress") + fn extract_assertion_payload( + &self, + location: OpcodeLocation, + ) -> Option { + let (_, found_assertion_payload) = + self.assertion_payloads.iter().find(|(loc, _)| location == *loc)?; + match found_assertion_payload { + AssertionPayload::StaticString(string) => { + Some(ResolvedAssertionPayload::String(string.clone())) } - BrilligSolverStatus::Finished => { - // Write execution outputs - solver.finalize(witness, &brillig.outputs)?; - Ok(None) + AssertionPayload::Dynamic(error_selector, expression) => { + let mut fields = vec![]; + for expr in expression { + match expr { + ExpressionOrMemory::Expression(expr) => { + let value = get_value(expr, &self.witness_map).ok()?; + fields.push(value); + } + ExpressionOrMemory::Memory(block_id) => { + let memory_block = self.block_solvers.get(block_id)?; + fields.extend((0..memory_block.block_len).map(|memory_index| { + *memory_block + .block_value + .get(&memory_index) + .expect("All memory is initialized on creation") + })); + } + } + } + + Some(match *error_selector { + STRING_ERROR_SELECTOR => { + // If the error selector is 0, it means the error is a string + let string = fields + .iter() + .map(|field| { + let as_u8: u8 = field + .try_to_u64() + .expect("String character doesn't fit in u64") + .try_into() + .expect("String character doesn't fit in u8"); + as_u8 as char + }) + .collect(); + ResolvedAssertionPayload::String(string) + } + _ => { + // If the error selector is not 0, it means the error is a custom error + ResolvedAssertionPayload::Raw(*error_selector, fields) + } + }) } } } @@ -433,9 +460,9 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { unreachable!("Not executing a BrilligCall opcode"); }; - let witness = &mut self.witness_map; - if is_predicate_false(witness, predicate)? { - return BrilligSolver::::zero_out_brillig_outputs(witness, outputs).map(|_| None); + if is_predicate_false(&self.witness_map, predicate)? { + return BrilligSolver::::zero_out_brillig_outputs(&mut self.witness_map, outputs) + .map(|_| None); } // If we're resuming execution after resolving a foreign call then @@ -443,7 +470,7 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { let mut solver: BrilligSolver<'_, B> = match self.brillig_solver.take() { Some(solver) => solver, None => BrilligSolver::new_call( - witness, + &self.witness_map, &self.block_solvers, inputs, &self.unconstrained_functions[*id as usize].bytecode, @@ -451,7 +478,10 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { self.instruction_pointer, )?, }; - match solver.solve()? { + + let result = solver.solve().map_err(|err| self.map_brillig_error(err))?; + + match result { BrilligSolverStatus::ForeignCallWait(foreign_call) => { // Cache the current state of the solver self.brillig_solver = Some(solver); @@ -462,12 +492,37 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { } BrilligSolverStatus::Finished => { // Write execution outputs - solver.finalize(witness, outputs)?; + solver.finalize(&mut self.witness_map, outputs)?; Ok(None) } } } + fn map_brillig_error(&self, mut err: OpcodeResolutionError) -> OpcodeResolutionError { + match &mut err { + OpcodeResolutionError::BrilligFunctionFailed { call_stack, payload } => { + // Some brillig errors have static strings as payloads, we can resolve them here + let last_location = + call_stack.last().expect("Call stacks should have at least one item"); + let assertion_descriptor = + self.assertion_payloads.iter().find_map(|(loc, payload)| { + if loc == last_location { + Some(payload) + } else { + None + } + }); + + if let Some(AssertionPayload::StaticString(string)) = assertion_descriptor { + *payload = Some(ResolvedAssertionPayload::String(string.clone())); + } + + err + } + _ => err, + } + } + pub fn step_into_brillig(&mut self) -> StepResult<'a, B> { let Opcode::BrilligCall { id, inputs, outputs, predicate } = &self.opcodes[self.instruction_pointer] @@ -605,6 +660,7 @@ pub fn insert_value( if old_value != value_to_insert { return Err(OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Unresolved, + payload: None, }); } diff --git a/acvm-repo/acvm/tests/solver.rs b/acvm-repo/acvm/tests/solver.rs index f009e2c05b8..df61083eee4 100644 --- a/acvm-repo/acvm/tests/solver.rs +++ b/acvm-repo/acvm/tests/solver.rs @@ -1,9 +1,9 @@ use std::collections::BTreeMap; use acir::{ - brillig::{BinaryFieldOp, MemoryAddress, Opcode as BrilligOpcode, ValueOrArray}, + brillig::{BinaryFieldOp, HeapArray, MemoryAddress, Opcode as BrilligOpcode, ValueOrArray}, circuit::{ - brillig::{Brillig, BrilligInputs, BrilligOutputs}, + brillig::{BrilligBytecode, BrilligInputs, BrilligOutputs}, opcodes::{BlockId, MemOp}, Opcode, OpcodeLocation, }, @@ -43,44 +43,26 @@ fn inversion_brillig_oracle_equivalence() { destination: MemoryAddress::from(2), }; - let brillig_data = Brillig { - inputs: vec![ - BrilligInputs::Single(Expression { - // Input Register 0 - mul_terms: vec![], - linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], - q_c: fe_0, - }), - BrilligInputs::Single(Expression::default()), // Input Register 1 - ], - // This tells the BrilligSolver which witnesses its output values correspond to - outputs: vec![ - BrilligOutputs::Simple(w_x_plus_y), // Output Register 0 - from input - BrilligOutputs::Simple(w_oracle), // Output Register 1 - BrilligOutputs::Simple(w_equal_res), // Output Register 2 - ], - bytecode: vec![ - BrilligOpcode::CalldataCopy { - destination_address: MemoryAddress(0), - size: 2, - offset: 0, - }, - equal_opcode, - // Oracles are named 'foreign calls' in brillig - BrilligOpcode::ForeignCall { - function: "invert".into(), - destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], - destination_value_types: vec![HeapValueType::field()], - inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))], - input_value_types: vec![HeapValueType::field()], - }, - BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 3 }, - ], - predicate: None, - }; - let opcodes = vec![ - Opcode::Brillig(brillig_data), + Opcode::BrilligCall { + id: 0, + inputs: vec![ + BrilligInputs::Single(Expression { + // Input Register 0 + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], + q_c: fe_0, + }), + BrilligInputs::Single(Expression::default()), // Input Register 1 + ], + // This tells the BrilligSolver which witnesses its output values correspond to + outputs: vec![ + BrilligOutputs::Simple(w_x_plus_y), // Output Register 0 - from input + BrilligOutputs::Simple(w_oracle), // Output Register 1 + BrilligOutputs::Simple(w_equal_res), // Output Register 2 + ], + predicate: None, + }, Opcode::AssertZero(Expression { mul_terms: vec![], linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)], @@ -99,14 +81,39 @@ fn inversion_brillig_oracle_equivalence() { }), ]; + let brillig_bytecode = BrilligBytecode { + bytecode: vec![ + BrilligOpcode::CalldataCopy { + destination_address: MemoryAddress(0), + size: 2, + offset: 0, + }, + equal_opcode, + // Oracles are named 'foreign calls' in brillig + BrilligOpcode::ForeignCall { + function: "invert".into(), + destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], + destination_value_types: vec![HeapValueType::field()], + inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))], + input_value_types: vec![HeapValueType::field()], + }, + BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 3 }, + ], + }; + let witness_assignments = BTreeMap::from([ (Witness(1), FieldElement::from(2u128)), (Witness(2), FieldElement::from(3u128)), ]) .into(); - let unconstrained_functions = vec![]; - let mut acvm = - ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); + let unconstrained_functions = vec![brillig_bytecode]; + let mut acvm = ACVM::new( + &StubbedBlackBoxSolver, + &opcodes, + witness_assignments, + &unconstrained_functions, + &[], + ); // use the partial witness generation solver with our acir program let solver_status = acvm.solve(); @@ -165,29 +172,52 @@ fn double_inversion_brillig_oracle() { destination: MemoryAddress::from(4), }; - let brillig_data = Brillig { - inputs: vec![ - BrilligInputs::Single(Expression { - // Input Register 0 - mul_terms: vec![], - linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], - q_c: fe_0, - }), - BrilligInputs::Single(Expression::default()), // Input Register 1 - BrilligInputs::Single(Expression { - // Input Register 2 - mul_terms: vec![], - linear_combinations: vec![(fe_1, w_i), (fe_1, w_j)], - q_c: fe_0, - }), - ], - outputs: vec![ - BrilligOutputs::Simple(w_x_plus_y), // Output Register 0 - from input - BrilligOutputs::Simple(w_oracle), // Output Register 1 - BrilligOutputs::Simple(w_i_plus_j), // Output Register 2 - from input - BrilligOutputs::Simple(w_ij_oracle), // Output Register 3 - BrilligOutputs::Simple(w_equal_res), // Output Register 4 - ], + let opcodes = vec![ + Opcode::BrilligCall { + id: 0, + inputs: vec![ + BrilligInputs::Single(Expression { + // Input Register 0 + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], + q_c: fe_0, + }), + BrilligInputs::Single(Expression::default()), // Input Register 1 + BrilligInputs::Single(Expression { + // Input Register 2 + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_i), (fe_1, w_j)], + q_c: fe_0, + }), + ], + outputs: vec![ + BrilligOutputs::Simple(w_x_plus_y), // Output Register 0 - from input + BrilligOutputs::Simple(w_oracle), // Output Register 1 + BrilligOutputs::Simple(w_i_plus_j), // Output Register 2 - from input + BrilligOutputs::Simple(w_ij_oracle), // Output Register 3 + BrilligOutputs::Simple(w_equal_res), // Output Register 4 + ], + predicate: None, + }, + Opcode::AssertZero(Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)], + q_c: fe_0, + }), + // Opcode::Directive(Directive::Invert { x: w_z, result: w_z_inverse }), + Opcode::AssertZero(Expression { + mul_terms: vec![(fe_1, w_z, w_z_inverse)], + linear_combinations: vec![], + q_c: -fe_1, + }), + Opcode::AssertZero(Expression { + mul_terms: vec![], + linear_combinations: vec![(-fe_1, w_oracle), (fe_1, w_z_inverse)], + q_c: fe_0, + }), + ]; + + let brillig_bytecode = BrilligBytecode { bytecode: vec![ BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), @@ -212,29 +242,8 @@ fn double_inversion_brillig_oracle() { }, BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 5 }, ], - predicate: None, }; - let opcodes = vec![ - Opcode::Brillig(brillig_data), - Opcode::AssertZero(Expression { - mul_terms: vec![], - linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)], - q_c: fe_0, - }), - // Opcode::Directive(Directive::Invert { x: w_z, result: w_z_inverse }), - Opcode::AssertZero(Expression { - mul_terms: vec![(fe_1, w_z, w_z_inverse)], - linear_combinations: vec![], - q_c: -fe_1, - }), - Opcode::AssertZero(Expression { - mul_terms: vec![], - linear_combinations: vec![(-fe_1, w_oracle), (fe_1, w_z_inverse)], - q_c: fe_0, - }), - ]; - let witness_assignments = BTreeMap::from([ (Witness(1), FieldElement::from(2u128)), (Witness(2), FieldElement::from(3u128)), @@ -242,9 +251,14 @@ fn double_inversion_brillig_oracle() { (Witness(9), FieldElement::from(10u128)), ]) .into(); - let unconstrained_functions = vec![]; - let mut acvm = - ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); + let unconstrained_functions = vec![brillig_bytecode]; + let mut acvm = ACVM::new( + &StubbedBlackBoxSolver, + &opcodes, + witness_assignments, + &unconstrained_functions, + &[], + ); // use the partial witness generation solver with our acir program let solver_status = acvm.solve(); @@ -311,18 +325,7 @@ fn oracle_dependent_execution() { let w_x_inv = Witness(3); let w_y_inv = Witness(4); - let brillig_data = Brillig { - inputs: vec![ - BrilligInputs::Single(w_x.into()), // Input Register 0 - BrilligInputs::Single(Expression::default()), // Input Register 1 - BrilligInputs::Single(w_y.into()), // Input Register 2, - ], - outputs: vec![ - BrilligOutputs::Simple(w_x), // Output Register 0 - from input - BrilligOutputs::Simple(w_y_inv), // Output Register 1 - BrilligOutputs::Simple(w_y), // Output Register 2 - from input - BrilligOutputs::Simple(w_y_inv), // Output Register 3 - ], + let brillig_bytecode = BrilligBytecode { bytecode: vec![ BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), @@ -346,7 +349,6 @@ fn oracle_dependent_execution() { }, BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 4 }, ], - predicate: None, }; // This equality check can be executed immediately before resolving any foreign calls. @@ -366,15 +368,34 @@ fn oracle_dependent_execution() { let opcodes = vec![ Opcode::AssertZero(equality_check), - Opcode::Brillig(brillig_data), + Opcode::BrilligCall { + id: 0, + inputs: vec![ + BrilligInputs::Single(w_x.into()), // Input Register 0 + BrilligInputs::Single(Expression::default()), // Input Register 1 + BrilligInputs::Single(w_y.into()), // Input Register 2, + ], + outputs: vec![ + BrilligOutputs::Simple(w_x), // Output Register 0 - from input + BrilligOutputs::Simple(w_y_inv), // Output Register 1 + BrilligOutputs::Simple(w_y), // Output Register 2 - from input + BrilligOutputs::Simple(w_y_inv), // Output Register 3 + ], + predicate: None, + }, Opcode::AssertZero(inverse_equality_check), ]; let witness_assignments = BTreeMap::from([(w_x, FieldElement::from(2u128)), (w_y, FieldElement::from(2u128))]).into(); - let unconstrained_functions = vec![]; - let mut acvm = - ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); + let unconstrained_functions = vec![brillig_bytecode]; + let mut acvm = ACVM::new( + &StubbedBlackBoxSolver, + &opcodes, + witness_assignments, + &unconstrained_functions, + &[], + ); // use the partial witness generation solver with our acir program let solver_status = acvm.solve(); @@ -436,21 +457,7 @@ fn brillig_oracle_predicate() { destination: MemoryAddress::from(2), }; - let brillig_opcode = Opcode::Brillig(Brillig { - inputs: vec![ - BrilligInputs::Single(Expression { - mul_terms: vec![], - linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], - q_c: fe_0, - }), - BrilligInputs::Single(Expression::default()), - ], - outputs: vec![ - BrilligOutputs::Simple(w_x_plus_y), - BrilligOutputs::Simple(w_oracle), - BrilligOutputs::Simple(w_equal_res), - BrilligOutputs::Simple(w_lt_res), - ], + let brillig_bytecode = BrilligBytecode { bytecode: vec![ BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), @@ -467,19 +474,40 @@ fn brillig_oracle_predicate() { input_value_types: vec![HeapValueType::field()], }, ], - predicate: Some(Expression::default()), - }); + }; - let opcodes = vec![brillig_opcode]; + let opcodes = vec![Opcode::BrilligCall { + id: 0, + inputs: vec![ + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], + q_c: fe_0, + }), + BrilligInputs::Single(Expression::default()), + ], + outputs: vec![ + BrilligOutputs::Simple(w_x_plus_y), + BrilligOutputs::Simple(w_oracle), + BrilligOutputs::Simple(w_equal_res), + BrilligOutputs::Simple(w_lt_res), + ], + predicate: Some(Expression::default()), + }]; let witness_assignments = BTreeMap::from([ (Witness(1), FieldElement::from(2u128)), (Witness(2), FieldElement::from(3u128)), ]) .into(); - let unconstrained_functions = vec![]; - let mut acvm = - ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); + let unconstrained_functions = vec![brillig_bytecode]; + let mut acvm = ACVM::new( + &StubbedBlackBoxSolver, + &opcodes, + witness_assignments, + &unconstrained_functions, + &[], + ); let solver_status = acvm.solve(); assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); @@ -514,12 +542,14 @@ fn unsatisfied_opcode_resolved() { let opcodes = vec![Opcode::AssertZero(opcode_a)]; let unconstrained_functions = vec![]; - let mut acvm = ACVM::new(&StubbedBlackBoxSolver, &opcodes, values, &unconstrained_functions); + let mut acvm = + ACVM::new(&StubbedBlackBoxSolver, &opcodes, values, &unconstrained_functions, &[]); let solver_status = acvm.solve(); assert_eq!( solver_status, ACVMStatus::Failure(OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Resolved(OpcodeLocation::Acir(0)), + payload: None }), "The first opcode is not satisfiable, expected an error indicating this" ); @@ -554,26 +584,12 @@ fn unsatisfied_opcode_resolved_brillig() { let jmp_if_opcode = BrilligOpcode::JumpIf { condition: MemoryAddress::from(2), location: location_of_stop }; - let trap_opcode = BrilligOpcode::Trap { revert_data_offset: 0, revert_data_size: 0 }; + let trap_opcode = BrilligOpcode::Trap { revert_data: HeapArray::default() }; let stop_opcode = BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }; - let brillig_opcode = Opcode::Brillig(Brillig { - inputs: vec![ - BrilligInputs::Single(Expression { - mul_terms: vec![], - linear_combinations: vec![(fe_1, w_x)], - q_c: fe_0, - }), - BrilligInputs::Single(Expression { - mul_terms: vec![], - linear_combinations: vec![(fe_1, w_y)], - q_c: fe_0, - }), - ], - outputs: vec![BrilligOutputs::Simple(w_result)], + let brillig_bytecode = BrilligBytecode { bytecode: vec![calldata_copy_opcode, equal_opcode, jmp_if_opcode, trap_opcode, stop_opcode], - predicate: Some(Expression::one()), - }); + }; let opcode_a = Expression { mul_terms: vec![], @@ -595,14 +611,34 @@ fn unsatisfied_opcode_resolved_brillig() { values.insert(w_y, FieldElement::from(1_i128)); values.insert(w_result, FieldElement::from(0_i128)); - let opcodes = vec![brillig_opcode, Opcode::AssertZero(opcode_a)]; - let unconstrained_functions = vec![]; - let mut acvm = ACVM::new(&StubbedBlackBoxSolver, &opcodes, values, &unconstrained_functions); + let opcodes = vec![ + Opcode::BrilligCall { + id: 0, + inputs: vec![ + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x)], + q_c: fe_0, + }), + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_y)], + q_c: fe_0, + }), + ], + outputs: vec![BrilligOutputs::Simple(w_result)], + predicate: Some(Expression::one()), + }, + Opcode::AssertZero(opcode_a), + ]; + let unconstrained_functions = vec![brillig_bytecode]; + let mut acvm = + ACVM::new(&StubbedBlackBoxSolver, &opcodes, values, &unconstrained_functions, &[]); let solver_status = acvm.solve(); assert_eq!( solver_status, ACVMStatus::Failure(OpcodeResolutionError::BrilligFunctionFailed { - message: None, + payload: None, call_stack: vec![OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 }] }), "The first opcode is not satisfiable, expected an error indicating this" @@ -642,7 +678,7 @@ fn memory_operations() { let opcodes = vec![init, read_op, expression]; let unconstrained_functions = vec![]; let mut acvm = - ACVM::new(&StubbedBlackBoxSolver, &opcodes, initial_witness, &unconstrained_functions); + ACVM::new(&StubbedBlackBoxSolver, &opcodes, initial_witness, &unconstrained_functions, &[]); let solver_status = acvm.solve(); assert_eq!(solver_status, ACVMStatus::Solved); let witness_map = acvm.finalize(); diff --git a/acvm-repo/acvm_js/build.sh b/acvm-repo/acvm_js/build.sh index 16fb26e55db..c07d2d8a4c1 100755 --- a/acvm-repo/acvm_js/build.sh +++ b/acvm-repo/acvm_js/build.sh @@ -25,7 +25,7 @@ function run_if_available { require_command jq require_command cargo require_command wasm-bindgen -require_command wasm-opt +#require_command wasm-opt self_path=$(dirname "$(readlink -f "$0")") pname=$(cargo read-manifest | jq -r '.name') diff --git a/acvm-repo/acvm_js/src/execute.rs b/acvm-repo/acvm_js/src/execute.rs index 2fab684467e..99d4b4ccb74 100644 --- a/acvm-repo/acvm_js/src/execute.rs +++ b/acvm-repo/acvm_js/src/execute.rs @@ -1,6 +1,7 @@ use std::{future::Future, pin::Pin}; use acvm::acir::circuit::brillig::BrilligBytecode; +use acvm::acir::circuit::ResolvedAssertionPayload; use acvm::BlackBoxFunctionSolver; use acvm::{ acir::circuit::{Circuit, Program}, @@ -78,7 +79,7 @@ pub async fn execute_circuit_with_return_witness( console_error_panic_hook::set_once(); let program: Program = Program::deserialize_program(&program) - .map_err(|_| JsExecutionError::new("Failed to deserialize circuit. This is likely due to differing serialization formats between ACVM_JS and your compiler".to_string(), None))?; + .map_err(|_| JsExecutionError::new("Failed to deserialize circuit. This is likely due to differing serialization formats between ACVM_JS and your compiler".to_string(), None, None))?; let mut witness_stack = execute_program_with_native_program_and_return( solver, @@ -93,7 +94,7 @@ pub async fn execute_circuit_with_return_witness( let main_circuit = &program.functions[0]; let return_witness = extract_indices(&solved_witness, main_circuit.return_values.0.iter().copied().collect()) - .map_err(|err| JsExecutionError::new(err, None))?; + .map_err(|err| JsExecutionError::new(err, None, None))?; Ok((solved_witness, return_witness).into()) } @@ -165,7 +166,10 @@ async fn execute_program_with_native_type_return( foreign_call_executor: &ForeignCallHandler, ) -> Result { let program: Program = Program::deserialize_program(&program) - .map_err(|_| JsExecutionError::new("Failed to deserialize circuit. This is likely due to differing serialization formats between ACVM_JS and your compiler".to_string(), None))?; + .map_err(|_| JsExecutionError::new( + "Failed to deserialize circuit. This is likely due to differing serialization formats between ACVM_JS and your compiler".to_string(), + None, + None))?; execute_program_with_native_program_and_return( solver, @@ -239,6 +243,7 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { &circuit.opcodes, initial_witness, self.unconstrained_functions, + &circuit.assert_messages, ); loop { @@ -250,39 +255,48 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { unreachable!("Execution should not stop while in `InProgress` state.") } ACVMStatus::Failure(error) => { - let (assert_message, call_stack): (Option<&str>, _) = match &error { + // Fetch call stack + let call_stack = match &error { OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Resolved(opcode_location), + .. } | OpcodeResolutionError::IndexOutOfBounds { opcode_location: ErrorLocation::Resolved(opcode_location), .. - } => ( - circuit.get_assert_message(*opcode_location), - Some(vec![*opcode_location]), - ), - OpcodeResolutionError::BrilligFunctionFailed { - call_stack, - message, - } => { - let revert_message = message.as_ref().map(String::as_str); - let failing_opcode = call_stack - .last() - .expect("Brillig error call stacks cannot be empty"); - ( - revert_message.or(circuit.get_assert_message(*failing_opcode)), - Some(call_stack.clone()), - ) + } => Some(vec![*opcode_location]), + OpcodeResolutionError::BrilligFunctionFailed { call_stack, .. } => { + Some(call_stack.clone()) } - _ => (None, None), + _ => None, }; - - let error_string = match &assert_message { - Some(assert_message) => format!("Assertion failed: {}", assert_message), - None => error.to_string(), + // If the failed opcode has an assertion message, integrate it into the error message for backwards compatibility. + // Otherwise, pass the raw assertion payload as is. + let (message, raw_assertion_payload) = match &error { + OpcodeResolutionError::UnsatisfiedConstrain { + payload: Some(payload), + .. + } + | OpcodeResolutionError::BrilligFunctionFailed { + payload: Some(payload), + .. + } => match payload { + ResolvedAssertionPayload::Raw(selector, fields) => { + (error.to_string(), Some((*selector, fields.clone()))) + } + ResolvedAssertionPayload::String(message) => { + (format!("Assertion failed: {}", message), None) + } + }, + _ => (error.to_string(), None), }; - return Err(JsExecutionError::new(error_string, call_stack).into()); + return Err(JsExecutionError::new( + message, + call_stack, + raw_assertion_payload, + ) + .into()); } ACVMStatus::RequiresForeignCall(foreign_call) => { let result = @@ -304,7 +318,7 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { call_resolved_outputs.push(*return_value); } else { // TODO: look at changing this call stack from None - return Err(JsExecutionError::new(format!("Failed to read from solved witness of ACIR call at witness {}", return_witness_index), None).into()); + return Err(JsExecutionError::new(format!("Failed to read from solved witness of ACIR call at witness {}", return_witness_index), None, None).into()); } } acvm.resolve_pending_acir_call(call_resolved_outputs); diff --git a/acvm-repo/acvm_js/src/js_execution_error.rs b/acvm-repo/acvm_js/src/js_execution_error.rs index d91a9425f7e..ae0e0aaa236 100644 --- a/acvm-repo/acvm_js/src/js_execution_error.rs +++ b/acvm-repo/acvm_js/src/js_execution_error.rs @@ -1,11 +1,18 @@ -use acvm::acir::circuit::OpcodeLocation; -use js_sys::{Array, Error, JsString, Reflect}; +use acvm::{acir::circuit::OpcodeLocation, FieldElement}; +use js_sys::{Array, Error, JsString, Map, Object, Reflect}; use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; +use crate::js_witness_map::field_element_to_js_string; + #[wasm_bindgen(typescript_custom_section)] const EXECUTION_ERROR: &'static str = r#" +export type RawAssertionPayload = { + selector: number; + fields: string[]; +}; export type ExecutionError = Error & { callStack?: string[]; + rawAssertionPayload?: RawAssertionPayload; }; "#; @@ -25,7 +32,11 @@ extern "C" { impl JsExecutionError { /// Creates a new execution error with the given call stack. /// Call stacks won't be optional in the future, after removing ErrorLocation in ACVM. - pub fn new(message: String, call_stack: Option>) -> Self { + pub fn new( + message: String, + call_stack: Option>, + assertion_payload: Option<(u64, Vec)>, + ) -> Self { let mut error = JsExecutionError::constructor(JsString::from(message)); let js_call_stack = match call_stack { Some(call_stack) => { @@ -37,8 +48,24 @@ impl JsExecutionError { } None => JsValue::UNDEFINED, }; + let assertion_payload = match assertion_payload { + Some((selector, fields)) => { + let raw_payload_map = Map::new(); + raw_payload_map + .set(&JsValue::from_str("selector"), &JsValue::from(selector.to_string())); + let js_fields = Array::new(); + for field in fields { + js_fields.push(&field_element_to_js_string(&field)); + } + raw_payload_map.set(&JsValue::from_str("fields"), &js_fields.into()); + + Object::from_entries(&raw_payload_map).unwrap().into() + } + None => JsValue::UNDEFINED, + }; error.set_property("callStack", js_call_stack); + error.set_property("rawAssertionPayload", assertion_payload); error } diff --git a/acvm-repo/acvm_js/test/browser/execute_circuit.test.ts b/acvm-repo/acvm_js/test/browser/execute_circuit.test.ts index 259c51ed1c6..f6287c2ae8a 100644 --- a/acvm-repo/acvm_js/test/browser/execute_circuit.test.ts +++ b/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/acvm-repo/acvm_js/test/node/execute_circuit.test.ts b/acvm-repo/acvm_js/test/node/execute_circuit.test.ts index 32487f8bbba..f9fd5c10b3e 100644 --- a/acvm-repo/acvm_js/test/node/execute_circuit.test.ts +++ b/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/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts b/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts index 722bae8e015..0d6fab0e1f3 100644 --- a/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts +++ b/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts @@ -2,13 +2,13 @@ import { WitnessMap } from '@noir-lang/acvm_js'; // See `complex_brillig_foreign_call` integration test in `acir/tests/test_program_serialization.rs`. export const bytecode = Uint8Array.from([ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 84, 75, 10, 132, 48, 12, 77, 218, 209, 145, 217, 205, 13, 6, 198, 3, 84, 79, - 224, 93, 196, 157, 162, 75, 79, 47, 22, 124, 197, 16, 186, 17, 43, 104, 32, 36, 109, 126, 143, 36, 45, 211, 70, 133, - 103, 134, 110, 61, 27, 232, 140, 179, 164, 224, 215, 64, 186, 115, 84, 113, 186, 92, 238, 42, 140, 230, 1, 24, 237, 5, - 24, 195, 62, 220, 116, 222, 41, 231, 146, 180, 127, 54, 242, 126, 94, 158, 51, 207, 57, 206, 111, 200, 2, 247, 4, 219, - 79, 245, 157, 132, 31, 137, 89, 52, 73, 176, 214, 46, 167, 125, 23, 89, 213, 254, 8, 156, 237, 56, 76, 125, 55, 91, - 229, 170, 161, 254, 133, 94, 42, 59, 171, 184, 69, 197, 46, 66, 202, 47, 40, 86, 39, 220, 155, 3, 185, 191, 180, 183, - 55, 163, 72, 98, 70, 66, 221, 251, 40, 173, 255, 35, 68, 62, 61, 5, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 84, 75, 10, 131, 64, 12, 77, 102, 90, 43, 221, 245, 6, 133, 246, 0, 211, 158, + 192, 187, 136, 59, 69, 151, 158, 94, 116, 48, 131, 241, 233, 70, 28, 65, 3, 195, 155, 79, 62, 47, 9, 25, 166, 81, 210, + 97, 177, 236, 239, 130, 70, 208, 223, 91, 154, 75, 208, 205, 4, 221, 62, 249, 113, 60, 95, 238, 40, 142, 230, 2, 28, + 237, 1, 28, 73, 245, 255, 132, 253, 142, 217, 151, 168, 245, 179, 43, 243, 115, 163, 113, 190, 18, 57, 63, 4, 83, 44, + 180, 55, 50, 180, 28, 188, 153, 224, 196, 122, 175, 111, 112, 68, 24, 65, 50, 204, 162, 100, 249, 119, 137, 226, 193, + 16, 251, 169, 50, 204, 235, 170, 41, 139, 214, 130, 42, 82, 253, 168, 253, 23, 222, 25, 236, 58, 176, 237, 20, 234, + 207, 107, 45, 78, 184, 55, 27, 124, 191, 104, 42, 111, 40, 121, 15, 94, 163, 77, 128, 65, 5, 0, 0, ]); export const initialWitnessMap: WitnessMap = new Map([ [1, '0x0000000000000000000000000000000000000000000000000000000000000001'], diff --git a/acvm-repo/acvm_js/test/shared/foreign_call.ts b/acvm-repo/acvm_js/test/shared/foreign_call.ts index 0e3d77f62a9..3c66ba18629 100644 --- a/acvm-repo/acvm_js/test/shared/foreign_call.ts +++ b/acvm-repo/acvm_js/test/shared/foreign_call.ts @@ -2,10 +2,10 @@ import { WitnessMap } from '@noir-lang/acvm_js'; // See `simple_brillig_foreign_call` integration test in `acir/tests/test_program_serialization.rs`. export const bytecode = Uint8Array.from([ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 144, 61, 10, 192, 32, 12, 133, 19, 11, 165, 116, 235, 77, 236, 13, 122, 153, - 14, 93, 58, 136, 120, 124, 241, 47, 129, 12, 42, 130, 126, 16, 18, 146, 16, 222, 11, 66, 225, 136, 129, 84, 111, 162, - 150, 112, 239, 161, 172, 231, 184, 113, 221, 45, 45, 245, 42, 242, 144, 216, 43, 250, 153, 83, 204, 191, 223, 189, - 198, 246, 92, 39, 60, 244, 63, 195, 59, 87, 99, 150, 165, 113, 83, 193, 0, 1, 19, 247, 29, 5, 160, 1, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 144, 193, 10, 192, 32, 8, 134, 117, 99, 99, 236, 182, 55, 105, 111, 176, 151, + 217, 161, 75, 135, 136, 30, 63, 42, 82, 144, 8, 47, 245, 65, 252, 230, 47, 162, 34, 52, 174, 242, 144, 226, 131, 148, + 255, 18, 206, 125, 164, 102, 142, 23, 215, 245, 50, 114, 222, 173, 15, 80, 38, 65, 217, 108, 39, 61, 7, 30, 115, 11, + 223, 186, 248, 251, 160, 221, 170, 146, 64, 191, 39, 215, 60, 3, 47, 3, 99, 171, 188, 84, 164, 1, 0, 0, ]); export const initialWitnessMap: WitnessMap = new Map([ [1, '0x0000000000000000000000000000000000000000000000000000000000000005'], diff --git a/acvm-repo/acvm_js/test/shared/memory_op.ts b/acvm-repo/acvm_js/test/shared/memory_op.ts index a69ae443259..20ea88c7130 100644 --- a/acvm-repo/acvm_js/test/shared/memory_op.ts +++ b/acvm-repo/acvm_js/test/shared/memory_op.ts @@ -1,9 +1,9 @@ // See `memory_op_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, 213, 81, 57, 14, 0, 32, 8, 147, 195, 255, 224, 15, 252, 255, 171, 212, 200, 208, - 129, 77, 24, 108, 66, 90, 150, 166, 20, 106, 23, 125, 143, 128, 62, 96, 103, 114, 173, 45, 198, 116, 182, 55, 140, - 106, 95, 74, 246, 149, 60, 47, 171, 46, 215, 126, 43, 87, 179, 111, 23, 8, 202, 176, 99, 248, 240, 9, 11, 137, 33, - 212, 110, 35, 3, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 82, 65, 10, 0, 32, 8, 203, 180, 255, 216, 15, 250, 255, 171, 10, 154, 16, 210, + 45, 61, 52, 144, 13, 132, 49, 135, 84, 54, 218, 26, 134, 22, 112, 5, 19, 180, 237, 61, 6, 88, 223, 208, 179, 125, 41, + 216, 151, 227, 188, 52, 187, 92, 253, 173, 92, 137, 190, 157, 143, 160, 254, 155, 45, 188, 148, 11, 38, 213, 237, 188, + 16, 35, 3, 0, 0, ]); export const initialWitnessMap = new Map([ diff --git a/acvm-repo/acvm_js/test/shared/nested_acir_call.ts b/acvm-repo/acvm_js/test/shared/nested_acir_call.ts index 4b73d01bb01..64051dff93f 100644 --- a/acvm-repo/acvm_js/test/shared/nested_acir_call.ts +++ b/acvm-repo/acvm_js/test/shared/nested_acir_call.ts @@ -2,13 +2,13 @@ import { WitnessMap, StackItem, WitnessStack } from '@noir-lang/acvm_js'; // See `nested_acir_call_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, 205, 146, 65, 10, 3, 33, 12, 69, 163, 46, 230, 58, 137, 209, 49, 238, 122, 149, 74, - 157, 251, 31, 161, 83, 154, 161, 86, 132, 89, 212, 194, 124, 248, 24, 36, 132, 228, 241, 29, 188, 229, 212, 47, 45, - 187, 205, 110, 11, 31, 25, 53, 28, 255, 103, 77, 14, 58, 29, 141, 55, 125, 241, 55, 145, 109, 102, 49, 174, 33, 212, - 228, 43, 49, 221, 209, 231, 34, 17, 67, 44, 171, 144, 80, 148, 248, 240, 194, 92, 37, 72, 202, 37, 39, 204, 20, 184, - 210, 22, 51, 111, 58, 204, 205, 219, 11, 161, 129, 208, 214, 6, 6, 114, 29, 193, 127, 193, 130, 137, 176, 236, 188, - 189, 252, 162, 183, 218, 230, 238, 97, 138, 250, 152, 245, 245, 87, 220, 12, 140, 113, 95, 153, 170, 129, 185, 17, 60, - 3, 54, 212, 19, 104, 145, 195, 151, 14, 4, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 205, 146, 97, 10, 195, 32, 12, 133, 163, 66, 207, 147, 24, 173, 241, 223, 174, 50, + 153, 189, 255, 17, 214, 177, 148, 57, 17, 250, 99, 14, 250, 224, 97, 144, 16, 146, 143, 231, 224, 45, 167, 126, 105, + 217, 109, 118, 91, 248, 200, 168, 225, 248, 63, 107, 114, 208, 233, 104, 188, 233, 139, 191, 137, 108, 51, 139, 113, + 13, 161, 38, 95, 137, 233, 142, 62, 23, 137, 24, 98, 89, 133, 132, 162, 196, 135, 23, 230, 42, 65, 82, 46, 57, 97, + 166, 192, 149, 182, 152, 121, 211, 97, 110, 222, 94, 8, 13, 132, 182, 54, 48, 144, 235, 8, 254, 11, 22, 76, 132, 101, + 231, 237, 229, 23, 189, 213, 54, 119, 15, 83, 212, 199, 172, 175, 191, 226, 102, 96, 140, 251, 202, 84, 13, 204, 141, + 224, 25, 176, 161, 158, 53, 121, 144, 73, 14, 4, 0, 0, ]); export const initialWitnessMap: WitnessMap = new Map([ diff --git a/acvm-repo/acvm_js/test/shared/variable_base_scalar_mul.ts b/acvm-repo/acvm_js/test/shared/variable_base_scalar_mul.ts new file mode 100644 index 00000000000..400f7bf4e61 --- /dev/null +++ b/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/acvm-repo/blackbox_solver/src/curve_specific_solver.rs b/acvm-repo/blackbox_solver/src/curve_specific_solver.rs index fab67467d9a..a809e21e2ca 100644 --- a/acvm-repo/blackbox_solver/src/curve_specific_solver.rs +++ b/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/acvm-repo/bn254_blackbox_solver/src/fixed_base_scalar_mul.rs b/acvm-repo/bn254_blackbox_solver/src/fixed_base_scalar_mul.rs index cd91c290f49..2d7ffe1cf1c 100644 --- a/acvm-repo/bn254_blackbox_solver/src/fixed_base_scalar_mul.rs +++ b/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/acvm-repo/bn254_blackbox_solver/src/lib.rs b/acvm-repo/bn254_blackbox_solver/src/lib.rs index 25b10252a78..9395260fe36 100644 --- a/acvm-repo/bn254_blackbox_solver/src/lib.rs +++ b/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/acvm-repo/brillig/src/black_box.rs b/acvm-repo/brillig/src/black_box.rs index 29861d0fd84..f31a434c772 100644 --- a/acvm-repo/brillig/src/black_box.rs +++ b/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/acvm-repo/brillig/src/opcodes.rs b/acvm-repo/brillig/src/opcodes.rs index 468fd88db45..a060aa83d41 100644 --- a/acvm-repo/brillig/src/opcodes.rs +++ b/acvm-repo/brillig/src/opcodes.rs @@ -52,6 +52,12 @@ pub struct HeapArray { pub size: usize, } +impl Default for HeapArray { + fn default() -> Self { + Self { pointer: MemoryAddress(0), size: 0 } + } +} + /// A memory-sized vector passed starting from a Brillig memory location and with a memory-held size #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy)] pub struct HeapVector { @@ -179,8 +185,7 @@ pub enum BrilligOpcode { BlackBox(BlackBoxOp), /// Used to denote execution failure, returning data after the offset Trap { - revert_data_offset: usize, - revert_data_size: usize, + revert_data: HeapArray, }, /// Stop execution, returning data after the offset Stop { diff --git a/acvm-repo/brillig_vm/src/black_box.rs b/acvm-repo/brillig_vm/src/black_box.rs index 19407da52db..9557cdae7b9 100644 --- a/acvm-repo/brillig_vm/src/black_box.rs +++ b/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/acvm-repo/brillig_vm/src/lib.rs b/acvm-repo/brillig_vm/src/lib.rs index 75299670f94..7901c313596 100644 --- a/acvm-repo/brillig_vm/src/lib.rs +++ b/acvm-repo/brillig_vm/src/lib.rs @@ -305,8 +305,12 @@ impl<'a, B: BlackBoxFunctionSolver> VM<'a, B> { } self.increment_program_counter() } - Opcode::Trap { revert_data_offset, revert_data_size } => { - self.trap(*revert_data_offset, *revert_data_size) + Opcode::Trap { revert_data } => { + if revert_data.size > 0 { + self.trap(self.memory.read_ref(revert_data.pointer).0, revert_data.size) + } else { + self.trap(0, 0) + } } Opcode::Stop { return_data_offset, return_data_size } => { self.finish(*return_data_offset, *return_data_size) @@ -715,7 +719,7 @@ mod tests { let jump_opcode = Opcode::Jump { location: 3 }; - let trap_opcode = Opcode::Trap { revert_data_offset: 0, revert_data_size: 0 }; + let trap_opcode = Opcode::Trap { revert_data: HeapArray::default() }; let not_equal_cmp_opcode = Opcode::BinaryFieldOp { op: BinaryFieldOp::Equals, diff --git a/compiler/noirc_driver/src/abi_gen.rs b/compiler/noirc_driver/src/abi_gen.rs index 51fe4986845..b1e52dc309d 100644 --- a/compiler/noirc_driver/src/abi_gen.rs +++ b/compiler/noirc_driver/src/abi_gen.rs @@ -2,11 +2,11 @@ use std::collections::BTreeMap; use acvm::acir::native_types::Witness; use iter_extended::{btree_map, vecmap}; -use noirc_abi::{Abi, AbiParameter, AbiReturnType, AbiType, AbiValue}; +use noirc_abi::{Abi, AbiErrorType, AbiParameter, AbiReturnType, AbiType, AbiValue}; use noirc_frontend::ast::Visibility; use noirc_frontend::{ hir::Context, - hir_def::{expr::HirArrayLiteral, function::Param, stmt::HirPattern}, + hir_def::{expr::HirArrayLiteral, function::Param, stmt::HirPattern, types::Type}, macros_api::{HirExpression, HirLiteral}, node_interner::{FuncId, NodeInterner}, }; @@ -20,12 +20,17 @@ pub(super) fn gen_abi( input_witnesses: Vec, return_witnesses: Vec, return_visibility: Visibility, + error_types: BTreeMap, ) -> Abi { let (parameters, return_type) = compute_function_abi(context, func_id); let param_witnesses = param_witnesses_from_abi_param(¶meters, input_witnesses); let return_type = return_type .map(|typ| AbiReturnType { abi_type: typ, visibility: return_visibility.into() }); - Abi { parameters, return_type, param_witnesses, return_witnesses } + let error_types = error_types + .into_iter() + .map(|(selector, typ)| (selector, AbiErrorType::from_type(context, &typ))) + .collect(); + Abi { parameters, return_type, param_witnesses, return_witnesses, error_types } } pub(super) fn compute_function_abi( diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index a2c069eac68..ef874d45f88 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -529,6 +529,7 @@ pub fn compile_no_check( main_input_witnesses, main_return_witnesses, names, + error_types, } = create_program( program, options.show_ssa, @@ -543,6 +544,7 @@ pub fn compile_no_check( main_input_witnesses, main_return_witnesses, visibility, + error_types, ); let file_map = filter_relevant_files(&debug, &context.file_manager); diff --git a/compiler/noirc_errors/Cargo.toml b/compiler/noirc_errors/Cargo.toml index c9cb7e2709f..41b1cd0ff58 100644 --- a/compiler/noirc_errors/Cargo.toml +++ b/compiler/noirc_errors/Cargo.toml @@ -20,4 +20,4 @@ serde_with = "3.2.0" tracing.workspace = true flate2.workspace = true serde_json.workspace = true -base64.workspace = true \ No newline at end of file +base64.workspace = true diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs index ee047903743..210e56b2ecb 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs +++ b/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/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index 8dc7ccb9ac0..e50316eabcc 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -5,7 +5,7 @@ use crate::brillig::brillig_ir::{ BrilligBinaryOp, BrilligContext, BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, }; use crate::ssa::ir::dfg::CallStack; -use crate::ssa::ir::instruction::{ConstrainError, UserDefinedConstrainError}; +use crate::ssa::ir::instruction::ConstrainError; use crate::ssa::ir::{ basic_block::{BasicBlock, BasicBlockId}, dfg::DataFlowGraph, @@ -248,34 +248,6 @@ impl<'block> BrilligBlock<'block> { self.convert_ssa_binary(binary, dfg, result_var); } Instruction::Constrain(lhs, rhs, assert_message) => { - let (has_revert_data, static_assert_message) = if let Some(error) = assert_message { - match error.as_ref() { - ConstrainError::Intrinsic(string) => (false, Some(string.clone())), - ConstrainError::UserDefined(UserDefinedConstrainError::Static(string)) => { - (true, Some(string.clone())) - } - ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic( - call_instruction, - )) => { - let Instruction::Call { func, arguments } = call_instruction else { - unreachable!("expected a call instruction") - }; - - let Value::Function(func_id) = &dfg[*func] else { - unreachable!("expected a function value") - }; - - self.convert_ssa_function_call(*func_id, arguments, dfg, &[]); - - // Dynamic assert messages are handled in the generated function call. - // We then don't need to attach one to the constrain instruction. - (false, None) - } - } - } else { - (false, None) - }; - let condition = SingleAddrVariable { address: self.brillig_context.allocate_register(), bit_size: 1, @@ -286,11 +258,27 @@ impl<'block> BrilligBlock<'block> { dfg, condition, ); - if has_revert_data { - self.brillig_context - .codegen_constrain_with_revert_data(condition, static_assert_message); - } else { - self.brillig_context.codegen_constrain(condition, static_assert_message); + match assert_message { + Some(ConstrainError::UserDefined(selector, values)) => { + let payload_values = + vecmap(values, |value| self.convert_ssa_value(*value, dfg)); + let payload_as_params = vecmap(values, |value| { + let value_type = dfg.type_of_value(*value); + FunctionContext::ssa_type_to_parameter(&value_type) + }); + self.brillig_context.codegen_constrain_with_revert_data( + condition, + payload_values, + payload_as_params, + selector.to_u64(), + ); + } + Some(ConstrainError::Intrinsic(message)) => { + self.brillig_context.codegen_constrain(condition, Some(message.clone())); + } + None => { + self.brillig_context.codegen_constrain(condition, None); + } } self.brillig_context.deallocate_single_addr(condition); } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs index 7e37e1da434..b4ed59de59d 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs @@ -130,7 +130,7 @@ pub(crate) mod tests { use std::vec; use acvm::acir::brillig::{ - ForeignCallParam, ForeignCallResult, HeapVector, MemoryAddress, ValueOrArray, + ForeignCallParam, ForeignCallResult, HeapArray, HeapVector, MemoryAddress, ValueOrArray, }; use acvm::brillig_vm::brillig::HeapValueType; use acvm::brillig_vm::{VMStatus, VM}; @@ -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, @@ -270,7 +280,7 @@ pub(crate) mod tests { // uses unresolved jumps which requires a block to be constructed in SSA and // we don't need this for Brillig IR tests context.push_opcode(BrilligOpcode::JumpIf { condition: r_equality, location: 8 }); - context.push_opcode(BrilligOpcode::Trap { revert_data_offset: 0, revert_data_size: 0 }); + context.push_opcode(BrilligOpcode::Trap { revert_data: HeapArray::default() }); context.stop_instruction(); diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs index 8a4f469f5c9..dee6c6076f4 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs @@ -29,7 +29,9 @@ pub(crate) struct GeneratedBrillig { /// It includes the bytecode of the function and all the metadata that allows linking with other functions. pub(crate) struct BrilligArtifact { pub(crate) byte_code: Vec, - /// A map of bytecode positions to assertion messages + /// A map of bytecode positions to assertion messages. + /// Some error messages (compiler intrinsics) are not emitted via revert data, + /// instead, they are handled externally so they don't add size to user programs. pub(crate) assert_messages: BTreeMap, /// The set of jumps that need to have their locations /// resolved. diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs index f8f39f03df4..d9109646338 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs @@ -1,7 +1,9 @@ -use acvm::acir::brillig::MemoryAddress; +use acvm::acir::brillig::{HeapArray, MemoryAddress}; use super::{ - brillig_variable::SingleAddrVariable, BrilligBinaryOp, BrilligContext, ReservedRegisters, + artifact::BrilligParameter, + brillig_variable::{BrilligVariable, SingleAddrVariable}, + BrilligBinaryOp, BrilligContext, ReservedRegisters, }; impl BrilligContext { @@ -144,25 +146,62 @@ impl BrilligContext { pub(crate) fn codegen_constrain_with_revert_data( &mut self, condition: SingleAddrVariable, - assert_message: Option, + revert_data_items: Vec, + revert_data_types: Vec, + error_selector: u64, ) { assert!(condition.bit_size == 1); self.codegen_if_not(condition.address, |ctx| { - let (revert_data_offset, revert_data_size) = - if let Some(assert_message) = assert_message { - let bytes = assert_message.as_bytes(); - for (i, byte) in bytes.iter().enumerate() { - ctx.const_instruction( - SingleAddrVariable::new(MemoryAddress(i), 8), - (*byte as usize).into(), + let revert_data = HeapArray { + pointer: ctx.allocate_register(), + // + 1 due to the revert data id being the first item returned + size: BrilligContext::flattened_tuple_size(&revert_data_types) + 1, + }; + ctx.codegen_allocate_fixed_length_array(revert_data.pointer, revert_data.size); + + let current_revert_data_pointer = ctx.allocate_register(); + ctx.mov_instruction(current_revert_data_pointer, revert_data.pointer); + let revert_data_id = + ctx.make_usize_constant_instruction((error_selector as u128).into()); + ctx.store_instruction(current_revert_data_pointer, revert_data_id.address); + + ctx.codegen_usize_op_in_place(current_revert_data_pointer, BrilligBinaryOp::Add, 1); + for (revert_variable, revert_param) in + revert_data_items.into_iter().zip(revert_data_types.into_iter()) + { + let flattened_size = BrilligContext::flattened_size(&revert_param); + match revert_param { + BrilligParameter::SingleAddr(_) => { + ctx.store_instruction( + current_revert_data_pointer, + revert_variable.extract_single_addr().address, + ); + } + BrilligParameter::Array(item_type, item_count) => { + let variable_pointer = revert_variable.extract_array().pointer; + + ctx.flatten_array( + &item_type, + item_count, + current_revert_data_pointer, + variable_pointer, ); } - (0, bytes.len()) - } else { - (0, 0) - }; - ctx.trap_instruction(revert_data_offset, revert_data_size); + BrilligParameter::Slice(_, _) => { + unimplemented!("Slices are not supported as revert data") + } + } + ctx.codegen_usize_op_in_place( + current_revert_data_pointer, + BrilligBinaryOp::Add, + flattened_size, + ); + } + ctx.trap_instruction(revert_data); + ctx.deallocate_register(revert_data.pointer); + ctx.deallocate_register(current_revert_data_pointer); + ctx.deallocate_single_addr(revert_data_id); }); } @@ -176,7 +215,7 @@ impl BrilligContext { assert!(condition.bit_size == 1); self.codegen_if_not(condition.address, |ctx| { - ctx.trap_instruction(0, 0); + ctx.trap_instruction(HeapArray::default()); if let Some(assert_message) = assert_message { ctx.obj.add_assert_message_to_last_opcode(assert_message); } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs index 41a6d1873e4..8b00939b3a7 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs @@ -114,13 +114,8 @@ impl DebugShow { } /// Emits a `trap` instruction. - pub(crate) fn trap_instruction(&self, revert_data_offset: usize, revert_data_size: usize) { - debug_println!( - self.enable_debug_trace, - " TRAP {}..{}", - revert_data_offset, - revert_data_offset + revert_data_size - ); + pub(crate) fn trap_instruction(&self, revert_data: HeapArray) { + debug_println!(self.enable_debug_trace, " TRAP {}", revert_data); } /// Emits a `mov` instruction. @@ -329,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/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs index 88cf987325d..732bd3cbc59 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs @@ -164,7 +164,7 @@ impl BrilligContext { } /// Computes the size of a parameter if it was flattened - fn flattened_size(param: &BrilligParameter) -> usize { + pub(super) fn flattened_size(param: &BrilligParameter) -> usize { match param { BrilligParameter::SingleAddr(_) => 1, BrilligParameter::Array(item_types, item_count) @@ -176,7 +176,7 @@ impl BrilligContext { } /// Computes the size of a parameter if it was flattened - fn flattened_tuple_size(tuple: &[BrilligParameter]) -> usize { + pub(super) fn flattened_tuple_size(tuple: &[BrilligParameter]) -> usize { tuple.iter().map(BrilligContext::flattened_size).sum() } @@ -369,7 +369,7 @@ impl BrilligContext { } // Flattens an array by recursively copying nested arrays and regular items. - fn flatten_array( + pub(super) fn flatten_array( &mut self, item_type: &[BrilligParameter], item_count: usize, diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs index dc160ce6a66..5d2430208e4 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs @@ -1,6 +1,6 @@ use acvm::{ acir::brillig::{ - BinaryFieldOp, BinaryIntOp, BlackBoxOp, HeapValueType, MemoryAddress, + BinaryFieldOp, BinaryIntOp, BlackBoxOp, HeapArray, HeapValueType, MemoryAddress, Opcode as BrilligOpcode, ValueOrArray, }, FieldElement, @@ -460,10 +460,10 @@ impl BrilligContext { }); } - pub(super) fn trap_instruction(&mut self, revert_data_offset: usize, revert_data_size: usize) { - self.debug_show.trap_instruction(revert_data_offset, revert_data_size); + pub(super) fn trap_instruction(&mut self, revert_data: HeapArray) { + self.debug_show.trap_instruction(revert_data); - self.push_opcode(BrilligOpcode::Trap { revert_data_offset, revert_data_size }); + self.push_opcode(BrilligOpcode::Trap { revert_data }); } } diff --git a/compiler/noirc_evaluator/src/errors.rs b/compiler/noirc_evaluator/src/errors.rs index b3e838e708e..1e922060100 100644 --- a/compiler/noirc_evaluator/src/errors.rs +++ b/compiler/noirc_evaluator/src/errors.rs @@ -7,7 +7,7 @@ //! An Error of the former is a user Error //! //! An Error of the latter is an error in the implementation of the compiler -use acvm::{acir::native_types::Expression, FieldElement}; +use acvm::FieldElement; use iter_extended::vecmap; use noirc_errors::{CustomDiagnostic as Diagnostic, FileDiagnostic}; use thiserror::Error; @@ -17,13 +17,6 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Eq, Clone, Error)] pub enum RuntimeError { - #[error("{}", format_failed_constraint(.assert_message))] - FailedConstraint { - lhs: Box, - rhs: Box, - call_stack: CallStack, - assert_message: Option, - }, #[error(transparent)] InternalError(#[from] InternalError), #[error("Index out of bounds, array has size {array_size}, but index was {index}")] @@ -52,16 +45,6 @@ pub enum RuntimeError { UnconstrainedOracleReturnToConstrained { call_stack: CallStack }, } -// We avoid showing the actual lhs and rhs since most of the time they are just 0 -// and 1 respectively. This would confuse users if a constraint such as -// assert(foo < bar) fails with "failed constraint: 0 = 1." -fn format_failed_constraint(message: &Option) -> String { - match message { - Some(message) => format!("Failed constraint: '{message}'"), - None => "Failed constraint".to_owned(), - } -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub enum SsaReport { Warning(InternalWarning), @@ -129,7 +112,6 @@ impl RuntimeError { | InternalError::UndeclaredAcirVar { call_stack } | InternalError::Unexpected { call_stack, .. }, ) - | RuntimeError::FailedConstraint { call_stack, .. } | RuntimeError::IndexOutOfBounds { call_stack, .. } | RuntimeError::InvalidRangeConstraint { call_stack, .. } | RuntimeError::TypeConversion { call_stack, .. } diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index 89ef74c7406..7f945f19784 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -7,7 +7,7 @@ //! This module heavily borrows from Cranelift #![allow(dead_code)] -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use crate::errors::{RuntimeError, SsaReport}; use acvm::acir::{ @@ -20,10 +20,16 @@ use acvm::acir::{ use noirc_errors::debug_info::{DebugFunctions, DebugInfo, DebugTypes, DebugVariables}; use noirc_frontend::ast::Visibility; -use noirc_frontend::{hir_def::function::FunctionSignature, monomorphization::ast::Program}; +use noirc_frontend::{ + hir_def::{function::FunctionSignature, types::Type as HirType}, + monomorphization::ast::Program, +}; use tracing::{span, Level}; -use self::{acir_gen::GeneratedAcir, ssa_gen::Ssa}; +use self::{ + acir_gen::{Artifacts, GeneratedAcir}, + ssa_gen::Ssa, +}; mod acir_gen; pub(super) mod function_builder; @@ -42,7 +48,7 @@ pub(crate) fn optimize_into_acir( print_brillig_trace: bool, force_brillig_output: bool, print_timings: bool, -) -> Result<(Vec, Vec), RuntimeError> { +) -> Result { let ssa_gen_span = span!(Level::TRACE, "ssa_generation"); let ssa_gen_span_guard = ssa_gen_span.enter(); let ssa = SsaBuilder::new(program, print_passes, force_brillig_output, print_timings)? @@ -97,10 +103,14 @@ pub struct SsaProgramArtifact { pub main_input_witnesses: Vec, pub main_return_witnesses: Vec, pub names: Vec, + pub error_types: BTreeMap, } impl SsaProgramArtifact { - fn new(unconstrained_functions: Vec) -> Self { + fn new( + unconstrained_functions: Vec, + error_types: BTreeMap, + ) -> Self { let program = AcirProgram { functions: Vec::default(), unconstrained_functions }; Self { program, @@ -109,6 +119,7 @@ impl SsaProgramArtifact { main_input_witnesses: Vec::default(), main_return_witnesses: Vec::default(), names: Vec::default(), + error_types, } } @@ -143,7 +154,7 @@ pub fn create_program( let func_sigs = program.function_signatures.clone(); let recursive = program.recursive; - let (generated_acirs, generated_brillig) = optimize_into_acir( + let (generated_acirs, generated_brillig, error_types) = optimize_into_acir( program, enable_ssa_logging, enable_brillig_logging, @@ -156,7 +167,12 @@ pub fn create_program( "The generated ACIRs should match the supplied function signatures" ); - let mut program_artifact = SsaProgramArtifact::new(generated_brillig); + let error_types = error_types + .into_iter() + .map(|(error_typ_id, error_typ)| (error_typ_id.to_u64(), error_typ)) + .collect(); + + let mut program_artifact = SsaProgramArtifact::new(generated_brillig, error_types); // For setting up the ABI we need separately specify main's input and return witnesses let mut is_main = true; for (acir, func_sig) in generated_acirs.into_iter().zip(func_sigs) { @@ -199,7 +215,7 @@ fn convert_generated_acir_into_circuit( return_witnesses, locations, input_witnesses, - assert_messages, + assertion_payloads: assert_messages, warnings, name, .. diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index 3f5e4129dd0..2d546bc7d86 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -9,7 +9,7 @@ use crate::ssa::ir::types::Type as SsaType; use crate::ssa::ir::{instruction::Endian, types::NumericType}; use acvm::acir::circuit::brillig::{BrilligInputs, BrilligOutputs}; use acvm::acir::circuit::opcodes::{BlockId, MemOp}; -use acvm::acir::circuit::Opcode; +use acvm::acir::circuit::{AssertionPayload, ExpressionOrMemory, Opcode}; use acvm::blackbox_solver; use acvm::brillig_vm::{MemoryValue, VMStatus, VM}; use acvm::{ @@ -495,7 +495,7 @@ impl AcirContext { &mut self, lhs: AcirVar, rhs: AcirVar, - assert_message: Option, + assert_message: Option, ) -> Result<(), RuntimeError> { let lhs_expr = self.var_to_expression(lhs)?; let rhs_expr = self.var_to_expression(rhs)?; @@ -511,14 +511,38 @@ impl AcirContext { } self.acir_ir.assert_is_zero(diff_expr); - if let Some(message) = assert_message { - self.acir_ir.assert_messages.insert(self.acir_ir.last_acir_opcode_location(), message); + if let Some(payload) = assert_message { + self.acir_ir + .assertion_payloads + .insert(self.acir_ir.last_acir_opcode_location(), payload); } self.mark_variables_equivalent(lhs, rhs)?; Ok(()) } + pub(crate) fn vars_to_expressions_or_memory( + &self, + values: &[AcirValue], + ) -> Result, RuntimeError> { + let mut result = Vec::with_capacity(values.len()); + for value in values { + match value { + AcirValue::Var(var, _) => { + result.push(ExpressionOrMemory::Expression(self.var_to_expression(*var)?)); + } + AcirValue::Array(vars) => { + let vars_as_vec: Vec<_> = vars.iter().cloned().collect(); + result.extend(self.vars_to_expressions_or_memory(&vars_as_vec)?); + } + AcirValue::DynamicArray(AcirDynamicArray { block_id, .. }) => { + result.push(ExpressionOrMemory::Memory(*block_id)); + } + } + } + Ok(result) + } + /// Adds a new Variable to context whose value will /// be constrained to be the division of `lhs` and `rhs` pub(crate) fn div_var( @@ -985,9 +1009,10 @@ impl AcirContext { let witness = self.var_to_witness(witness_var)?; self.acir_ir.range_constraint(witness, *bit_size)?; if let Some(message) = message { - self.acir_ir - .assert_messages - .insert(self.acir_ir.last_acir_opcode_location(), message); + self.acir_ir.assertion_payloads.insert( + self.acir_ir.last_acir_opcode_location(), + AssertionPayload::StaticString(message.clone()), + ); } } NumericType::NativeField => { diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index 9f305a28c25..2f4f4f9f6cc 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -7,12 +7,11 @@ use crate::{ errors::{InternalError, RuntimeError, SsaReport}, ssa::ir::dfg::CallStack, }; - use acvm::acir::{ circuit::{ brillig::{BrilligInputs, BrilligOutputs}, opcodes::{BlackBoxFuncCall, FunctionInput, Opcode as AcirOpcode}, - OpcodeLocation, + AssertionPayload, OpcodeLocation, }, native_types::Witness, BlackBoxFunc, @@ -61,7 +60,7 @@ pub(crate) struct GeneratedAcir { pub(crate) call_stack: CallStack, /// Correspondence between an opcode index and the error message associated with it. - pub(crate) assert_messages: BTreeMap, + pub(crate) assertion_payloads: BTreeMap, pub(crate) warnings: Vec, @@ -284,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], @@ -603,12 +609,12 @@ impl GeneratedAcir { ); } for (brillig_index, message) in generated_brillig.assert_messages.iter() { - self.assert_messages.insert( + self.assertion_payloads.insert( OpcodeLocation::Brillig { acir_index: self.opcodes.len() - 1, brillig_index: *brillig_index, }, - message.clone(), + AssertionPayload::StaticString(message.clone()), ); } } @@ -670,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, @@ -724,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/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index 65cd9c05992..c097dfaf88e 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -9,7 +9,8 @@ use self::acir_ir::generated_acir::BrilligStdlibFunc; use super::function_builder::data_bus::DataBus; use super::ir::dfg::CallStack; use super::ir::function::FunctionId; -use super::ir::instruction::{ConstrainError, UserDefinedConstrainError}; +use super::ir::instruction::{ConstrainError, ErrorSelector, ErrorType}; +use super::ir::printer::try_to_extract_string_from_error_payload; use super::{ ir::{ dfg::DataFlowGraph, @@ -31,7 +32,7 @@ pub(crate) use acir_ir::generated_acir::GeneratedAcir; use noirc_frontend::monomorphization::ast::InlineType; use acvm::acir::circuit::brillig::BrilligBytecode; -use acvm::acir::circuit::OpcodeLocation; +use acvm::acir::circuit::{AssertionPayload, OpcodeLocation}; use acvm::acir::native_types::Witness; use acvm::acir::BlackBoxFunc; use acvm::{ @@ -275,12 +276,12 @@ impl AcirValue { } } +pub(crate) type Artifacts = + (Vec, Vec, BTreeMap); + impl Ssa { #[tracing::instrument(level = "trace", skip_all)] - pub(crate) fn into_acir( - self, - brillig: &Brillig, - ) -> Result<(Vec, Vec), RuntimeError> { + pub(crate) fn into_acir(self, brillig: &Brillig) -> Result { let mut acirs = Vec::new(); // TODO: can we parallelise this? let mut shared_context = SharedContext::default(); @@ -613,24 +614,39 @@ impl<'a> Context<'a> { let lhs = self.convert_numeric_value(*lhs, dfg)?; let rhs = self.convert_numeric_value(*rhs, dfg)?; - let assert_message = if let Some(error) = assert_message { - match error.as_ref() { - ConstrainError::Intrinsic(string) - | ConstrainError::UserDefined(UserDefinedConstrainError::Static(string)) => { - Some(string.clone()) + let assert_payload = if let Some(error) = assert_message { + match error { + ConstrainError::Intrinsic(string) => { + Some(AssertionPayload::StaticString(string.clone())) } - ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic( - call_instruction, - )) => { - self.convert_ssa_call(call_instruction, dfg, ssa, brillig, &[])?; - None + ConstrainError::UserDefined(error_selector, values) => { + if let Some(constant_string) = try_to_extract_string_from_error_payload( + *error_selector, + values, + dfg, + ) { + Some(AssertionPayload::StaticString(constant_string)) + } else { + let acir_vars: Vec<_> = values + .iter() + .map(|value| self.convert_value(*value, dfg)) + .collect(); + + let expressions_or_memory = + self.acir_context.vars_to_expressions_or_memory(&acir_vars)?; + + Some(AssertionPayload::Dynamic( + error_selector.to_u64(), + expressions_or_memory, + )) + } } } } else { None }; - self.acir_context.assert_eq_var(lhs, rhs, assert_message)?; + self.acir_context.assert_eq_var(lhs, rhs, assert_payload)?; } Instruction::Cast(value_id, _) => { let acir_var = self.convert_numeric_value(*value_id, dfg)?; @@ -2703,7 +2719,7 @@ mod test { let ssa = builder.finish(); - let (acir_functions, _) = ssa + let (acir_functions, _, _) = ssa .into_acir(&Brillig::default()) .expect("Should compile manually written SSA into ACIR"); // Expected result: @@ -2798,7 +2814,7 @@ mod test { let ssa = builder.finish(); - let (acir_functions, _) = ssa + let (acir_functions, _, _) = ssa .into_acir(&Brillig::default()) .expect("Should compile manually written SSA into ACIR"); // The expected result should look very similar to the above test expect that the input witnesses of the `Call` @@ -2888,7 +2904,7 @@ mod test { let ssa = builder.finish(); - let (acir_functions, _) = ssa + let (acir_functions, _, _) = ssa .into_acir(&Brillig::default()) .expect("Should compile manually written SSA into ACIR"); @@ -3001,9 +3017,8 @@ mod test { let ssa = builder.finish(); let brillig = ssa.to_brillig(false); - println!("{}", ssa); - let (acir_functions, brillig_functions) = + let (acir_functions, brillig_functions, _) = ssa.into_acir(&brillig).expect("Should compile manually written SSA into ACIR"); assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); @@ -3059,7 +3074,7 @@ mod test { // The Brillig bytecode we insert for the stdlib is hardcoded so we do not need to provide any // Brillig artifacts to the ACIR gen pass. - let (acir_functions, brillig_functions) = ssa + let (acir_functions, brillig_functions, _) = ssa .into_acir(&Brillig::default()) .expect("Should compile manually written SSA into ACIR"); @@ -3131,7 +3146,7 @@ mod test { let brillig = ssa.to_brillig(false); println!("{}", ssa); - let (acir_functions, brillig_functions) = + let (acir_functions, brillig_functions, _) = ssa.into_acir(&brillig).expect("Should compile manually written SSA into ACIR"); assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); @@ -3219,7 +3234,7 @@ mod test { let brillig = ssa.to_brillig(false); println!("{}", ssa); - let (acir_functions, brillig_functions) = + let (acir_functions, brillig_functions, _) = ssa.into_acir(&brillig).expect("Should compile manually written SSA into ACIR"); assert_eq!(acir_functions.len(), 2, "Should only have two ACIR functions"); diff --git a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index 091ab294edc..4b277ea244d 100644 --- a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -1,6 +1,6 @@ pub(crate) mod data_bus; -use std::{borrow::Cow, rc::Rc}; +use std::{borrow::Cow, collections::BTreeMap, rc::Rc}; use acvm::FieldElement; use noirc_errors::Location; @@ -9,7 +9,7 @@ use noirc_frontend::monomorphization::ast::InlineType; use crate::ssa::ir::{ basic_block::BasicBlockId, function::{Function, FunctionId}, - instruction::{Binary, BinaryOp, Instruction, TerminatorInstruction}, + instruction::{Binary, BinaryOp, ErrorSelector, Instruction, TerminatorInstruction}, types::Type, value::{Value, ValueId}, }; @@ -19,7 +19,7 @@ use super::{ basic_block::BasicBlock, dfg::{CallStack, InsertInstructionResult}, function::RuntimeType, - instruction::{ConstrainError, InstructionId, Intrinsic}, + instruction::{ConstrainError, ErrorType, InstructionId, Intrinsic}, }, ssa_gen::Ssa, }; @@ -36,6 +36,7 @@ pub(crate) struct FunctionBuilder { current_block: BasicBlockId, finished_functions: Vec, call_stack: CallStack, + error_types: BTreeMap, } impl FunctionBuilder { @@ -51,6 +52,7 @@ impl FunctionBuilder { current_function: new_function, finished_functions: Vec::new(), call_stack: CallStack::new(), + error_types: BTreeMap::default(), } } @@ -100,7 +102,7 @@ impl FunctionBuilder { /// Consume the FunctionBuilder returning all the functions it has generated. pub(crate) fn finish(mut self) -> Ssa { self.finished_functions.push(self.current_function); - Ssa::new(self.finished_functions) + Ssa::new(self.finished_functions, self.error_types) } /// Add a parameter to the current function with the given parameter type. @@ -269,7 +271,7 @@ impl FunctionBuilder { &mut self, lhs: ValueId, rhs: ValueId, - assert_message: Option>, + assert_message: Option, ) { self.insert_instruction(Instruction::Constrain(lhs, rhs, assert_message), None); } @@ -480,6 +482,10 @@ impl FunctionBuilder { } } } + + pub(crate) fn record_error_type(&mut self, selector: ErrorSelector, typ: ErrorType) { + self.error_types.insert(selector, typ); + } } impl std::ops::Index for FunctionBuilder { diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 04f33d528cd..582e00b6be2 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -1,5 +1,12 @@ -use acvm::{acir::BlackBoxFunc, FieldElement}; +use std::hash::{Hash, Hasher}; + +use acvm::{ + acir::{circuit::STRING_ERROR_SELECTOR, BlackBoxFunc}, + FieldElement, +}; +use fxhash::FxHasher; use iter_extended::vecmap; +use noirc_frontend::hir_def::types::Type as HirType; use super::{ basic_block::BasicBlockId, @@ -157,7 +164,7 @@ pub(crate) enum Instruction { Truncate { value: ValueId, bit_size: u32, max_bit_size: u32 }, /// Constrains two values to be equal to one another. - Constrain(ValueId, ValueId, Option>), + Constrain(ValueId, ValueId, Option), /// Range constrain `value` to `max_bit_size` RangeCheck { value: ValueId, max_bit_size: u32, assert_message: Option }, @@ -346,12 +353,12 @@ impl Instruction { // Must map the `lhs` and `rhs` first as the value `f` is moved with the closure let lhs = f(*lhs); let rhs = f(*rhs); - let assert_message = assert_message.as_ref().map(|error| match error.as_ref() { - ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic(call_instr)) => { - let new_instr = call_instr.map_values(f); - Box::new(ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic( - new_instr, - ))) + let assert_message = assert_message.as_ref().map(|error| match error { + ConstrainError::UserDefined(selector, payload_values) => { + ConstrainError::UserDefined( + *selector, + payload_values.iter().map(|&value| f(value)).collect(), + ) } _ => error.clone(), }); @@ -412,13 +419,10 @@ impl Instruction { Instruction::Constrain(lhs, rhs, assert_error) => { f(*lhs); f(*rhs); - if let Some(error) = assert_error.as_ref() { - if let ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic( - call_instr, - )) = error.as_ref() - { - call_instr.for_each_value(f); - } + if let Some(ConstrainError::UserDefined(_, values)) = assert_error.as_ref() { + values.iter().for_each(|&val| { + f(val); + }); } } @@ -599,22 +603,36 @@ impl Instruction { } } +pub(crate) type ErrorType = HirType; + +#[derive(Debug, Copy, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)] +pub(crate) struct ErrorSelector(u64); + +impl ErrorSelector { + pub(crate) fn new(typ: &ErrorType) -> Self { + match typ { + ErrorType::String(_) => Self(STRING_ERROR_SELECTOR), + _ => { + let mut hasher = FxHasher::default(); + typ.hash(&mut hasher); + let hash = hasher.finish(); + assert!(hash != 0, "ICE: Error type {} collides with the string error type", typ); + Self(hash) + } + } + } + + pub(crate) fn to_u64(self) -> u64 { + self.0 + } +} + #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub(crate) enum ConstrainError { // These are errors which have been hardcoded during SSA gen Intrinsic(String), // These are errors issued by the user - UserDefined(UserDefinedConstrainError), -} - -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub(crate) enum UserDefinedConstrainError { - // These are errors which come from static strings specified by a Noir program - Static(String), - // These are errors which come from runtime expressions specified by a Noir program - // We store an `Instruction` as we want this Instruction to be atomic in SSA with - // a constrain instruction, and leave codegen of this instruction to lower level passes. - Dynamic(Instruction), + UserDefined(ErrorSelector, Vec), } impl From for ConstrainError { diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index 1187ea8cb07..a8365ffef39 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/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/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs index b4198e2cfec..d844f350927 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs @@ -7,7 +7,7 @@ use super::{Binary, BinaryOp, ConstrainError, DataFlowGraph, Instruction, Type, pub(super) fn decompose_constrain( lhs: ValueId, rhs: ValueId, - msg: &Option>, + msg: &Option, dfg: &mut DataFlowGraph, ) -> Vec { let lhs = dfg.resolve(lhs); diff --git a/compiler/noirc_evaluator/src/ssa/ir/post_order.rs b/compiler/noirc_evaluator/src/ssa/ir/post_order.rs index d95ec451779..94ff96ba1d7 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/post_order.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/post_order.rs @@ -51,7 +51,7 @@ impl PostOrder { // stack, we push the item that's due for a visit first to the top. for successor_id in func.dfg[block_id].successors().rev() { if !visited.contains(&successor_id) { - // This not visited check would also be cover by the the next + // This not visited check would also be cover by the next // iteration, but checking here two saves an iteration per successor. stack.push((Visit::First, successor_id)); } diff --git a/compiler/noirc_evaluator/src/ssa/ir/printer.rs b/compiler/noirc_evaluator/src/ssa/ir/printer.rs index d17d2989341..bbff3cf8acb 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/printer.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/printer.rs @@ -4,16 +4,17 @@ use std::{ fmt::{Formatter, Result}, }; +use acvm::acir::circuit::STRING_ERROR_SELECTOR; use iter_extended::vecmap; use super::{ basic_block::BasicBlockId, + dfg::DataFlowGraph, function::Function, instruction::{ - ConstrainError, Instruction, InstructionId, TerminatorInstruction, - UserDefinedConstrainError, + ConstrainError, ErrorSelector, Instruction, InstructionId, TerminatorInstruction, }, - value::ValueId, + value::{Value, ValueId}, }; /// Helper function for Function's Display impl to pretty-print the function with the given formatter. @@ -63,7 +64,6 @@ pub(crate) fn display_block( /// Specialize displaying value ids so that if they refer to a numeric /// constant or a function we print those directly. fn value(function: &Function, id: ValueId) -> String { - use super::value::Value; let id = function.dfg.resolve(id); match &function.dfg[id] { Value::NumericConstant { constant, typ } => { @@ -159,7 +159,6 @@ fn display_instruction_inner( Instruction::Constrain(lhs, rhs, error) => { write!(f, "constrain {} == {}", show(*lhs), show(*rhs))?; if let Some(error) = error { - write!(f, " ")?; display_constrain_error(function, error, f) } else { writeln!(f) @@ -198,18 +197,51 @@ fn display_instruction_inner( } } +/// Tries to extract a constant string from an error payload. +pub(crate) fn try_to_extract_string_from_error_payload( + error_selector: ErrorSelector, + values: &[ValueId], + dfg: &DataFlowGraph, +) -> Option { + ((error_selector.to_u64() == STRING_ERROR_SELECTOR) && (values.len() == 1)) + .then_some(()) + .and_then(|()| { + let Value::Array { array: values, .. } = &dfg[values[0]] else { + return None; + }; + let fields: Option> = + values.iter().map(|value_id| dfg.get_numeric_constant(*value_id)).collect(); + + fields + }) + .map(|fields| { + fields + .iter() + .map(|field| { + let as_u8 = field.try_to_u64().unwrap_or_default() as u8; + as_u8 as char + }) + .collect() + }) +} + fn display_constrain_error( function: &Function, error: &ConstrainError, f: &mut Formatter, ) -> Result { match error { - ConstrainError::Intrinsic(assert_message_string) - | ConstrainError::UserDefined(UserDefinedConstrainError::Static(assert_message_string)) => { - writeln!(f, "{assert_message_string:?}") - } - ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic(assert_message_call)) => { - display_instruction_inner(function, assert_message_call, f) + ConstrainError::Intrinsic(assert_message_string) => { + writeln!(f, " '{assert_message_string:?}'") + } + ConstrainError::UserDefined(selector, values) => { + if let Some(constant_string) = + try_to_extract_string_from_error_payload(*selector, values, &function.dfg) + { + writeln!(f, " '{}'", constant_string) + } else { + writeln!(f, ", data {}", value_list(function, values)) + } } } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs b/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs index aa0368cc2dd..cfeb8751f25 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs @@ -14,7 +14,7 @@ use crate::ssa::{ ir::{ basic_block::BasicBlockId, function::{Function, FunctionId, Signature}, - instruction::{BinaryOp, ConstrainError, Instruction, UserDefinedConstrainError}, + instruction::{BinaryOp, Instruction}, types::{NumericType, Type}, value::{Value, ValueId}, }, @@ -90,18 +90,6 @@ impl DefunctionalizationContext { Instruction::Call { func: target_func_id, arguments } => { (*target_func_id, arguments) } - // Constrain instruction potentially hold a call instruction themselves - // thus we need to account for them. - Instruction::Constrain(_, _, Some(constrain_error)) => { - if let ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic( - Instruction::Call { func: target_func_id, arguments }, - )) = constrain_error.as_ref() - { - (*target_func_id, arguments) - } else { - continue; - } - } _ => continue, }; @@ -133,22 +121,7 @@ impl DefunctionalizationContext { } _ => {} } - if let Some(mut new_instruction) = replacement_instruction { - if let Instruction::Constrain(lhs, rhs, constrain_error_call) = instruction { - let new_error_call = if let Some(error) = constrain_error_call { - match error.as_ref() { - ConstrainError::UserDefined( - UserDefinedConstrainError::Dynamic(_), - ) => Some(Box::new(ConstrainError::UserDefined( - UserDefinedConstrainError::Dynamic(new_instruction), - ))), - _ => None, - } - } else { - None - }; - new_instruction = Instruction::Constrain(lhs, rhs, new_error_call); - } + if let Some(new_instruction) = replacement_instruction { func.dfg[instruction_id] = new_instruction; } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs index 81e3023f3f8..42727054503 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs @@ -97,7 +97,7 @@ impl Context<'_> { let typ = self.function.dfg.type_of_value(lhs); let (max_bit, pow) = if let Some(rhs_constant) = self.function.dfg.get_numeric_constant(rhs) { - // Happy case is that we know precisely by how many bits the the integer will + // Happy case is that we know precisely by how many bits the integer will // increase: lhs_bit_size + rhs let bit_shift_size = rhs_constant.to_u128() as u32; diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index ebde3dcf66e..c121ac19ff9 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -8,10 +8,11 @@ use context::SharedContext; use iter_extended::{try_vecmap, vecmap}; use noirc_errors::Location; use noirc_frontend::ast::{UnaryOp, Visibility}; +use noirc_frontend::hir_def::types::Type as HirType; use noirc_frontend::monomorphization::ast::{self, Expression, Program}; use crate::{ - errors::{InternalError, RuntimeError}, + errors::RuntimeError, ssa::{function_builder::data_bus::DataBusBuilder, ir::instruction::Intrinsic}, }; @@ -20,13 +21,12 @@ use self::{ value::{Tree, Values}, }; +use super::ir::instruction::ErrorSelector; use super::{ function_builder::data_bus::DataBus, ir::{ function::RuntimeType, - instruction::{ - BinaryOp, ConstrainError, Instruction, TerminatorInstruction, UserDefinedConstrainError, - }, + instruction::{BinaryOp, ConstrainError, TerminatorInstruction}, types::Type, value::ValueId, }, @@ -146,8 +146,8 @@ impl<'a> FunctionContext<'a> { } Expression::Call(call) => self.codegen_call(call), Expression::Let(let_expr) => self.codegen_let(let_expr), - Expression::Constrain(expr, location, assert_message) => { - self.codegen_constrain(expr, *location, assert_message) + Expression::Constrain(expr, location, assert_payload) => { + self.codegen_constrain(expr, *location, assert_payload) } Expression::Assign(assign) => self.codegen_assign(assign), Expression::Semi(semi) => self.codegen_semi(semi), @@ -448,7 +448,7 @@ impl<'a> FunctionContext<'a> { self.builder.insert_constrain( is_offset_out_of_bounds, true_const, - Some(Box::new("Index out of bounds".to_owned().into())), + Some("Index out of bounds".to_owned().into()), ); } @@ -675,7 +675,7 @@ impl<'a> FunctionContext<'a> { &mut self, expr: &Expression, location: Location, - assert_message: &Option>, + assert_payload: &Option>, ) -> Result { let expr = self.codegen_non_tuple_expression(expr)?; let true_literal = self.builder.numeric_constant(true, Type::bool()); @@ -683,9 +683,9 @@ impl<'a> FunctionContext<'a> { // Set the location here for any errors that may occur when we codegen the assert message self.builder.set_location(location); - let assert_message = self.codegen_constrain_error(assert_message)?; + let assert_payload = self.codegen_constrain_error(assert_payload)?; - self.builder.insert_constrain(expr, true_literal, assert_message); + self.builder.insert_constrain(expr, true_literal, assert_payload); Ok(Self::unit_value()) } @@ -696,42 +696,23 @@ impl<'a> FunctionContext<'a> { // inserted to the SSA as we want that instruction to be atomic in SSA with a constrain instruction. fn codegen_constrain_error( &mut self, - assert_message: &Option>, - ) -> Result>, RuntimeError> { - let Some(assert_message_expr) = assert_message else { return Ok(None) }; + assert_message: &Option>, + ) -> Result, RuntimeError> { + let Some(assert_message_payload) = assert_message else { return Ok(None) }; + let (assert_message_expression, assert_message_typ) = assert_message_payload.as_ref(); - if let ast::Expression::Literal(ast::Literal::Str(assert_message)) = - assert_message_expr.as_ref() - { - return Ok(Some(Box::new(ConstrainError::UserDefined( - UserDefinedConstrainError::Static(assert_message.to_string()), - )))); - } + let values = self.codegen_expression(assert_message_expression)?.into_value_list(self); + + let error_type_id = ErrorSelector::new(assert_message_typ); - let ast::Expression::Call(call) = assert_message_expr.as_ref() else { - return Err(InternalError::Unexpected { - expected: "Expected a call expression".to_owned(), - found: "Instead found {expr:?}".to_owned(), - call_stack: self.builder.get_call_stack(), + // Do not record string errors in the ABI + match assert_message_typ { + HirType::String(_) => {} + _ => { + self.builder.record_error_type(error_type_id, assert_message_typ.clone()); } - .into()); }; - - let func = self.codegen_non_tuple_expression(&call.func)?; - let mut arguments = Vec::with_capacity(call.arguments.len()); - - for argument in &call.arguments { - let mut values = self.codegen_expression(argument)?.into_value_list(self); - arguments.append(&mut values); - } - - // If an array is passed as an argument we increase its reference count - for argument in &arguments { - self.builder.increment_array_reference_count(*argument); - } - - let instr = Instruction::Call { func, arguments }; - Ok(Some(Box::new(ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic(instr))))) + Ok(Some(ConstrainError::UserDefined(error_type_id, values))) } fn codegen_assign(&mut self, assign: &ast::Assign) -> Result { diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs index b05a2cbc741..03d4ac05bd9 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs @@ -4,8 +4,10 @@ use iter_extended::btree_map; use crate::ssa::ir::{ function::{Function, FunctionId, RuntimeType}, + instruction::ErrorSelector, map::AtomicCounter, }; +use noirc_frontend::hir_def::types::Type as HirType; /// Contains the entire SSA representation of the program. pub(crate) struct Ssa { @@ -17,12 +19,16 @@ pub(crate) struct Ssa { /// This mapping is necessary to use the correct function pointer for an ACIR call, /// as the final program artifact will be a list of only entry point functions. pub(crate) entry_point_to_generated_index: BTreeMap, + pub(crate) error_selector_to_type: BTreeMap, } impl Ssa { /// Create a new Ssa object from the given SSA functions. /// The first function in this vector is expected to be the main function. - pub(crate) fn new(functions: Vec) -> Self { + pub(crate) fn new( + functions: Vec, + error_types: BTreeMap, + ) -> Self { let main_id = functions.first().expect("Expected at least 1 SSA function").id(); let mut max_id = main_id; @@ -50,6 +56,7 @@ impl Ssa { main_id, next_id: AtomicCounter::starting_after(max_id), entry_point_to_generated_index, + error_selector_to_type: error_types, } } diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 1903d113586..26b7c212a30 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -163,7 +163,7 @@ impl<'a> Interpreter<'a> { self.scopes.last_mut().unwrap() } - fn define_pattern( + pub(super) fn define_pattern( &mut self, pattern: &HirPattern, typ: &Type, @@ -265,7 +265,7 @@ impl<'a> Interpreter<'a> { Err(InterpreterError::NonComptimeVarReferenced { name, location }) } - fn lookup(&self, ident: &HirIdent) -> IResult { + pub(super) fn lookup(&self, ident: &HirIdent) -> IResult { self.lookup_id(ident.id, ident.location) } @@ -294,7 +294,7 @@ impl<'a> Interpreter<'a> { } /// Evaluate an expression and return the result - fn evaluate(&mut self, id: ExprId) -> IResult { + pub(super) fn evaluate(&mut self, id: ExprId) -> IResult { match self.interner.expression(&id) { HirExpression::Ident(ident) => self.evaluate_ident(ident, id), HirExpression::Literal(literal) => self.evaluate_literal(literal, id), diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 6c07957b27f..60baaecab59 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -1292,15 +1292,14 @@ impl<'a> Resolver<'a> { }) } StatementKind::Constrain(constrain_stmt) => { - let span = constrain_stmt.0.span; - let assert_msg_call_expr_id = - self.resolve_assert_message(constrain_stmt.1, span, constrain_stmt.0.clone()); let expr_id = self.resolve_expression(constrain_stmt.0); + let assert_message_expr_id = + constrain_stmt.1.map(|assert_expr_id| self.resolve_expression(assert_expr_id)); HirStatement::Constrain(HirConstrainStatement( expr_id, self.file, - assert_msg_call_expr_id, + assert_message_expr_id, )) } StatementKind::Expression(expr) => { @@ -1368,48 +1367,6 @@ impl<'a> Resolver<'a> { } } - fn resolve_assert_message( - &mut self, - assert_message_expr: Option, - span: Span, - condition: Expression, - ) -> Option { - let assert_message_expr = assert_message_expr?; - - if matches!( - assert_message_expr, - Expression { kind: ExpressionKind::Literal(Literal::Str(..)), .. } - ) { - return Some(self.resolve_expression(assert_message_expr)); - } - - let is_in_stdlib = self.path_resolver.module_id().krate.is_stdlib(); - let assert_msg_call_path = if is_in_stdlib { - ExpressionKind::Variable(Path { - segments: vec![Ident::from("internal"), Ident::from("resolve_assert_message")], - kind: PathKind::Crate, - span, - }) - } else { - ExpressionKind::Variable(Path { - segments: vec![ - Ident::from("std"), - Ident::from("internal"), - Ident::from("resolve_assert_message"), - ], - kind: PathKind::Dep, - span, - }) - }; - let assert_msg_call_args = vec![assert_message_expr.clone(), condition]; - let assert_msg_call_expr = Expression::call( - Expression { kind: assert_msg_call_path, span }, - assert_msg_call_args, - span, - ); - Some(self.resolve_expression(assert_msg_call_expr)) - } - pub fn intern_stmt(&mut self, stmt: Statement) -> StmtId { let hir_stmt = self.resolve_stmt(stmt.kind, stmt.span); let id = self.interner.push_stmt(hir_stmt); diff --git a/compiler/noirc_frontend/src/monomorphization/ast.rs b/compiler/noirc_frontend/src/monomorphization/ast.rs index 97a71f800a8..4bd501c07ba 100644 --- a/compiler/noirc_frontend/src/monomorphization/ast.rs +++ b/compiler/noirc_frontend/src/monomorphization/ast.rs @@ -5,6 +5,7 @@ use noirc_errors::{ Location, }; +use super::HirType; use crate::hir_def::function::FunctionSignature; use crate::{ ast::{BinaryOpKind, IntegerBitSize, Signedness, Visibility}, @@ -36,7 +37,7 @@ pub enum Expression { ExtractTupleField(Box, usize), Call(Call), Let(Let), - Constrain(Box, Location, Option>), + Constrain(Box, Location, Option>), Assign(Assign), Semi(Box), Break, diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 94cc6bcd849..f918610af2c 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -593,7 +593,11 @@ impl<'interner> Monomorphizer<'interner> { let location = self.interner.expr_location(&constrain.0); let assert_message = constrain .2 - .map(|assert_msg_expr| self.expr(assert_msg_expr)) + .map(|assert_msg_expr| { + self.expr(assert_msg_expr).map(|expr| { + (expr, self.interner.id_type(assert_msg_expr).follow_bindings()) + }) + }) .transpose()? .map(Box::new); Ok(ast::Expression::Constrain(Box::new(expr), location, assert_message)) @@ -1100,9 +1104,6 @@ impl<'interner> Monomorphizer<'interner> { // The first argument to the `print` oracle is a bool, indicating a newline to be inserted at the end of the input // The second argument is expected to always be an ident self.append_printable_type_info(&hir_arguments[1], &mut arguments); - } else if name.as_str() == "assert_message" { - // The first argument to the `assert_message` oracle is the expression passed as a message to an `assert` or `assert_eq` statement - self.append_printable_type_info(&hir_arguments[0], &mut arguments); } } } diff --git a/docs/docs/noir/standard_library/cryptographic_primitives/scalar.mdx b/docs/docs/noir/standard_library/cryptographic_primitives/scalar.mdx index c2946b2b73b..b835236a03e 100644 --- a/docs/docs/noir/standard_library/cryptographic_primitives/scalar.mdx +++ b/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/docs/docs/tutorials/noirjs_app.md b/docs/docs/tutorials/noirjs_app.md index 6446e0b2a76..3dd9fe7d2b0 100644 --- a/docs/docs/tutorials/noirjs_app.md +++ b/docs/docs/tutorials/noirjs_app.md @@ -24,7 +24,7 @@ Before we start, we want to make sure we have Node and Nargo installed. We start by opening a terminal and executing `node --version`. If we don't get an output like `v20.10.0`, that means node is not installed. Let's do that by following the handy [nvm guide](https://github.com/nvm-sh/nvm?tab=readme-ov-file#install--update-script). -As for `Nargo`, we can follow the the [Nargo guide](../getting_started/installation/index.md) to install it. If you're lazy, just paste this on a terminal and run `noirup`: +As for `Nargo`, we can follow the [Nargo guide](../getting_started/installation/index.md) to install it. If you're lazy, just paste this on a terminal and run `noirup`: ```sh curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash diff --git a/docs/versioned_docs/version-v0.17.0/noir_js/getting_started/01_tiny_noir_app.md b/docs/versioned_docs/version-v0.17.0/noir_js/getting_started/01_tiny_noir_app.md index 142cd02b94c..b3f1d6df747 100644 --- a/docs/versioned_docs/version-v0.17.0/noir_js/getting_started/01_tiny_noir_app.md +++ b/docs/versioned_docs/version-v0.17.0/noir_js/getting_started/01_tiny_noir_app.md @@ -18,7 +18,7 @@ In this guide, we will be pinned to 0.17.0. Make sure you have Node installed on your machine by opening a terminal and executing `node --version`. If you don't see a version, you should install [node](https://github.com/nvm-sh/nvm). You can also use `yarn` if you prefer that package manager over npm (which comes with node). -First of all, follow the the [Nargo guide](../../getting_started/00_nargo_installation.md) to install nargo version 0.17.0 and create a new project with `nargo new circuit`. Once there, `cd` into the `circuit` folder. You should then be able to compile your circuit into `json` format and see it inside the `target` folder: +First of all, follow the [Nargo guide](../../getting_started/00_nargo_installation.md) to install nargo version 0.17.0 and create a new project with `nargo new circuit`. Once there, `cd` into the `circuit` folder. You should then be able to compile your circuit into `json` format and see it inside the `target` folder: ```bash nargo compile diff --git a/docs/versioned_docs/version-v0.19.0/noir_js/getting_started/01_tiny_noir_app.md b/docs/versioned_docs/version-v0.19.0/noir_js/getting_started/01_tiny_noir_app.md index 795baa59d59..64ba4a2c44a 100644 --- a/docs/versioned_docs/version-v0.19.0/noir_js/getting_started/01_tiny_noir_app.md +++ b/docs/versioned_docs/version-v0.19.0/noir_js/getting_started/01_tiny_noir_app.md @@ -20,7 +20,7 @@ In this guide, we will be pinned to 0.17.0. Make sure you have Node installed on your machine by opening a terminal and executing `node --version`. If you don't see a version, you should install [node](https://github.com/nvm-sh/nvm). You can also use `yarn` if you prefer that package manager over npm (which comes with node). -First of all, follow the the [Nargo guide](../../getting_started/00_nargo_installation.md) to install nargo version 0.17.0 and create a new project with `nargo new circuit`. Once there, `cd` into the `circuit` folder. You should then be able to compile your circuit into `json` format and see it inside the `target` folder: +First of all, follow the [Nargo guide](../../getting_started/00_nargo_installation.md) to install nargo version 0.17.0 and create a new project with `nargo new circuit`. Once there, `cd` into the `circuit` folder. You should then be able to compile your circuit into `json` format and see it inside the `target` folder: ```bash nargo compile diff --git a/docs/versioned_docs/version-v0.19.1/noir_js/getting_started/01_tiny_noir_app.md b/docs/versioned_docs/version-v0.19.1/noir_js/getting_started/01_tiny_noir_app.md index 795baa59d59..64ba4a2c44a 100644 --- a/docs/versioned_docs/version-v0.19.1/noir_js/getting_started/01_tiny_noir_app.md +++ b/docs/versioned_docs/version-v0.19.1/noir_js/getting_started/01_tiny_noir_app.md @@ -20,7 +20,7 @@ In this guide, we will be pinned to 0.17.0. Make sure you have Node installed on your machine by opening a terminal and executing `node --version`. If you don't see a version, you should install [node](https://github.com/nvm-sh/nvm). You can also use `yarn` if you prefer that package manager over npm (which comes with node). -First of all, follow the the [Nargo guide](../../getting_started/00_nargo_installation.md) to install nargo version 0.17.0 and create a new project with `nargo new circuit`. Once there, `cd` into the `circuit` folder. You should then be able to compile your circuit into `json` format and see it inside the `target` folder: +First of all, follow the [Nargo guide](../../getting_started/00_nargo_installation.md) to install nargo version 0.17.0 and create a new project with `nargo new circuit`. Once there, `cd` into the `circuit` folder. You should then be able to compile your circuit into `json` format and see it inside the `target` folder: ```bash nargo compile diff --git a/docs/versioned_docs/version-v0.19.2/noir_js/getting_started/01_tiny_noir_app.md b/docs/versioned_docs/version-v0.19.2/noir_js/getting_started/01_tiny_noir_app.md index 795baa59d59..64ba4a2c44a 100644 --- a/docs/versioned_docs/version-v0.19.2/noir_js/getting_started/01_tiny_noir_app.md +++ b/docs/versioned_docs/version-v0.19.2/noir_js/getting_started/01_tiny_noir_app.md @@ -20,7 +20,7 @@ In this guide, we will be pinned to 0.17.0. Make sure you have Node installed on your machine by opening a terminal and executing `node --version`. If you don't see a version, you should install [node](https://github.com/nvm-sh/nvm). You can also use `yarn` if you prefer that package manager over npm (which comes with node). -First of all, follow the the [Nargo guide](../../getting_started/00_nargo_installation.md) to install nargo version 0.17.0 and create a new project with `nargo new circuit`. Once there, `cd` into the `circuit` folder. You should then be able to compile your circuit into `json` format and see it inside the `target` folder: +First of all, follow the [Nargo guide](../../getting_started/00_nargo_installation.md) to install nargo version 0.17.0 and create a new project with `nargo new circuit`. Once there, `cd` into the `circuit` folder. You should then be able to compile your circuit into `json` format and see it inside the `target` folder: ```bash nargo compile diff --git a/docs/versioned_docs/version-v0.19.3/noir_js/getting_started/01_tiny_noir_app.md b/docs/versioned_docs/version-v0.19.3/noir_js/getting_started/01_tiny_noir_app.md index 795baa59d59..64ba4a2c44a 100644 --- a/docs/versioned_docs/version-v0.19.3/noir_js/getting_started/01_tiny_noir_app.md +++ b/docs/versioned_docs/version-v0.19.3/noir_js/getting_started/01_tiny_noir_app.md @@ -20,7 +20,7 @@ In this guide, we will be pinned to 0.17.0. Make sure you have Node installed on your machine by opening a terminal and executing `node --version`. If you don't see a version, you should install [node](https://github.com/nvm-sh/nvm). You can also use `yarn` if you prefer that package manager over npm (which comes with node). -First of all, follow the the [Nargo guide](../../getting_started/00_nargo_installation.md) to install nargo version 0.17.0 and create a new project with `nargo new circuit`. Once there, `cd` into the `circuit` folder. You should then be able to compile your circuit into `json` format and see it inside the `target` folder: +First of all, follow the [Nargo guide](../../getting_started/00_nargo_installation.md) to install nargo version 0.17.0 and create a new project with `nargo new circuit`. Once there, `cd` into the `circuit` folder. You should then be able to compile your circuit into `json` format and see it inside the `target` folder: ```bash nargo compile diff --git a/docs/versioned_docs/version-v0.19.4/noir_js/getting_started/01_tiny_noir_app.md b/docs/versioned_docs/version-v0.19.4/noir_js/getting_started/01_tiny_noir_app.md index 795baa59d59..64ba4a2c44a 100644 --- a/docs/versioned_docs/version-v0.19.4/noir_js/getting_started/01_tiny_noir_app.md +++ b/docs/versioned_docs/version-v0.19.4/noir_js/getting_started/01_tiny_noir_app.md @@ -20,7 +20,7 @@ In this guide, we will be pinned to 0.17.0. Make sure you have Node installed on your machine by opening a terminal and executing `node --version`. If you don't see a version, you should install [node](https://github.com/nvm-sh/nvm). You can also use `yarn` if you prefer that package manager over npm (which comes with node). -First of all, follow the the [Nargo guide](../../getting_started/00_nargo_installation.md) to install nargo version 0.17.0 and create a new project with `nargo new circuit`. Once there, `cd` into the `circuit` folder. You should then be able to compile your circuit into `json` format and see it inside the `target` folder: +First of all, follow the [Nargo guide](../../getting_started/00_nargo_installation.md) to install nargo version 0.17.0 and create a new project with `nargo new circuit`. Once there, `cd` into the `circuit` folder. You should then be able to compile your circuit into `json` format and see it inside the `target` folder: ```bash nargo compile diff --git a/docs/versioned_docs/version-v0.22.0/tutorials/noirjs_app.md b/docs/versioned_docs/version-v0.22.0/tutorials/noirjs_app.md index 0763b6224c9..516e07c89fd 100644 --- a/docs/versioned_docs/version-v0.22.0/tutorials/noirjs_app.md +++ b/docs/versioned_docs/version-v0.22.0/tutorials/noirjs_app.md @@ -21,7 +21,7 @@ In this guide, we will be pinned to 0.17.0. Make sure you have Node installed on your machine by opening a terminal and executing `node --version`. If you don't see a version, you should install [node](https://github.com/nvm-sh/nvm). You can also use `yarn` if you prefer that package manager over npm (which comes with node). -First of all, follow the the [Nargo guide](../getting_started/installation/index.md) to install nargo version 0.17.0 and create a new project with `nargo new circuit`. Once there, `cd` into the `circuit` folder. You should then be able to compile your circuit into `json` format and see it inside the `target` folder: +First of all, follow the [Nargo guide](../getting_started/installation/index.md) to install nargo version 0.17.0 and create a new project with `nargo new circuit`. Once there, `cd` into the `circuit` folder. You should then be able to compile your circuit into `json` format and see it inside the `target` folder: ```bash nargo compile diff --git a/docs/versioned_docs/version-v0.23.0/tutorials/noirjs_app.md b/docs/versioned_docs/version-v0.23.0/tutorials/noirjs_app.md index 82899217e61..e3bc8c58b24 100644 --- a/docs/versioned_docs/version-v0.23.0/tutorials/noirjs_app.md +++ b/docs/versioned_docs/version-v0.23.0/tutorials/noirjs_app.md @@ -24,7 +24,7 @@ Before we start, we want to make sure we have Node and Nargo installed. We start by opening a terminal and executing `node --version`. If we don't get an output like `v20.10.0`, that means node is not installed. Let's do that by following the handy [nvm guide](https://github.com/nvm-sh/nvm?tab=readme-ov-file#install--update-script). -As for `Nargo`, we can follow the the [Nargo guide](../getting_started/installation/index.md) to install it. If you're lazy, just paste this on a terminal and run `noirup`: +As for `Nargo`, we can follow the [Nargo guide](../getting_started/installation/index.md) to install it. If you're lazy, just paste this on a terminal and run `noirup`: ```sh curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash diff --git a/docs/versioned_docs/version-v0.24.0/tutorials/noirjs_app.md b/docs/versioned_docs/version-v0.24.0/tutorials/noirjs_app.md index 12beb476994..0d75e2e8045 100644 --- a/docs/versioned_docs/version-v0.24.0/tutorials/noirjs_app.md +++ b/docs/versioned_docs/version-v0.24.0/tutorials/noirjs_app.md @@ -24,7 +24,7 @@ Before we start, we want to make sure we have Node and Nargo installed. We start by opening a terminal and executing `node --version`. If we don't get an output like `v20.10.0`, that means node is not installed. Let's do that by following the handy [nvm guide](https://github.com/nvm-sh/nvm?tab=readme-ov-file#install--update-script). -As for `Nargo`, we can follow the the [Nargo guide](../getting_started/installation/index.md) to install it. If you're lazy, just paste this on a terminal and run `noirup`: +As for `Nargo`, we can follow the [Nargo guide](../getting_started/installation/index.md) to install it. If you're lazy, just paste this on a terminal and run `noirup`: ```sh curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash diff --git a/docs/versioned_docs/version-v0.25.0/tutorials/noirjs_app.md b/docs/versioned_docs/version-v0.25.0/tutorials/noirjs_app.md index 02044cd2ff6..0cea72314ce 100644 --- a/docs/versioned_docs/version-v0.25.0/tutorials/noirjs_app.md +++ b/docs/versioned_docs/version-v0.25.0/tutorials/noirjs_app.md @@ -24,7 +24,7 @@ Before we start, we want to make sure we have Node and Nargo installed. We start by opening a terminal and executing `node --version`. If we don't get an output like `v20.10.0`, that means node is not installed. Let's do that by following the handy [nvm guide](https://github.com/nvm-sh/nvm?tab=readme-ov-file#install--update-script). -As for `Nargo`, we can follow the the [Nargo guide](../getting_started/installation/index.md) to install it. If you're lazy, just paste this on a terminal and run `noirup`: +As for `Nargo`, we can follow the [Nargo guide](../getting_started/installation/index.md) to install it. If you're lazy, just paste this on a terminal and run `noirup`: ```sh curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash diff --git a/docs/versioned_docs/version-v0.26.0/tutorials/noirjs_app.md b/docs/versioned_docs/version-v0.26.0/tutorials/noirjs_app.md index d098827e6d9..a6dda7e08c3 100644 --- a/docs/versioned_docs/version-v0.26.0/tutorials/noirjs_app.md +++ b/docs/versioned_docs/version-v0.26.0/tutorials/noirjs_app.md @@ -24,7 +24,7 @@ Before we start, we want to make sure we have Node and Nargo installed. We start by opening a terminal and executing `node --version`. If we don't get an output like `v20.10.0`, that means node is not installed. Let's do that by following the handy [nvm guide](https://github.com/nvm-sh/nvm?tab=readme-ov-file#install--update-script). -As for `Nargo`, we can follow the the [Nargo guide](../getting_started/installation/index.md) to install it. If you're lazy, just paste this on a terminal and run `noirup`: +As for `Nargo`, we can follow the [Nargo guide](../getting_started/installation/index.md) to install it. If you're lazy, just paste this on a terminal and run `noirup`: ```sh curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash diff --git a/docs/versioned_docs/version-v0.27.0/tutorials/noirjs_app.md b/docs/versioned_docs/version-v0.27.0/tutorials/noirjs_app.md index 12beb476994..0d75e2e8045 100644 --- a/docs/versioned_docs/version-v0.27.0/tutorials/noirjs_app.md +++ b/docs/versioned_docs/version-v0.27.0/tutorials/noirjs_app.md @@ -24,7 +24,7 @@ Before we start, we want to make sure we have Node and Nargo installed. We start by opening a terminal and executing `node --version`. If we don't get an output like `v20.10.0`, that means node is not installed. Let's do that by following the handy [nvm guide](https://github.com/nvm-sh/nvm?tab=readme-ov-file#install--update-script). -As for `Nargo`, we can follow the the [Nargo guide](../getting_started/installation/index.md) to install it. If you're lazy, just paste this on a terminal and run `noirup`: +As for `Nargo`, we can follow the [Nargo guide](../getting_started/installation/index.md) to install it. If you're lazy, just paste this on a terminal and run `noirup`: ```sh curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash diff --git a/noir_stdlib/src/grumpkin_scalar_mul.nr b/noir_stdlib/src/grumpkin_scalar_mul.nr index 06d30d62332..c1195073ef6 100644 --- a/noir_stdlib/src/grumpkin_scalar_mul.nr +++ b/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_stdlib/src/internal.nr b/noir_stdlib/src/internal.nr deleted file mode 100644 index 8d5c01dda7f..00000000000 --- a/noir_stdlib/src/internal.nr +++ /dev/null @@ -1,12 +0,0 @@ -// This file contains functions which should only be used in calls injected by the Noir compiler. -// These functions should not be called manually in user code. -// -// Changes to this file will not be considered breaking. - -#[oracle(assert_message)] -unconstrained fn assert_message_oracle(_input: T) {} -unconstrained pub fn resolve_assert_message(input: T, condition: bool) { - if !condition { - assert_message_oracle(input); - } -} diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index 90c04472066..ebde4b88858 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -26,7 +26,6 @@ mod default; mod prelude; mod uint128; mod bigint; -mod internal; // Oracle calls are required to be wrapped in an unconstrained function // Thus, the only argument to the `println` oracle is expected to always be an ident diff --git a/noir_stdlib/src/scalar_mul.nr b/noir_stdlib/src/scalar_mul.nr index eee7aac39f2..457b7b7791c 100644 --- a/noir_stdlib/src/scalar_mul.nr +++ b/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/test_programs/execution_success/scalar_mul/Nargo.toml b/test_programs/execution_success/fixed_base_scalar_mul/Nargo.toml similarity index 63% rename from test_programs/execution_success/scalar_mul/Nargo.toml rename to test_programs/execution_success/fixed_base_scalar_mul/Nargo.toml index 926114ec374..a8e45c9b5ad 100644 --- a/test_programs/execution_success/scalar_mul/Nargo.toml +++ b/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/test_programs/execution_success/scalar_mul/Prover.toml b/test_programs/execution_success/fixed_base_scalar_mul/Prover.toml similarity index 100% rename from test_programs/execution_success/scalar_mul/Prover.toml rename to test_programs/execution_success/fixed_base_scalar_mul/Prover.toml diff --git a/test_programs/execution_success/scalar_mul/src/main.nr b/test_programs/execution_success/fixed_base_scalar_mul/src/main.nr similarity index 100% rename from test_programs/execution_success/scalar_mul/src/main.nr rename to test_programs/execution_success/fixed_base_scalar_mul/src/main.nr diff --git a/test_programs/execution_success/inline_never_basic/Nargo.toml b/test_programs/execution_success/inline_never_basic/Nargo.toml new file mode 100644 index 00000000000..16691770d76 --- /dev/null +++ b/test_programs/execution_success/inline_never_basic/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "inline_never_basic" +type = "bin" +authors = [""] +compiler_version = ">=0.27.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/execution_success/inline_never_basic/Prover.toml b/test_programs/execution_success/inline_never_basic/Prover.toml new file mode 100644 index 00000000000..f28f2f8cc48 --- /dev/null +++ b/test_programs/execution_success/inline_never_basic/Prover.toml @@ -0,0 +1,2 @@ +x = "5" +y = "10" diff --git a/test_programs/execution_success/inline_never_basic/src/main.nr b/test_programs/execution_success/inline_never_basic/src/main.nr new file mode 100644 index 00000000000..1922aaedb6c --- /dev/null +++ b/test_programs/execution_success/inline_never_basic/src/main.nr @@ -0,0 +1,8 @@ +fn main(x: Field, y: pub Field) { + basic_check(x, y); +} + +#[inline(never)] +fn basic_check(x: Field, y: Field) { + assert(x != y); +} diff --git a/test_programs/execution_success/variable_base_scalar_mul/Nargo.toml b/test_programs/execution_success/variable_base_scalar_mul/Nargo.toml new file mode 100644 index 00000000000..66712ab503c --- /dev/null +++ b/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/test_programs/execution_success/variable_base_scalar_mul/Prover.toml b/test_programs/execution_success/variable_base_scalar_mul/Prover.toml new file mode 100644 index 00000000000..51d6fc9b96c --- /dev/null +++ b/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/test_programs/execution_success/variable_base_scalar_mul/src/main.nr b/test_programs/execution_success/variable_base_scalar_mul/src/main.nr new file mode 100644 index 00000000000..4914ad01777 --- /dev/null +++ b/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/test_programs/noir_test_success/should_fail_with_matches/src/main.nr b/test_programs/noir_test_success/should_fail_with_matches/src/main.nr index d2b7d155a32..56ee5cfa586 100644 --- a/test_programs/noir_test_success/should_fail_with_matches/src/main.nr +++ b/test_programs/noir_test_success/should_fail_with_matches/src/main.nr @@ -17,3 +17,67 @@ fn test_should_fail_with_runtime_match() { fn test_should_fail_without_runtime_match() { assert_eq(dep::std::hash::pedersen_commitment([27]).x, 0); } + +struct InvalidPointError { + point: dep::std::hash::PedersenPoint, +} + +#[test(should_fail_with = "InvalidPointError { point: PedersenPoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8 } }")] +fn test_should_fail_with_struct() { + let hash = dep::std::hash::pedersen_commitment([27]); + assert_eq(hash.x, 0, InvalidPointError { point: hash }); +} + +#[test(should_fail_with = "A: 0x00 is not 1!")] +fn test_should_fail_with_basic_type_fmt_string() { + let a = 0; + let b = 1; + assert_eq(a, b, f"A: {a} is not 1!"); +} + +#[test(should_fail_with = "Invalid hash: PedersenPoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8 }")] +fn test_should_fail_with_struct_fmt_string() { + let hash = dep::std::hash::pedersen_commitment([27]); + assert_eq(hash.x, 0, f"Invalid hash: {hash}"); +} + +// Also test unconstrained versions + +#[test(should_fail_with = "Not equal")] +unconstrained fn unconstrained_test_should_fail_with_match() { + assert_eq(0, 1, "Not equal"); +} + +#[test(should_fail)] +unconstrained fn unconstrained_test_should_fail_without_match() { + assert_eq(0, 1); +} + +#[test(should_fail_with = "Not equal")] +unconstrained fn unconstrained_test_should_fail_with_runtime_match() { + assert_eq(dep::std::hash::pedersen_commitment([27]).x, 0, "Not equal"); +} + +#[test(should_fail)] +unconstrained fn unconstrained_test_should_fail_without_runtime_match() { + assert_eq(dep::std::hash::pedersen_commitment([27]).x, 0); +} + +#[test(should_fail_with = "InvalidPointError { point: PedersenPoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8 } }")] +unconstrained fn unconstrained_test_should_fail_with_struct() { + let hash = dep::std::hash::pedersen_commitment([27]); + assert_eq(hash.x, 0, InvalidPointError { point: hash }); +} + +#[test(should_fail_with = "A: 0x00 is not 1!")] +unconstrained fn unconstrained_test_should_fail_with_basic_type_fmt_string() { + let a = 0; + let b = 1; + assert_eq(a, b, f"A: {a} is not 1!"); +} + +#[test(should_fail_with = "Invalid hash: PedersenPoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8 }")] +unconstrained fn unconstrained_test_should_fail_with_struct_fmt_string() { + let hash = dep::std::hash::pedersen_commitment([27]); + assert_eq(hash.x, 0, f"Invalid hash: {hash}"); +} diff --git a/tooling/debugger/ignored-tests.txt b/tooling/debugger/ignored-tests.txt index f2338e85bef..ed8e8df73af 100644 --- a/tooling/debugger/ignored-tests.txt +++ b/tooling/debugger/ignored-tests.txt @@ -22,4 +22,4 @@ no_predicates_numeric_generic_poseidon regression_4709 fold_distinct_return fold_fibonacci -fold_complex_outputs \ No newline at end of file +fold_complex_outputs diff --git a/tooling/debugger/src/context.rs b/tooling/debugger/src/context.rs index a423016eacf..ea32c864a0b 100644 --- a/tooling/debugger/src/context.rs +++ b/tooling/debugger/src/context.rs @@ -2,7 +2,6 @@ use crate::foreign_calls::DebugForeignCallExecutor; use acvm::acir::circuit::brillig::BrilligBytecode; use acvm::acir::circuit::{Circuit, Opcode, OpcodeLocation}; use acvm::acir::native_types::{Witness, WitnessMap}; -use acvm::brillig_vm::brillig::ForeignCallResult; use acvm::brillig_vm::MemoryValue; use acvm::pwg::{ ACVMStatus, BrilligSolver, BrilligSolverStatus, ForeignCallWaitInfo, StepResult, ACVM, @@ -54,6 +53,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { &circuit.opcodes, initial_witness, unconstrained_functions, + &circuit.assert_messages, ), brillig_solver: None, foreign_call_executor, @@ -356,9 +356,6 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { let foreign_call_result = self.foreign_call_executor.execute(&foreign_call); match foreign_call_result { Ok(foreign_call_result) => { - let foreign_call_result = foreign_call_result - .get_brillig_output() - .unwrap_or(ForeignCallResult::default()); if let Some(mut solver) = self.brillig_solver.take() { solver.resolve_pending_foreign_call(foreign_call_result); self.brillig_solver = Some(solver); diff --git a/tooling/debugger/src/foreign_calls.rs b/tooling/debugger/src/foreign_calls.rs index f11ac22cd75..209439f5f92 100644 --- a/tooling/debugger/src/foreign_calls.rs +++ b/tooling/debugger/src/foreign_calls.rs @@ -5,7 +5,7 @@ use acvm::{ }; use nargo::{ artifacts::debug::{DebugArtifact, DebugVars, StackFrame}, - ops::{DefaultForeignCallExecutor, ForeignCallExecutor, NargoForeignCallResult}, + ops::{DefaultForeignCallExecutor, ForeignCallExecutor}, }; use noirc_errors::debug_info::{DebugFnId, DebugVarId}; use noirc_printable_type::ForeignCallError; @@ -94,7 +94,7 @@ impl ForeignCallExecutor for DefaultDebugForeignCallExecutor { fn execute( &mut self, foreign_call: &ForeignCallWaitInfo, - ) -> Result { + ) -> Result { let foreign_call_name = foreign_call.function.as_str(); match DebugForeignCall::lookup(foreign_call_name) { Some(DebugForeignCall::VarAssign) => { @@ -105,7 +105,7 @@ impl ForeignCallExecutor for DefaultDebugForeignCallExecutor { foreign_call.inputs[1..].iter().flat_map(|x| x.fields()).collect(); self.debug_vars.assign_var(var_id, &values); } - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } Some(DebugForeignCall::VarDrop) => { let fcp_var_id = &foreign_call.inputs[0]; @@ -113,7 +113,7 @@ impl ForeignCallExecutor for DefaultDebugForeignCallExecutor { let var_id = debug_var_id(var_id_value); self.debug_vars.drop_var(var_id); } - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } Some(DebugForeignCall::MemberAssign(arity)) => { if let Some(ForeignCallParam::Single(var_id_value)) = foreign_call.inputs.first() { @@ -141,7 +141,7 @@ impl ForeignCallExecutor for DefaultDebugForeignCallExecutor { .collect(); self.debug_vars.assign_field(var_id, indexes, &values); } - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } Some(DebugForeignCall::DerefAssign) => { let fcp_var_id = &foreign_call.inputs[0]; @@ -150,7 +150,7 @@ impl ForeignCallExecutor for DefaultDebugForeignCallExecutor { let var_id = debug_var_id(var_id_value); self.debug_vars.assign_deref(var_id, &fcp_value.fields()); } - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } Some(DebugForeignCall::FnEnter) => { let fcp_fn_id = &foreign_call.inputs[0]; @@ -159,11 +159,11 @@ impl ForeignCallExecutor for DefaultDebugForeignCallExecutor { }; let fn_id = debug_fn_id(fn_id_value); self.debug_vars.push_fn(fn_id); - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } Some(DebugForeignCall::FnExit) => { self.debug_vars.pop_fn(); - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } None => self.executor.execute(foreign_call), } diff --git a/tooling/lsp/src/solver.rs b/tooling/lsp/src/solver.rs index 0fea9b16b54..b47c30af5f6 100644 --- a/tooling/lsp/src/solver.rs +++ b/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/tooling/nargo/src/errors.rs b/tooling/nargo/src/errors.rs index ac03330a7c8..4b0c29a9b1b 100644 --- a/tooling/nargo/src/errors.rs +++ b/tooling/nargo/src/errors.rs @@ -1,7 +1,11 @@ +use std::collections::BTreeMap; + use acvm::{ - acir::circuit::{OpcodeLocation, ResolvedOpcodeLocation}, + acir::circuit::{OpcodeLocation, ResolvedAssertionPayload, ResolvedOpcodeLocation}, pwg::{ErrorLocation, OpcodeResolutionError}, + FieldElement, }; +use noirc_abi::{Abi, AbiErrorType}; use noirc_errors::{ debug_info::DebugInfo, reporter::ReportedErrors, CustomDiagnostic, FileDiagnostic, }; @@ -9,7 +13,9 @@ use noirc_errors::{ pub use noirc_errors::Location; use noirc_frontend::graph::CrateName; -use noirc_printable_type::ForeignCallError; +use noirc_printable_type::{ + decode_value, ForeignCallError, PrintableType, PrintableValue, PrintableValueDisplay, +}; use thiserror::Error; /// Errors covering situations where a package cannot be compiled. @@ -53,24 +59,34 @@ impl NargoError { /// /// We want to extract the user defined error so that we can compare it /// in tests to expected failure messages - pub fn user_defined_failure_message(&self) -> Option<&str> { + pub fn user_defined_failure_message( + &self, + error_types: &BTreeMap, + ) -> Option { let execution_error = match self { NargoError::ExecutionError(error) => error, _ => return None, }; match execution_error { - ExecutionError::AssertionFailed(message, _) => Some(message), + ExecutionError::AssertionFailed(payload, _) => match payload { + ResolvedAssertionPayload::String(message) => Some(message.to_string()), + ResolvedAssertionPayload::Raw(error_selector, fields) => { + let abi_type = error_types.get(error_selector)?; + let decoded = prepare_for_display(fields, abi_type.clone()); + Some(decoded.to_string()) + } + }, ExecutionError::SolvingError(error, _) => match error { OpcodeResolutionError::IndexOutOfBounds { .. } | OpcodeResolutionError::OpcodeNotSolvable(_) | OpcodeResolutionError::UnsatisfiedConstrain { .. } | OpcodeResolutionError::AcirMainCallAttempted { .. } + | OpcodeResolutionError::BrilligFunctionFailed { .. } | OpcodeResolutionError::AcirCallOutputsMismatch { .. } => None, - OpcodeResolutionError::BrilligFunctionFailed { message, .. } => { - message.as_ref().map(|s| s.as_str()) + OpcodeResolutionError::BlackBoxFunctionFailed(_, reason) => { + Some(reason.to_string()) } - OpcodeResolutionError::BlackBoxFunctionFailed(_, reason) => Some(reason), }, } } @@ -78,8 +94,8 @@ impl NargoError { #[derive(Debug, Error)] pub enum ExecutionError { - #[error("Failed assertion: '{}'", .0)] - AssertionFailed(String, Vec), + #[error("Failed assertion")] + AssertionFailed(ResolvedAssertionPayload, Vec), #[error("Failed to solve program: '{}'", .0)] SolvingError(OpcodeResolutionError, Option>), @@ -101,7 +117,7 @@ fn extract_locations_from_error( acir_call_stack, ) | ExecutionError::SolvingError( - OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: error_location }, + OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: error_location, .. }, acir_call_stack, ) => match error_location { ErrorLocation::Unresolved => { @@ -144,11 +160,52 @@ fn extract_locations_from_error( ) } -fn extract_message_from_error(nargo_err: &NargoError) -> String { +fn prepare_for_display(fields: &[FieldElement], error_type: AbiErrorType) -> PrintableValueDisplay { + match error_type { + AbiErrorType::FmtString { length, item_types } => { + let mut fields_iter = fields.iter().copied(); + let PrintableValue::String(string) = + decode_value(&mut fields_iter, &PrintableType::String { length }) + else { + unreachable!("Got non-string from string decoding"); + }; + let _length_of_items = fields_iter.next(); + let items = item_types.into_iter().map(|abi_type| { + let printable_typ = (&abi_type).into(); + let decoded = decode_value(&mut fields_iter, &printable_typ); + (decoded, printable_typ) + }); + PrintableValueDisplay::FmtString(string, items.collect()) + } + AbiErrorType::Custom(abi_typ) => { + let printable_type = (&abi_typ).into(); + let decoded = decode_value(&mut fields.iter().copied(), &printable_type); + PrintableValueDisplay::Plain(decoded, printable_type) + } + } +} + +fn extract_message_from_error( + error_types: &BTreeMap, + nargo_err: &NargoError, +) -> String { match nargo_err { - NargoError::ExecutionError(ExecutionError::AssertionFailed(message, _)) => { + NargoError::ExecutionError(ExecutionError::AssertionFailed( + ResolvedAssertionPayload::String(message), + _, + )) => { format!("Assertion failed: '{message}'") } + NargoError::ExecutionError(ExecutionError::AssertionFailed( + ResolvedAssertionPayload::Raw(error_selector, fields), + .., + )) => { + if let Some(error_type) = error_types.get(error_selector) { + format!("Assertion failed: {}", prepare_for_display(fields, error_type.clone())) + } else { + "Assertion failed".to_string() + } + } NargoError::ExecutionError(ExecutionError::SolvingError( OpcodeResolutionError::IndexOutOfBounds { index, array_size, .. }, _, @@ -166,6 +223,7 @@ fn extract_message_from_error(nargo_err: &NargoError) -> String { /// Tries to generate a runtime diagnostic from a nargo error. It will successfully do so if it's a runtime error with a call stack. pub fn try_to_diagnose_runtime_error( nargo_err: &NargoError, + abi: &Abi, debug: &[DebugInfo], ) -> Option { let source_locations = match nargo_err { @@ -177,7 +235,7 @@ pub fn try_to_diagnose_runtime_error( // The location of the error itself will be the location at the top // of the call stack (the last item in the Vec). let location = source_locations.last()?; - let message = extract_message_from_error(nargo_err); + let message = extract_message_from_error(&abi.error_types, nargo_err); Some( CustomDiagnostic::simple_error(message, String::new(), location.span) .in_file(location.file) diff --git a/tooling/nargo/src/ops/execute.rs b/tooling/nargo/src/ops/execute.rs index 34755d14ed2..4a75212ba47 100644 --- a/tooling/nargo/src/ops/execute.rs +++ b/tooling/nargo/src/ops/execute.rs @@ -1,7 +1,6 @@ use acvm::acir::circuit::brillig::BrilligBytecode; use acvm::acir::circuit::{OpcodeLocation, Program, ResolvedOpcodeLocation}; use acvm::acir::native_types::WitnessStack; -use acvm::brillig_vm::brillig::ForeignCallResult; use acvm::pwg::{ACVMStatus, ErrorLocation, OpcodeNotSolvable, OpcodeResolutionError, ACVM}; use acvm::BlackBoxFunctionSolver; use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; @@ -9,7 +8,7 @@ use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; use crate::errors::ExecutionError; use crate::NargoError; -use super::foreign_calls::{ForeignCallExecutor, NargoForeignCallResult}; +use super::foreign_calls::ForeignCallExecutor; struct ProgramExecutor<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> { functions: &'a [Circuit], @@ -63,10 +62,9 @@ impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> ProgramExecutor<'a, &circuit.opcodes, initial_witness, self.unconstrained_functions, + &circuit.assert_messages, ); - // This message should be resolved by a nargo foreign call only when we have an unsatisfied assertion. - let mut assert_message: Option = None; loop { let solver_status = acvm.solve(); @@ -79,6 +77,7 @@ impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> ProgramExecutor<'a, let call_stack = match &error { OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Resolved(opcode_location), + .. } | OpcodeResolutionError::IndexOutOfBounds { opcode_location: ErrorLocation::Resolved(opcode_location), @@ -103,55 +102,25 @@ impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> ProgramExecutor<'a, _ => None, }; - return Err(NargoError::ExecutionError(match call_stack { - Some(call_stack) => { - // First check whether we have a runtime assertion message that should be resolved on an ACVM failure - // If we do not have a runtime assertion message, we check whether the error is a brillig error with a user-defined message, - // and finally we should check whether the circuit has any hardcoded messages associated with a specific `OpcodeLocation`. - // Otherwise return the provided opcode resolution error. - if let Some(assert_message) = assert_message { - ExecutionError::AssertionFailed( - assert_message.to_owned(), - call_stack, - ) - } else if let OpcodeResolutionError::BrilligFunctionFailed { - message: Some(message), - .. - } = &error - { - ExecutionError::AssertionFailed(message.to_owned(), call_stack) - } else if let Some(assert_message) = circuit.get_assert_message( - call_stack - .last() - .expect("Call stacks should not be empty") - .opcode_location, - ) { - ExecutionError::AssertionFailed( - assert_message.to_owned(), - call_stack, - ) - } else { - ExecutionError::SolvingError(error, Some(call_stack)) - } + let assertion_payload = match &error { + OpcodeResolutionError::BrilligFunctionFailed { payload, .. } + | OpcodeResolutionError::UnsatisfiedConstrain { payload, .. } => { + payload.clone() } - None => ExecutionError::SolvingError(error, None), + _ => None, + }; + + return Err(NargoError::ExecutionError(match assertion_payload { + Some(payload) => ExecutionError::AssertionFailed( + payload, + call_stack.expect("Should have call stack for an assertion failure"), + ), + None => ExecutionError::SolvingError(error, call_stack), })); } ACVMStatus::RequiresForeignCall(foreign_call) => { let foreign_call_result = self.foreign_call_executor.execute(&foreign_call)?; - match foreign_call_result { - NargoForeignCallResult::BrilligOutput(foreign_call_result) => { - acvm.resolve_pending_foreign_call(foreign_call_result); - } - NargoForeignCallResult::ResolvedAssertMessage(message) => { - if assert_message.is_some() { - unreachable!("Resolving an assert message should happen only once as the VM should have failed"); - } - assert_message = Some(message); - - acvm.resolve_pending_foreign_call(ForeignCallResult::default()); - } - } + acvm.resolve_pending_foreign_call(foreign_call_result); } ACVMStatus::RequiresAcirCall(call_info) => { // Store the parent function index whose context we are currently executing diff --git a/tooling/nargo/src/ops/foreign_calls.rs b/tooling/nargo/src/ops/foreign_calls.rs index 33767314a37..90f6659ad28 100644 --- a/tooling/nargo/src/ops/foreign_calls.rs +++ b/tooling/nargo/src/ops/foreign_calls.rs @@ -10,69 +10,13 @@ pub trait ForeignCallExecutor { fn execute( &mut self, foreign_call: &ForeignCallWaitInfo, - ) -> Result; -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum NargoForeignCallResult { - BrilligOutput(ForeignCallResult), - ResolvedAssertMessage(String), -} - -impl NargoForeignCallResult { - pub fn get_assert_message(self) -> Option { - match self { - Self::ResolvedAssertMessage(msg) => Some(msg), - _ => None, - } - } - - pub fn get_brillig_output(self) -> Option { - match self { - Self::BrilligOutput(foreign_call_result) => Some(foreign_call_result), - _ => None, - } - } -} - -impl From for NargoForeignCallResult { - fn from(value: ForeignCallResult) -> Self { - Self::BrilligOutput(value) - } -} - -impl From for NargoForeignCallResult { - fn from(value: String) -> Self { - Self::ResolvedAssertMessage(value) - } -} - -impl From for NargoForeignCallResult { - fn from(value: FieldElement) -> Self { - let foreign_call_result: ForeignCallResult = value.into(); - foreign_call_result.into() - } -} - -impl From> for NargoForeignCallResult { - fn from(values: Vec) -> Self { - let foreign_call_result: ForeignCallResult = values.into(); - foreign_call_result.into() - } -} - -impl From> for NargoForeignCallResult { - fn from(values: Vec) -> Self { - let foreign_call_result: ForeignCallResult = values.into(); - foreign_call_result.into() - } + ) -> Result; } /// This enumeration represents the Brillig foreign calls that are natively supported by nargo. /// After resolution of a foreign call, nargo will restart execution of the ACVM pub enum ForeignCall { Print, - AssertMessage, CreateMock, SetMockParams, GetMockLastParams, @@ -91,7 +35,6 @@ impl ForeignCall { pub(crate) fn name(&self) -> &'static str { match self { ForeignCall::Print => "print", - ForeignCall::AssertMessage => "assert_message", ForeignCall::CreateMock => "create_mock", ForeignCall::SetMockParams => "set_mock_params", ForeignCall::GetMockLastParams => "get_mock_last_params", @@ -104,7 +47,6 @@ impl ForeignCall { pub(crate) fn lookup(op_name: &str) -> Option { match op_name { "print" => Some(ForeignCall::Print), - "assert_message" => Some(ForeignCall::AssertMessage), "create_mock" => Some(ForeignCall::CreateMock), "set_mock_params" => Some(ForeignCall::SetMockParams), "get_mock_last_params" => Some(ForeignCall::GetMockLastParams), @@ -223,13 +165,6 @@ impl DefaultForeignCallExecutor { Ok(()) } - fn execute_assert_message( - foreign_call_inputs: &[ForeignCallParam], - ) -> Result { - let display_string = Self::format_printable_value(foreign_call_inputs, true)?; - Ok(display_string.into()) - } - fn format_printable_value( foreign_call_inputs: &[ForeignCallParam], skip_newline: bool, @@ -246,16 +181,15 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { fn execute( &mut self, foreign_call: &ForeignCallWaitInfo, - ) -> Result { + ) -> Result { let foreign_call_name = foreign_call.function.as_str(); match ForeignCall::lookup(foreign_call_name) { Some(ForeignCall::Print) => { if self.show_output { Self::execute_print(&foreign_call.inputs)?; } - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } - Some(ForeignCall::AssertMessage) => Self::execute_assert_message(&foreign_call.inputs), Some(ForeignCall::CreateMock) => { let mock_oracle_name = Self::parse_string(&foreign_call.inputs[0]); assert!(ForeignCall::lookup(&mock_oracle_name).is_none()); @@ -271,7 +205,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { .unwrap_or_else(|| panic!("Unknown mock id {}", id)) .params = Some(params.to_vec()); - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } Some(ForeignCall::GetMockLastParams) => { let (id, _) = Self::extract_mock_id(&foreign_call.inputs)?; @@ -291,7 +225,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { .unwrap_or_else(|| panic!("Unknown mock id {}", id)) .result = ForeignCallResult { values: params.to_vec() }; - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } Some(ForeignCall::SetMockTimes) => { let (id, params) = Self::extract_mock_id(&foreign_call.inputs)?; @@ -302,12 +236,12 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { .unwrap_or_else(|| panic!("Unknown mock id {}", id)) .times_left = Some(times); - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } Some(ForeignCall::ClearMock) => { let (id, _) = Self::extract_mock_id(&foreign_call.inputs)?; self.mocked_responses.retain(|response| response.id != id); - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } None => { let mock_response_position = self @@ -346,7 +280,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { let parsed_response: ForeignCallResult = response.result()?; - Ok(parsed_response.into()) + Ok(parsed_response) } (None, None) => panic!( "No mock for foreign call {}({:?})", @@ -423,7 +357,7 @@ mod tests { }; let result = executor.execute(&foreign_call); - assert_eq!(result.unwrap(), ForeignCallResult { values: foreign_call.inputs }.into()); + assert_eq!(result.unwrap(), ForeignCallResult { values: foreign_call.inputs }); server.close(); } diff --git a/tooling/nargo/src/ops/mod.rs b/tooling/nargo/src/ops/mod.rs index 2f5e6ebb7d4..cada2f0e915 100644 --- a/tooling/nargo/src/ops/mod.rs +++ b/tooling/nargo/src/ops/mod.rs @@ -3,9 +3,7 @@ pub use self::compile::{ compile_workspace, report_errors, }; pub use self::execute::execute_program; -pub use self::foreign_calls::{ - DefaultForeignCallExecutor, ForeignCall, ForeignCallExecutor, NargoForeignCallResult, -}; +pub use self::foreign_calls::{DefaultForeignCallExecutor, ForeignCall, ForeignCallExecutor}; pub use self::optimize::{optimize_contract, optimize_program}; pub use self::transform::{transform_contract, transform_program}; diff --git a/tooling/nargo/src/ops/test.rs b/tooling/nargo/src/ops/test.rs index b216fff827d..86dd8cd7cd5 100644 --- a/tooling/nargo/src/ops/test.rs +++ b/tooling/nargo/src/ops/test.rs @@ -2,9 +2,9 @@ use acvm::{ acir::native_types::{WitnessMap, WitnessStack}, BlackBoxFunctionSolver, }; +use noirc_abi::Abi; use noirc_driver::{compile_no_check, CompileError, CompileOptions}; use noirc_errors::{debug_info::DebugInfo, FileDiagnostic}; -use noirc_evaluator::errors::RuntimeError; use noirc_frontend::hir::{def_map::TestFunction, Context}; use crate::{errors::try_to_diagnose_runtime_error, NargoError}; @@ -44,6 +44,7 @@ pub fn run_test( ); test_status_program_compile_pass( test_function, + compiled_program.abi, compiled_program.debug, circuit_execution, ) @@ -64,18 +65,7 @@ fn test_status_program_compile_fail(err: CompileError, test_function: &TestFunct return TestStatus::CompileError(err.into()); } - // The test has failed compilation, extract the assertion message if present and check if it's expected. - let assert_message = if let CompileError::RuntimeError(RuntimeError::FailedConstraint { - assert_message, - .. - }) = &err - { - assert_message.clone() - } else { - None - }; - - check_expected_failure_message(test_function, assert_message, Some(err.into())) + check_expected_failure_message(test_function, None, Some(err.into())) } /// The test function compiled successfully. @@ -84,6 +74,7 @@ fn test_status_program_compile_fail(err: CompileError, test_function: &TestFunct /// passed/failed to determine the test status. fn test_status_program_compile_pass( test_function: &TestFunction, + abi: Abi, debug: Vec, circuit_execution: Result, ) -> TestStatus { @@ -105,7 +96,7 @@ fn test_status_program_compile_pass( // If we reach here, then the circuit execution failed. // // Check if the function should have passed - let diagnostic = try_to_diagnose_runtime_error(&circuit_execution_err, &debug); + let diagnostic = try_to_diagnose_runtime_error(&circuit_execution_err, &abi, &debug); let test_should_have_passed = !test_function.should_fail(); if test_should_have_passed { return TestStatus::Fail { @@ -116,7 +107,7 @@ fn test_status_program_compile_pass( check_expected_failure_message( test_function, - circuit_execution_err.user_defined_failure_message().map(|s| s.to_string()), + circuit_execution_err.user_defined_failure_message(&abi.error_types), diagnostic, ) } diff --git a/tooling/nargo_cli/src/cli/execute_cmd.rs b/tooling/nargo_cli/src/cli/execute_cmd.rs index a353065491f..854ad559012 100644 --- a/tooling/nargo_cli/src/cli/execute_cmd.rs +++ b/tooling/nargo_cli/src/cli/execute_cmd.rs @@ -154,7 +154,9 @@ pub(crate) fn execute_program( warnings: compiled_program.warnings.clone(), }; - if let Some(diagnostic) = try_to_diagnose_runtime_error(&err, &compiled_program.debug) { + if let Some(diagnostic) = + try_to_diagnose_runtime_error(&err, &compiled_program.abi, &compiled_program.debug) + { diagnostic.report(&debug_artifact, false); } diff --git a/tooling/nargo_cli/src/cli/fs/inputs.rs b/tooling/nargo_cli/src/cli/fs/inputs.rs index 023195010ac..bd038c51ad5 100644 --- a/tooling/nargo_cli/src/cli/fs/inputs.rs +++ b/tooling/nargo_cli/src/cli/fs/inputs.rs @@ -107,6 +107,7 @@ mod tests { // Neither of these should be relevant so we leave them empty. param_witnesses: BTreeMap::new(), return_witnesses: Vec::new(), + error_types: BTreeMap::new(), }; let input_map = BTreeMap::from([ ("foo".to_owned(), InputValue::Field(42u128.into())), diff --git a/tooling/noir_js/src/witness_generation.ts b/tooling/noir_js/src/witness_generation.ts index cef1d817d9b..1f233422061 100644 --- a/tooling/noir_js/src/witness_generation.ts +++ b/tooling/noir_js/src/witness_generation.ts @@ -26,12 +26,6 @@ const defaultForeignCallHandler: ForeignCallHandler = async (name: string, args: // // If a user needs to print values then they should provide a custom foreign call handler. return []; - } else if (name == 'assert_message') { - // By default we do not do anything for `assert_message` foreign calls due to a need for formatting, - // however we provide an empty response in order to not halt execution. - // - // If a user needs to use dynamic assertion messages then they should provide a custom foreign call handler. - return []; } throw Error(`Unexpected oracle during execution: ${name}(${args.join(', ')})`); }; diff --git a/tooling/noir_js_backend_barretenberg/package.json b/tooling/noir_js_backend_barretenberg/package.json index a276a29046f..b5782269674 100644 --- a/tooling/noir_js_backend_barretenberg/package.json +++ b/tooling/noir_js_backend_barretenberg/package.json @@ -42,7 +42,7 @@ "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "dependencies": { - "@aztec/bb.js": "0.35.1", + "@aztec/bb.js": "portal:../../../../barretenberg/ts", "@noir-lang/types": "workspace:*", "fflate": "^0.8.0" }, diff --git a/tooling/noir_js_backend_barretenberg/test/public_input_deflattening.test.ts b/tooling/noir_js_backend_barretenberg/test/public_input_deflattening.test.ts index 079a1ad268b..cfd43eff250 100644 --- a/tooling/noir_js_backend_barretenberg/test/public_input_deflattening.test.ts +++ b/tooling/noir_js_backend_barretenberg/test/public_input_deflattening.test.ts @@ -55,6 +55,7 @@ const abi: Abi = { visibility: 'public', }, return_witnesses: [2, 13, 13], + error_types: {}, }; it('flattens a witness map in order of its witness indices', async () => { diff --git a/tooling/noir_js_types/src/types.ts b/tooling/noir_js_types/src/types.ts index 456e5a57f40..664a7c2a457 100644 --- a/tooling/noir_js_types/src/types.ts +++ b/tooling/noir_js_types/src/types.ts @@ -19,6 +19,14 @@ export type AbiParameter = { visibility: Visibility; }; +export type AbiErrorType = + | { + error_kind: 'fmtstring'; + length: number; + item_types: AbiType[]; + } + | ({ error_kind: 'custom' } & AbiType); + // Map from witness index to hex string value of witness. export type WitnessMap = Map; @@ -27,6 +35,7 @@ export type Abi = { param_witnesses: Record; return_type: { abi_type: AbiType; visibility: Visibility } | null; return_witnesses: number[]; + error_types: Record; }; export interface VerifierBackend { diff --git a/tooling/noirc_abi/Cargo.toml b/tooling/noirc_abi/Cargo.toml index 3258ea04c40..040f8a0dc79 100644 --- a/tooling/noirc_abi/Cargo.toml +++ b/tooling/noirc_abi/Cargo.toml @@ -12,6 +12,7 @@ license.workspace = true acvm.workspace = true iter-extended.workspace = true noirc_frontend.workspace = true +noirc_printable_type.workspace = true toml.workspace = true serde_json = "1.0" serde.workspace = true diff --git a/tooling/noirc_abi/src/input_parser/mod.rs b/tooling/noirc_abi/src/input_parser/mod.rs index 4cf66820b8d..9629ddc87ab 100644 --- a/tooling/noirc_abi/src/input_parser/mod.rs +++ b/tooling/noirc_abi/src/input_parser/mod.rs @@ -270,6 +270,7 @@ mod serialization_tests { // These two fields are unused when serializing/deserializing to file. param_witnesses: BTreeMap::new(), return_witnesses: Vec::new(), + error_types: Default::default(), }; let input_map: BTreeMap = BTreeMap::from([ diff --git a/tooling/noirc_abi/src/lib.rs b/tooling/noirc_abi/src/lib.rs index 8ff0154d32c..48c88833bdf 100644 --- a/tooling/noirc_abi/src/lib.rs +++ b/tooling/noirc_abi/src/lib.rs @@ -12,8 +12,9 @@ use input_parser::InputValue; use iter_extended::{try_btree_map, try_vecmap, vecmap}; use noirc_frontend::ast::{Signedness, Visibility}; use noirc_frontend::{hir::Context, Type, TypeBinding, TypeVariableKind}; +use noirc_printable_type::PrintableType; use serde::{Deserialize, Serialize}; -use std::ops::Range; +use std::{borrow::Borrow, ops::Range}; use std::{collections::BTreeMap, str}; // This is the ABI used to bridge the different TOML formats for the initial // witness, the partial witness generator and the interpreter. @@ -203,6 +204,38 @@ impl AbiType { } } +impl From<&AbiType> for PrintableType { + fn from(value: &AbiType) -> Self { + match value { + AbiType::Field => PrintableType::Field, + AbiType::String { length } => PrintableType::String { length: *length }, + AbiType::Tuple { fields } => { + let fields = fields.iter().map(|field| field.into()).collect(); + PrintableType::Tuple { types: fields } + } + AbiType::Array { length, typ } => { + let borrowed: &AbiType = typ.borrow(); + PrintableType::Array { length: Some(*length), typ: Box::new(borrowed.into()) } + } + AbiType::Boolean => PrintableType::Boolean, + AbiType::Struct { path, fields } => { + let fields = + fields.iter().map(|(name, field)| (name.clone(), field.into())).collect(); + PrintableType::Struct { + name: path.split("::").last().unwrap_or_default().to_string(), + fields, + } + } + AbiType::Integer { sign: Sign::Unsigned, width } => { + PrintableType::UnsignedInteger { width: *width } + } + AbiType::Integer { sign: Sign::Signed, width } => { + PrintableType::SignedInteger { width: *width } + } + } + } +} + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] /// An argument or return value of the circuit's `main` function. pub struct AbiParameter { @@ -223,6 +256,7 @@ pub struct AbiReturnType { pub abi_type: AbiType, pub visibility: AbiVisibility, } + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Abi { /// An ordered list of the arguments to the program's `main` function, specifying their types and visibility. @@ -232,6 +266,7 @@ pub struct Abi { pub param_witnesses: BTreeMap>>, pub return_type: Option, pub return_witnesses: Vec, + pub error_types: BTreeMap, } impl Abi { @@ -281,6 +316,7 @@ impl Abi { param_witnesses, return_type: self.return_type, return_witnesses: self.return_witnesses, + error_types: self.error_types, } } @@ -547,6 +583,29 @@ fn range_to_vec(ranges: &[Range]) -> Vec { result } +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "error_kind", rename_all = "lowercase")] +pub enum AbiErrorType { + FmtString { length: u64, item_types: Vec }, + Custom(AbiType), +} +impl AbiErrorType { + pub fn from_type(context: &Context, typ: &Type) -> Self { + match typ { + Type::FmtString(len, item_types) => { + let length = len.evaluate_to_u64().expect("Cannot evaluate fmt length"); + let Type::Tuple(item_types) = item_types.as_ref() else { + unreachable!("FmtString items must be a tuple") + }; + let item_types = + item_types.iter().map(|typ| AbiType::from_type(context, typ)).collect(); + Self::FmtString { length, item_types } + } + _ => Self::Custom(AbiType::from_type(context, typ)), + } + } +} + #[cfg(test)] mod test { use std::collections::BTreeMap; @@ -583,6 +642,7 @@ mod test { visibility: AbiVisibility::Public, }), return_witnesses: vec![Witness(3)], + error_types: BTreeMap::default(), }; // Note we omit return value from inputs diff --git a/tooling/noirc_abi_wasm/build.sh b/tooling/noirc_abi_wasm/build.sh index 16fb26e55db..c07d2d8a4c1 100755 --- a/tooling/noirc_abi_wasm/build.sh +++ b/tooling/noirc_abi_wasm/build.sh @@ -25,7 +25,7 @@ function run_if_available { require_command jq require_command cargo require_command wasm-bindgen -require_command wasm-opt +#require_command wasm-opt self_path=$(dirname "$(readlink -f "$0")") pname=$(cargo read-manifest | jq -r '.name') diff --git a/tooling/noirc_abi_wasm/test/shared/abi_encode.ts b/tooling/noirc_abi_wasm/test/shared/abi_encode.ts index cb80c6710ba..f4ab8175700 100644 --- a/tooling/noirc_abi_wasm/test/shared/abi_encode.ts +++ b/tooling/noirc_abi_wasm/test/shared/abi_encode.ts @@ -12,6 +12,7 @@ export const abi: Abi = { param_witnesses: { foo: [{ start: 1, end: 2 }], bar: [{ start: 2, end: 4 }] }, return_type: null, return_witnesses: [], + error_types: {}, }; export const inputs: InputMap = { diff --git a/tooling/noirc_abi_wasm/test/shared/array_as_field.ts b/tooling/noirc_abi_wasm/test/shared/array_as_field.ts index 0cc0035fa68..3698b913c66 100644 --- a/tooling/noirc_abi_wasm/test/shared/array_as_field.ts +++ b/tooling/noirc_abi_wasm/test/shared/array_as_field.ts @@ -11,6 +11,7 @@ export const abi: Abi = { param_witnesses: { foo: [{ start: 1, end: 3 }] }, return_type: null, return_witnesses: [], + error_types: {}, }; export const inputs: InputMap = { diff --git a/tooling/noirc_abi_wasm/test/shared/field_as_array.ts b/tooling/noirc_abi_wasm/test/shared/field_as_array.ts index 6ae709459de..4e3e2fd12a8 100644 --- a/tooling/noirc_abi_wasm/test/shared/field_as_array.ts +++ b/tooling/noirc_abi_wasm/test/shared/field_as_array.ts @@ -11,6 +11,7 @@ export const abi: Abi = { param_witnesses: { foo: [{ start: 1, end: 3 }] }, return_type: null, return_witnesses: [], + error_types: {}, }; export const inputs: InputMap = { diff --git a/tooling/noirc_abi_wasm/test/shared/structs.ts b/tooling/noirc_abi_wasm/test/shared/structs.ts index 6614f8f278e..ee666e40e87 100644 --- a/tooling/noirc_abi_wasm/test/shared/structs.ts +++ b/tooling/noirc_abi_wasm/test/shared/structs.ts @@ -54,6 +54,7 @@ export const abi: Abi = { }, return_type: null, return_witnesses: [], + error_types: {}, }; export const inputs: InputMap = { diff --git a/tooling/noirc_abi_wasm/test/shared/uint_overflow.ts b/tooling/noirc_abi_wasm/test/shared/uint_overflow.ts index c6e066e2bcd..82a3e3998ca 100644 --- a/tooling/noirc_abi_wasm/test/shared/uint_overflow.ts +++ b/tooling/noirc_abi_wasm/test/shared/uint_overflow.ts @@ -11,6 +11,7 @@ export const abi: Abi = { param_witnesses: { foo: [{ start: 1, end: 2 }] }, return_type: null, return_witnesses: [], + error_types: {}, }; export const inputs: InputMap = { diff --git a/yarn.lock b/yarn.lock index e9915882fac..b45678f5d8b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -221,19 +221,18 @@ __metadata: languageName: node linkType: hard -"@aztec/bb.js@npm:0.35.1": - version: 0.35.1 - resolution: "@aztec/bb.js@npm:0.35.1" +"@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg": + version: 0.0.0-use.local + resolution: "@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg" dependencies: comlink: ^4.4.1 commander: ^10.0.1 debug: ^4.3.4 tslib: ^2.4.0 bin: - bb.js: dest/node/main.js - checksum: 8e3551f059523d9494af4721a9219e2c6e63c8ed1df447a2d0daa9f8526a794758ae708bd1d9c9b1fbfb89c56dc867d9f0b87250dbabfcde23ec02dabbb5a32a + bb.js: ./dest/node/main.js languageName: node - linkType: hard + linkType: soft "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.11, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.23.5, @babel/code-frame@npm:^7.8.3": version: 7.23.5 @@ -4396,7 +4395,7 @@ __metadata: version: 0.0.0-use.local resolution: "@noir-lang/backend_barretenberg@workspace:tooling/noir_js_backend_barretenberg" dependencies: - "@aztec/bb.js": 0.35.1 + "@aztec/bb.js": "portal:../../../../barretenberg/ts" "@noir-lang/types": "workspace:*" "@types/node": ^20.6.2 "@types/prettier": ^3