diff --git a/barretenberg/acir_tests/browser-test-app/src/index.ts b/barretenberg/acir_tests/browser-test-app/src/index.ts index 56708214eeb..45b39346ddf 100644 --- a/barretenberg/acir_tests/browser-test-app/src/index.ts +++ b/barretenberg/acir_tests/browser-test-app/src/index.ts @@ -31,10 +31,9 @@ async function runTest( acirComposer, bytecode, witness, - true ); debug(`verifying...`); - const verified = await api.acirVerifyProof(acirComposer, proof, true); + const verified = await api.acirVerifyProof(acirComposer, proof); debug(`verified: ${verified}`); await api.destroy(); diff --git a/barretenberg/acir_tests/gen_inner_proof_inputs.sh b/barretenberg/acir_tests/gen_inner_proof_inputs.sh index 36137bde82e..ade57bcea4f 100755 --- a/barretenberg/acir_tests/gen_inner_proof_inputs.sh +++ b/barretenberg/acir_tests/gen_inner_proof_inputs.sh @@ -20,7 +20,7 @@ export BRANCH ./clone_test_vectors.sh -cd acir_tests/assert_statement +cd acir_tests/assert_statement_recursive PROOF_DIR=$PWD/proofs PROOF_PATH=$PROOF_DIR/$PROOF_NAME diff --git a/barretenberg/cpp/src/barretenberg/bb/main.cpp b/barretenberg/cpp/src/barretenberg/bb/main.cpp index 6c567b00bcd..b49d6f95908 100644 --- a/barretenberg/cpp/src/barretenberg/bb/main.cpp +++ b/barretenberg/cpp/src/barretenberg/bb/main.cpp @@ -91,7 +91,7 @@ acir_format::AcirFormat get_constraint_system(std::string const& bytecode_path) * @return true if the proof is valid * @return false if the proof is invalid */ -bool proveAndVerify(const std::string& bytecodePath, const std::string& witnessPath, bool recursive) +bool proveAndVerify(const std::string& bytecodePath, const std::string& witnessPath) { auto constraint_system = get_constraint_system(bytecodePath); auto witness = get_witness(witnessPath); @@ -109,14 +109,14 @@ bool proveAndVerify(const std::string& bytecodePath, const std::string& witnessP write_benchmark("subgroup_size", acir_composer.get_dyadic_circuit_size(), "acir_test", current_dir); Timer proof_timer; - auto proof = acir_composer.create_proof(recursive); + auto proof = acir_composer.create_proof(); write_benchmark("proof_construction_time", proof_timer.milliseconds(), "acir_test", current_dir); Timer vk_timer; acir_composer.init_verification_key(); write_benchmark("vk_construction_time", vk_timer.milliseconds(), "acir_test", current_dir); - auto verified = acir_composer.verify_proof(proof, recursive); + auto verified = acir_composer.verify_proof(proof); vinfo("verified: ", verified); return verified; @@ -172,9 +172,7 @@ bool accumulateAndVerifyGoblin(const std::string& bytecodePath, const std::strin * @return true if the proof is valid * @return false if the proof is invalid */ -bool proveAndVerifyGoblin(const std::string& bytecodePath, - const std::string& witnessPath, - [[maybe_unused]] bool recursive) +bool proveAndVerifyGoblin(const std::string& bytecodePath, const std::string& witnessPath) { // Populate the acir constraint system and witness from gzipped data auto constraint_system = get_constraint_system(bytecodePath); @@ -212,10 +210,7 @@ bool proveAndVerifyGoblin(const std::string& bytecodePath, * @param recursive Whether to use recursive proof generation of non-recursive * @param outputPath Path to write the proof to */ -void prove(const std::string& bytecodePath, - const std::string& witnessPath, - bool recursive, - const std::string& outputPath) +void prove(const std::string& bytecodePath, const std::string& witnessPath, const std::string& outputPath) { auto constraint_system = get_constraint_system(bytecodePath); auto witness = get_witness(witnessPath); @@ -224,7 +219,7 @@ void prove(const std::string& bytecodePath, acir_composer.create_circuit(constraint_system, witness); init_bn254_crs(acir_composer.get_dyadic_circuit_size()); acir_composer.init_proving_key(); - auto proof = acir_composer.create_proof(recursive); + auto proof = acir_composer.create_proof(); if (outputPath == "-") { writeRawBytesToStdout(proof); @@ -270,12 +265,12 @@ void gateCount(const std::string& bytecodePath) * @return true If the proof is valid * @return false If the proof is invalid */ -bool verify(const std::string& proof_path, bool recursive, const std::string& vk_path) +bool verify(const std::string& proof_path, const std::string& vk_path) { auto acir_composer = verifier_init(); auto vk_data = from_buffer(read_file(vk_path)); acir_composer.load_verification_key(std::move(vk_data)); - auto verified = acir_composer.verify_proof(read_file(proof_path), recursive); + auto verified = acir_composer.verify_proof(read_file(proof_path)); vinfo("verified: ", verified); return verified; @@ -491,7 +486,6 @@ int main(int argc, char* argv[]) std::string vk_path = get_option(args, "-k", "./target/vk"); std::string pk_path = get_option(args, "-r", "./target/pk"); CRS_PATH = get_option(args, "-c", CRS_PATH); - bool recursive = flag_present(args, "-r") || flag_present(args, "--recursive"); // Skip CRS initialization for any command which doesn't require the CRS. if (command == "--version") { @@ -504,21 +498,21 @@ int main(int argc, char* argv[]) return 0; } if (command == "prove_and_verify") { - return proveAndVerify(bytecode_path, witness_path, recursive) ? 0 : 1; + return proveAndVerify(bytecode_path, witness_path) ? 0 : 1; } if (command == "accumulate_and_verify_goblin") { return accumulateAndVerifyGoblin(bytecode_path, witness_path) ? 0 : 1; } if (command == "prove_and_verify_goblin") { - return proveAndVerifyGoblin(bytecode_path, witness_path, recursive) ? 0 : 1; + return proveAndVerifyGoblin(bytecode_path, witness_path) ? 0 : 1; } if (command == "prove") { std::string output_path = get_option(args, "-o", "./proofs/proof"); - prove(bytecode_path, witness_path, recursive, output_path); + prove(bytecode_path, witness_path, output_path); } else if (command == "gates") { gateCount(bytecode_path); } else if (command == "verify") { - return verify(proof_path, recursive, vk_path) ? 0 : 1; + return verify(proof_path, vk_path) ? 0 : 1; } else if (command == "contract") { std::string output_path = get_option(args, "-o", "./target/contract.sol"); contract(output_path, vk_path); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index 360d43324e9..dfe00b03703 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -193,7 +193,9 @@ void build_constraints(Builder& builder, AcirFormat const& constraint_system, bo template Builder create_circuit(const AcirFormat& constraint_system, size_t size_hint, WitnessVector const& witness) { - Builder builder{ size_hint, witness, constraint_system.public_inputs, constraint_system.varnum }; + Builder builder{ + size_hint, witness, constraint_system.public_inputs, constraint_system.varnum, constraint_system.recursive + }; bool has_valid_witness_assignments = !witness.empty(); build_constraints(builder, constraint_system, has_valid_witness_assignments); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp index f88777038b0..3c053d83814 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp @@ -22,6 +22,12 @@ namespace acir_format { struct AcirFormat { // The number of witnesses in the circuit uint32_t varnum; + // Specifies whether a prover that produces SNARK recursion friendly proofs should be used. + // The proof produced when this flag is true should be friendly for recursive verification inside + // of another SNARK. For example, a recursive friendly proof may use Blake3Pedersen for + // hashing in its transcript, while we still want a prove that uses Keccak for its transcript in order + // to be able to verify SNARKs on Ethereum. + bool recursive; std::vector public_inputs; diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp index 3c85f61d4d1..8908b348096 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp @@ -29,6 +29,7 @@ TEST_F(AcirFormatTests, TestASingleConstraintNoPubInputs) AcirFormat constraint_system{ .varnum = 4, + .recursive = false, .public_inputs = {}, .logic_constraints = {}, .range_constraints = {}, @@ -141,6 +142,7 @@ TEST_F(AcirFormatTests, TestLogicGateFromNoirCircuit) // EXPR [ (-1, _6) 1 ] AcirFormat constraint_system{ .varnum = 6, + .recursive = false, .public_inputs = { 1 }, .logic_constraints = { logic_constraint }, .range_constraints = { range_a, range_b }, @@ -205,6 +207,7 @@ TEST_F(AcirFormatTests, TestSchnorrVerifyPass) .signature = signature, }; AcirFormat constraint_system{ .varnum = 81, + .recursive = false, .public_inputs = {}, .logic_constraints = {}, .range_constraints = range_constraints, @@ -297,6 +300,7 @@ TEST_F(AcirFormatTests, TestSchnorrVerifySmallRange) }; AcirFormat constraint_system{ .varnum = 81, + .recursive = false, .public_inputs = {}, .logic_constraints = {}, .range_constraints = range_constraints, @@ -408,6 +412,7 @@ TEST_F(AcirFormatTests, TestVarKeccak) AcirFormat constraint_system{ .varnum = 36, + .recursive = false, .public_inputs = {}, .logic_constraints = {}, .range_constraints = { range_a, range_b, range_c, range_d }, @@ -451,6 +456,7 @@ TEST_F(AcirFormatTests, TestKeccakPermutation) }; AcirFormat constraint_system{ .varnum = 51, + .recursive = false, .public_inputs = {}, .logic_constraints = {}, .range_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp index 058feb5e6a9..52dd9935241 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp @@ -340,6 +340,7 @@ AcirFormat circuit_buf_to_acir_format(std::vector const& buf) AcirFormat af; // `varnum` is the true number of variables, thus we add one to the index which starts at zero af.varnum = circuit.current_witness_index + 1; + af.recursive = circuit.recursive; af.public_inputs = join({ map(circuit.public_parameters.value, [](auto e) { return e.value; }), map(circuit.return_values.value, [](auto e) { return e.value; }) }); std::map block_id_to_block_constraint; diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/bigint_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/bigint_constraint.test.cpp index e8c4b22b4bf..77717980a4e 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/bigint_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/bigint_constraint.test.cpp @@ -48,6 +48,7 @@ TEST_F(BigIntTests, TestBigIntConstraintDummy) AcirFormat constraint_system{ .varnum = 4, + .recursive = false, .public_inputs = {}, .logic_constraints = {}, .range_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp index 384520e7f80..a88e89de566 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp @@ -110,6 +110,7 @@ TEST_F(UltraPlonkRAM, TestBlockConstraint) size_t num_variables = generate_block_constraint(block, witness_values); AcirFormat constraint_system{ .varnum = static_cast(num_variables), + .recursive = false, .public_inputs = {}, .logic_constraints = {}, .range_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ec_operations.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ec_operations.test.cpp index 11ea6a77b9d..d64110d938d 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ec_operations.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ec_operations.test.cpp @@ -49,6 +49,7 @@ TEST_F(EcOperations, TestECOperations) AcirFormat constraint_system{ .varnum = static_cast(num_variables + 1), + .recursive = false, .public_inputs = {}, .logic_constraints = {}, .range_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp index fb3e405e2f1..ba6c67b2162 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp @@ -89,6 +89,7 @@ TEST_F(ECDSASecp256k1, TestECDSAConstraintSucceed) size_t num_variables = generate_ecdsa_constraint(ecdsa_k1_constraint, witness_values); AcirFormat constraint_system{ .varnum = static_cast(num_variables), + .recursive = false, .public_inputs = {}, .logic_constraints = {}, .range_constraints = {}, @@ -134,6 +135,7 @@ TEST_F(ECDSASecp256k1, TestECDSACompilesForVerifier) size_t num_variables = generate_ecdsa_constraint(ecdsa_k1_constraint, witness_values); AcirFormat constraint_system{ .varnum = static_cast(num_variables), + .recursive = false, .public_inputs = {}, .logic_constraints = {}, .range_constraints = {}, @@ -174,6 +176,7 @@ TEST_F(ECDSASecp256k1, TestECDSAConstraintFail) AcirFormat constraint_system{ .varnum = static_cast(num_variables), + .recursive = false, .public_inputs = {}, .logic_constraints = {}, .range_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp index fe64491c90c..ee9f4e2faff 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp @@ -124,6 +124,7 @@ TEST(ECDSASecp256r1, test_hardcoded) AcirFormat constraint_system{ .varnum = static_cast(num_variables), + .recursive = false, .public_inputs = {}, .logic_constraints = {}, .range_constraints = {}, @@ -170,6 +171,7 @@ TEST(ECDSASecp256r1, TestECDSAConstraintSucceed) size_t num_variables = generate_ecdsa_constraint(ecdsa_r1_constraint, witness_values); AcirFormat constraint_system{ .varnum = static_cast(num_variables), + .recursive = false, .public_inputs = {}, .logic_constraints = {}, .range_constraints = {}, @@ -214,6 +216,7 @@ TEST(ECDSASecp256r1, TestECDSACompilesForVerifier) size_t num_variables = generate_ecdsa_constraint(ecdsa_r1_constraint, witness_values); AcirFormat constraint_system{ .varnum = static_cast(num_variables), + .recursive = false, .public_inputs = {}, .logic_constraints = {}, .range_constraints = {}, @@ -253,6 +256,7 @@ TEST(ECDSASecp256r1, TestECDSAConstraintFail) AcirFormat constraint_system{ .varnum = static_cast(num_variables), + .recursive = false, .public_inputs = {}, .logic_constraints = {}, .range_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index f281f965eea..cc8fde631b4 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -82,6 +82,7 @@ Builder create_inner_circuit() }; AcirFormat constraint_system{ .varnum = 6, + .recursive = true, .public_inputs = { 1, 2 }, .logic_constraints = { logic_constraint }, .range_constraints = { range_a, range_b }, @@ -235,6 +236,7 @@ Builder create_outer_circuit(std::vector& inner_circuits) } AcirFormat constraint_system{ .varnum = static_cast(witness.size()), + .recursive = false, .public_inputs = {}, .logic_constraints = {}, .range_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp index c983baf68b0..02adc0e63e6 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp @@ -1118,6 +1118,7 @@ struct Circuit { PublicInputs public_parameters; PublicInputs return_values; std::vector> assert_messages; + bool recursive; friend bool operator==(const Circuit&, const Circuit&); std::vector bincodeSerialize() const; @@ -5938,6 +5939,9 @@ inline bool operator==(const Circuit& lhs, const Circuit& rhs) if (!(lhs.assert_messages == rhs.assert_messages)) { return false; } + if (!(lhs.recursive == rhs.recursive)) { + return false; + } return true; } @@ -5971,6 +5975,7 @@ void serde::Serializable::serialize(const Circuit::Circuit& ob serde::Serializable::serialize(obj.public_parameters, serializer); serde::Serializable::serialize(obj.return_values, serializer); serde::Serializable::serialize(obj.assert_messages, serializer); + serde::Serializable::serialize(obj.recursive, serializer); serializer.decrease_container_depth(); } @@ -5986,6 +5991,7 @@ Circuit::Circuit serde::Deserializable::deserialize(Deserializ obj.public_parameters = serde::Deserializable::deserialize(deserializer); obj.return_values = serde::Deserializable::deserialize(deserializer); obj.assert_messages = serde::Deserializable::deserialize(deserializer); + obj.recursive = serde::Deserializable::deserialize(deserializer); deserializer.decrease_container_depth(); return obj; } diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp index 072540db80f..c7fae9f8c19 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp @@ -8,6 +8,7 @@ #include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" #include "barretenberg/stdlib/primitives/circuit_builders/circuit_builders_fwd.hpp" #include "contract.hpp" +#include namespace acir_proofs { @@ -29,6 +30,7 @@ void AcirComposer::create_circuit(acir_format::AcirFormat& constraint_system, Wi vinfo("building circuit..."); builder_ = acir_format::create_circuit(constraint_system, size_hint_, witness); vinfo("gates: ", builder_.get_total_circuit_size()); + vinfo("circuit is recursive friendly: ", builder_.is_recursive_circuit); } std::shared_ptr AcirComposer::init_proving_key() @@ -39,7 +41,7 @@ std::shared_ptr AcirComposer::init_proving_key() return proving_key_; } -std::vector AcirComposer::create_proof(bool is_recursive) +std::vector AcirComposer::create_proof() { if (!proving_key_) { throw_or_abort("Must compute proving key before constructing proof."); @@ -49,7 +51,7 @@ std::vector AcirComposer::create_proof(bool is_recursive) vinfo("creating proof..."); std::vector proof; - if (is_recursive) { + if (builder_.is_recursive_circuit) { auto prover = composer.create_prover(builder_); proof = prover.construct_proof().proof_data; } else { @@ -68,6 +70,7 @@ std::shared_ptr AcirComposer::init_verification_key vinfo("computing verification key..."); acir_format::Composer composer(proving_key_, nullptr); verification_key_ = composer.compute_verification_key(builder_); + vinfo("done."); return verification_key_; } @@ -78,7 +81,7 @@ void AcirComposer::load_verification_key(bb::plonk::verification_key_data&& data std::make_shared(std::move(data), srs::get_crs_factory()->get_verifier_crs()); } -bool AcirComposer::verify_proof(std::vector const& proof, bool is_recursive) +bool AcirComposer::verify_proof(std::vector const& proof) { acir_format::Composer composer(proving_key_, verification_key_); @@ -91,17 +94,7 @@ bool AcirComposer::verify_proof(std::vector const& proof, bool is_recur // Hack. Shouldn't need to do this. 2144 is size with no public inputs. builder_.public_inputs.resize((proof.size() - 2144) / 32); - // TODO: We could get rid of this, if we made the Noir program specify whether something should be - // TODO: created with the recursive setting or not. ie: - // - // #[recursive_friendly] - // fn main() {} - // would put in the ACIR that we want this to be recursion friendly with a flag maybe and the backend - // would set the is_recursive flag to be true. - // This would eliminate the need for nargo to have a --recursive flag - // - // End result is that we may just be able to get it off of builder_, like builder_.is_recursive_friendly - if (is_recursive) { + if (verification_key_->is_recursive_circuit) { auto verifier = composer.create_verifier(builder_); return verifier.verify_proof({ proof }); } else { diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp index a83d7f85c95..9ff9b51ace3 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp @@ -21,13 +21,13 @@ class AcirComposer { std::shared_ptr init_proving_key(); - std::vector create_proof(bool is_recursive); + std::vector create_proof(); void load_verification_key(bb::plonk::verification_key_data&& data); std::shared_ptr init_verification_key(); - bool verify_proof(std::vector const& proof, bool is_recursive); + bool verify_proof(std::vector const& proof); std::string get_solidity_verifier(); size_t get_total_circuit_size() { return builder_.get_total_circuit_size(); }; diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp index 6572e08a6a2..7fc9eff28f3 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp @@ -49,7 +49,6 @@ WASM_EXPORT void acir_init_proving_key(in_ptr acir_composer_ptr, uint8_t const* WASM_EXPORT void acir_create_proof(in_ptr acir_composer_ptr, uint8_t const* acir_vec, uint8_t const* witness_vec, - bool const* is_recursive, uint8_t** out) { auto acir_composer = reinterpret_cast(*acir_composer_ptr); @@ -59,8 +58,8 @@ WASM_EXPORT void acir_create_proof(in_ptr acir_composer_ptr, acir_composer->create_circuit(constraint_system, witness); acir_composer->init_proving_key(); - auto proof = acir_composer->create_proof(*is_recursive); - *out = to_heap_buffer(proof); + auto proof_data = acir_composer->create_proof(); + *out = to_heap_buffer(proof_data); } WASM_EXPORT void acir_goblin_accumulate(in_ptr acir_composer_ptr, @@ -144,14 +143,11 @@ WASM_EXPORT void acir_goblin_verify(in_ptr acir_composer_ptr, uint8_t const* pro *result = acir_composer->verify(proof); } -WASM_EXPORT void acir_verify_proof(in_ptr acir_composer_ptr, - uint8_t const* proof_buf, - bool const* is_recursive, - bool* result) +WASM_EXPORT void acir_verify_proof(in_ptr acir_composer_ptr, uint8_t const* proof_buf, bool* result) { auto acir_composer = reinterpret_cast(*acir_composer_ptr); auto proof = from_buffer>(proof_buf); - *result = acir_composer->verify_proof(proof, *is_recursive); + *result = acir_composer->verify_proof(proof); } WASM_EXPORT void acir_get_solidity_verifier(in_ptr acir_composer_ptr, out_str_buf out) diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp index 76b24886b1d..4dfc3259947 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp @@ -31,7 +31,6 @@ WASM_EXPORT void acir_init_proving_key(in_ptr acir_composer_ptr, uint8_t const* WASM_EXPORT void acir_create_proof(in_ptr acir_composer_ptr, uint8_t const* constraint_system_buf, uint8_t const* witness_buf, - bool const* is_recursive, uint8_t** out); /** @@ -62,10 +61,7 @@ WASM_EXPORT void acir_get_verification_key(in_ptr acir_composer_ptr, uint8_t** o WASM_EXPORT void acir_get_proving_key(in_ptr acir_composer_ptr, uint8_t const* acir_vec, uint8_t** out); -WASM_EXPORT void acir_verify_proof(in_ptr acir_composer_ptr, - uint8_t const* proof_buf, - bool const* is_recursive, - bool* result); +WASM_EXPORT void acir_verify_proof(in_ptr acir_composer_ptr, uint8_t const* proof_buf, bool* result); /** * @brief Verifies a GUH proof produced during goblin accumulation diff --git a/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp b/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp index d293f509d4f..ad3aa5fd260 100644 --- a/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp +++ b/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp @@ -497,6 +497,8 @@ std::shared_ptr UltraComposer::compute_verification_key circuit_verification_key->contains_recursive_proof = circuit_constructor.contains_recursive_proof; + circuit_verification_key->is_recursive_circuit = circuit_constructor.is_recursive_circuit; + return circuit_verification_key; } diff --git a/barretenberg/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp b/barretenberg/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp index 2b7ee9fa75e..0022b3de741 100644 --- a/barretenberg/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp +++ b/barretenberg/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp @@ -92,6 +92,7 @@ verification_key::verification_key(verification_key_data&& data, , polynomial_manifest(static_cast(data.circuit_type)) , contains_recursive_proof(data.contains_recursive_proof) , recursive_proof_public_input_indices(std::move(data.recursive_proof_public_input_indices)) + , is_recursive_circuit(data.is_recursive_circuit) {} verification_key::verification_key(const verification_key& other) diff --git a/barretenberg/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp b/barretenberg/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp index 5bf59acd4d2..c09c8d32558 100644 --- a/barretenberg/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp +++ b/barretenberg/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp @@ -18,6 +18,7 @@ struct verification_key_data { std::map commitments; bool contains_recursive_proof = false; std::vector recursive_proof_public_input_indices; + bool is_recursive_circuit = false; // for serialization: update with any new fields MSGPACK_FIELDS(circuit_type, @@ -25,7 +26,8 @@ struct verification_key_data { num_public_inputs, commitments, contains_recursive_proof, - recursive_proof_public_input_indices); + recursive_proof_public_input_indices, + is_recursive_circuit); [[nodiscard]] bb::fr hash_native(size_t hash_index = 0) const; }; @@ -36,7 +38,8 @@ inline std::ostream& operator<<(std::ostream& os, verification_key_data const& k << "key.num_public_inputs: " << static_cast(key.num_public_inputs) << "\n" << "key.commitments: " << key.commitments << "\n" << "key.contains_recursive_proof: " << key.contains_recursive_proof << "\n" - << "key.recursive_proof_public_input_indices: " << key.recursive_proof_public_input_indices << "\n"; + << "key.recursive_proof_public_input_indices: " << key.recursive_proof_public_input_indices << "\n" + << "key.is_recursive_circuit: " << key.is_recursive_circuit << "\n"; }; inline bool operator==(verification_key_data const& lhs, verification_key_data const& rhs) @@ -72,6 +75,7 @@ struct verification_key { .commitments = commitments, .contains_recursive_proof = contains_recursive_proof, .recursive_proof_public_input_indices = recursive_proof_public_input_indices, + .is_recursive_circuit = is_recursive_circuit, }; } @@ -94,17 +98,23 @@ struct verification_key { bool contains_recursive_proof = false; std::vector recursive_proof_public_input_indices; + + bool is_recursive_circuit = false; + size_t program_width = 3; // for serialization: update with new fields void msgpack_pack(auto& packer) const { - verification_key_data data = { static_cast(circuit_type), - static_cast(circuit_size), - static_cast(num_public_inputs), - commitments, - contains_recursive_proof, - recursive_proof_public_input_indices }; + verification_key_data data = { + static_cast(circuit_type), + static_cast(circuit_size), + static_cast(num_public_inputs), + commitments, + contains_recursive_proof, + recursive_proof_public_input_indices, + is_recursive_circuit, + }; packer.pack(data); } void msgpack_unpack(auto obj) diff --git a/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/circuit_builder_base.hpp b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/circuit_builder_base.hpp index ffc8e1dce5d..5231f1cad23 100644 --- a/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/circuit_builder_base.hpp +++ b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/circuit_builder_base.hpp @@ -35,10 +35,17 @@ template class CircuitBuilderBase { // DOCTODO(#231): replace with the relevant wiki link. std::map tau; - // Publicin put indices which contain recursive proof information + // Public input indices which contain recursive proof information std::vector recursive_proof_public_input_indices; bool contains_recursive_proof = false; + // We only know from the circuit description whether a circuit should use a prover which produces + // proofs that are friendly to verify in a circuit themselves. However, a verifier does not need a full circuit + // description and should be able to verify a proof with just the verification key and the proof. + // This field exists to later set the same field in the verification key, and make sure + // that we are using the correct prover/verifier. + bool is_recursive_circuit = false; + bool _failed = false; std::string _err; static constexpr uint32_t REAL_VARIABLE = UINT32_MAX - 1; diff --git a/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp index 2ef60b71cc3..4ee4751b403 100644 --- a/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp +++ b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp @@ -673,7 +673,8 @@ class UltraCircuitBuilder_ : public CircuitBuilderBase& public_inputs, - size_t varnum) + size_t varnum, + bool recursive = false) : CircuitBuilderBase(size_hint) { selectors.reserve(size_hint); @@ -696,6 +697,8 @@ class UltraCircuitBuilder_ : public CircuitBuilderBasezero_idx = put_constant_variable(FF::zero()); this->tau.insert({ DUMMY_TAG, DUMMY_TAG }); // TODO(luke): explain this + + this->is_recursive_circuit = recursive; }; UltraCircuitBuilder_(const UltraCircuitBuilder_& other) = default; UltraCircuitBuilder_(UltraCircuitBuilder_&& other) diff --git a/barretenberg/exports.json b/barretenberg/exports.json index 3925ff1fa54..f04b2871163 100644 --- a/barretenberg/exports.json +++ b/barretenberg/exports.json @@ -536,10 +536,6 @@ { "name": "witness_buf", "type": "const uint8_t *" - }, - { - "name": "is_recursive", - "type": "const bool *" } ], "outArgs": [ @@ -670,10 +666,6 @@ { "name": "proof_buf", "type": "const uint8_t *" - }, - { - "name": "is_recursive", - "type": "const bool *" } ], "outArgs": [ diff --git a/barretenberg/ts/src/barretenberg_api/index.ts b/barretenberg/ts/src/barretenberg_api/index.ts index d6280851562..1a337c33e2a 100644 --- a/barretenberg/ts/src/barretenberg_api/index.ts +++ b/barretenberg/ts/src/barretenberg_api/index.ts @@ -356,9 +356,8 @@ export class BarretenbergApi { acirComposerPtr: Ptr, constraintSystemBuf: Uint8Array, witnessBuf: Uint8Array, - isRecursive: boolean, ): Promise { - const inArgs = [acirComposerPtr, constraintSystemBuf, witnessBuf, isRecursive].map(serializeBufferable); + const inArgs = [acirComposerPtr, constraintSystemBuf, witnessBuf].map(serializeBufferable); const outTypes: OutputType[] = [BufferDeserializer()]; const result = await this.wasm.callWasmExport( 'acir_create_proof', @@ -449,8 +448,8 @@ export class BarretenbergApi { return out[0]; } - async acirVerifyProof(acirComposerPtr: Ptr, proofBuf: Uint8Array, isRecursive: boolean): Promise { - const inArgs = [acirComposerPtr, proofBuf, isRecursive].map(serializeBufferable); + async acirVerifyProof(acirComposerPtr: Ptr, proofBuf: Uint8Array): Promise { + const inArgs = [acirComposerPtr, proofBuf].map(serializeBufferable); const outTypes: OutputType[] = [BoolDeserializer()]; const result = await this.wasm.callWasmExport( 'acir_verify_proof', @@ -865,13 +864,8 @@ export class BarretenbergApiSync { return; } - acirCreateProof( - acirComposerPtr: Ptr, - constraintSystemBuf: Uint8Array, - witnessBuf: Uint8Array, - isRecursive: boolean, - ): Uint8Array { - const inArgs = [acirComposerPtr, constraintSystemBuf, witnessBuf, isRecursive].map(serializeBufferable); + acirCreateProof(acirComposerPtr: Ptr, constraintSystemBuf: Uint8Array, witnessBuf: Uint8Array): Uint8Array { + const inArgs = [acirComposerPtr, constraintSystemBuf, witnessBuf].map(serializeBufferable); const outTypes: OutputType[] = [BufferDeserializer()]; const result = this.wasm.callWasmExport( 'acir_create_proof', @@ -954,8 +948,8 @@ export class BarretenbergApiSync { return out[0]; } - acirVerifyProof(acirComposerPtr: Ptr, proofBuf: Uint8Array, isRecursive: boolean): boolean { - const inArgs = [acirComposerPtr, proofBuf, isRecursive].map(serializeBufferable); + acirVerifyProof(acirComposerPtr: Ptr, proofBuf: Uint8Array): boolean { + const inArgs = [acirComposerPtr, proofBuf].map(serializeBufferable); const outTypes: OutputType[] = [BoolDeserializer()]; const result = this.wasm.callWasmExport( 'acir_verify_proof', diff --git a/barretenberg/ts/src/main.ts b/barretenberg/ts/src/main.ts index d06de96431d..ec974fd1c35 100755 --- a/barretenberg/ts/src/main.ts +++ b/barretenberg/ts/src/main.ts @@ -99,7 +99,7 @@ async function initLite() { return { api, acirComposer }; } -export async function proveAndVerify(bytecodePath: string, witnessPath: string, crsPath: string, isRecursive: boolean) { +export async function proveAndVerify(bytecodePath: string, witnessPath: string, crsPath: string) { /* eslint-disable camelcase */ const acir_test = path.basename(process.cwd()); @@ -116,11 +116,11 @@ export async function proveAndVerify(bytecodePath: string, witnessPath: string, writeBenchmark('subgroup_size', subgroupSize, { acir_test, threads }); const proofTimer = new Timer(); - const proof = await api.acirCreateProof(acirComposer, bytecode, witness, isRecursive); + const proof = await api.acirCreateProof(acirComposer, bytecode, witness); writeBenchmark('proof_construction_time', proofTimer.ms(), { acir_test, threads }); debug(`verifying...`); - const verified = await api.acirVerifyProof(acirComposer, proof, isRecursive); + const verified = await api.acirVerifyProof(acirComposer, proof); debug(`verified: ${verified}`); return verified; } finally { @@ -186,19 +186,13 @@ export async function proveAndVerifyGoblin(bytecodePath: string, witnessPath: st /* eslint-enable camelcase */ } -export async function prove( - bytecodePath: string, - witnessPath: string, - crsPath: string, - isRecursive: boolean, - outputPath: string, -) { +export async function prove(bytecodePath: string, witnessPath: string, crsPath: string, outputPath: string) { const { api, acirComposer } = await init(bytecodePath, crsPath); try { debug(`creating proof...`); const bytecode = getBytecode(bytecodePath); const witness = getWitness(witnessPath); - const proof = await api.acirCreateProof(acirComposer, bytecode, witness, isRecursive); + const proof = await api.acirCreateProof(acirComposer, bytecode, witness); debug(`done.`); if (outputPath === '-') { @@ -241,11 +235,11 @@ export function acvmInfo(outputPath: string) { } } -export async function verify(proofPath: string, isRecursive: boolean, vkPath: string) { +export async function verify(proofPath: string, vkPath: string) { const { api, acirComposer } = await initLite(); try { await api.acirLoadVerificationKey(acirComposer, new RawBuffer(readFileSync(vkPath))); - const verified = await api.acirVerifyProof(acirComposer, readFileSync(proofPath), isRecursive); + const verified = await api.acirVerifyProof(acirComposer, readFileSync(proofPath)); debug(`verified: ${verified}`); return verified; } finally { @@ -379,10 +373,9 @@ program .description('Generate a proof and verify it. Process exits with success or failure code.') .option('-b, --bytecode-path ', 'Specify the bytecode path', './target/acir.gz') .option('-w, --witness-path ', 'Specify the witness path', './target/witness.gz') - .option('-r, --recursive', 'prove and verify using recursive prover and verifier', false) - .action(async ({ bytecodePath, witnessPath, recursive, crsPath }) => { + .action(async ({ bytecodePath, witnessPath, crsPath }) => { handleGlobalOptions(); - const result = await proveAndVerify(bytecodePath, witnessPath, crsPath, recursive); + const result = await proveAndVerify(bytecodePath, witnessPath, crsPath); process.exit(result ? 0 : 1); }); @@ -413,11 +406,10 @@ program .description('Generate a proof and write it to a file.') .option('-b, --bytecode-path ', 'Specify the bytecode path', './target/acir.gz') .option('-w, --witness-path ', 'Specify the witness path', './target/witness.gz') - .option('-r, --recursive', 'prove using recursive prover', false) .option('-o, --output-path ', 'Specify the proof output path', './proofs/proof') - .action(async ({ bytecodePath, witnessPath, recursive, outputPath, crsPath }) => { + .action(async ({ bytecodePath, witnessPath, outputPath, crsPath }) => { handleGlobalOptions(); - await prove(bytecodePath, witnessPath, crsPath, recursive, outputPath); + await prove(bytecodePath, witnessPath, crsPath, outputPath); }); program @@ -433,11 +425,10 @@ program .command('verify') .description('Verify a proof. Process exists with success or failure code.') .requiredOption('-p, --proof-path ', 'Specify the path to the proof') - .option('-r, --recursive', 'prove using recursive prover', false) .requiredOption('-k, --vk ', 'path to a verification key. avoids recomputation.') - .action(async ({ proofPath, recursive, vk }) => { + .action(async ({ proofPath, vk }) => { handleGlobalOptions(); - const result = await verify(proofPath, recursive, vk); + const result = await verify(proofPath, vk); process.exit(result ? 0 : 1); }); diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index 6bf33898baa..d367cd2206c 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -73,10 +73,10 @@ library Constants { uint256 internal constant HEADER_LENGTH = 18; uint256 internal constant FUNCTION_DATA_LENGTH = 4; uint256 internal constant CONTRACT_DEPLOYMENT_DATA_LENGTH = 6; - uint256 internal constant PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH = 200; + uint256 internal constant PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH = 204; uint256 internal constant CONTRACT_STORAGE_UPDATE_REQUEST_LENGTH = 3; uint256 internal constant CONTRACT_STORAGE_READ_LENGTH = 2; - uint256 internal constant PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 190; + uint256 internal constant PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 201; uint256 internal constant GET_NOTES_ORACLE_RETURN_LENGTH = 674; uint256 internal constant CALL_PRIVATE_FUNCTION_RETURN_SIZE = 210; uint256 internal constant PUBLIC_CIRCUIT_PUBLIC_INPUTS_HASH_INPUT_LENGTH = 98; diff --git a/noir/.gitrepo b/noir/.gitrepo index debb993e0bc..4e626e98025 100644 --- a/noir/.gitrepo +++ b/noir/.gitrepo @@ -1,12 +1,12 @@ ; DO NOT EDIT (unless you know what you are doing) ; ; This subdirectory is a git "subrepo", and this file is maintained by the -; git-subrepo command. See https://github.com/ingydotnet/git-subrepo#readme +; git-subrepo command. See ; [subrepo] - remote = https://github.com/noir-lang/noir - branch = aztec-packages - commit = ddd94a2f7f620da14e4222c2325119737b91908d - parent = 4ddf8f42d005d3a1b72fac6b9d40c475a3c4231d - method = merge - cmdver = 0.4.6 + remote = + branch = aztec-packages + commit = 9a70040211d205f472aa31924649c2fef999eb49 + parent = 9c965a7c9e652dfeaba2f09152e5db287407473d + method = merge + cmdver = 0.4.6 diff --git a/noir/acvm-repo/acir/codegen/acir.cpp b/noir/acvm-repo/acir/codegen/acir.cpp index 1b9b9c2e5bd..bdee08794e6 100644 --- a/noir/acvm-repo/acir/codegen/acir.cpp +++ b/noir/acvm-repo/acir/codegen/acir.cpp @@ -1057,6 +1057,7 @@ namespace Circuit { Circuit::PublicInputs public_parameters; Circuit::PublicInputs return_values; std::vector> assert_messages; + bool recursive; friend bool operator==(const Circuit&, const Circuit&); std::vector bincodeSerialize() const; @@ -4889,6 +4890,7 @@ namespace Circuit { if (!(lhs.public_parameters == rhs.public_parameters)) { return false; } if (!(lhs.return_values == rhs.return_values)) { return false; } if (!(lhs.assert_messages == rhs.assert_messages)) { return false; } + if (!(lhs.recursive == rhs.recursive)) { return false; } return true; } @@ -4919,6 +4921,7 @@ void serde::Serializable::serialize(const Circuit::Circuit &ob serde::Serializable::serialize(obj.public_parameters, serializer); serde::Serializable::serialize(obj.return_values, serializer); serde::Serializable::serialize(obj.assert_messages, serializer); + serde::Serializable::serialize(obj.recursive, serializer); serializer.decrease_container_depth(); } @@ -4933,6 +4936,7 @@ Circuit::Circuit serde::Deserializable::deserialize(Deserializ obj.public_parameters = serde::Deserializable::deserialize(deserializer); obj.return_values = serde::Deserializable::deserialize(deserializer); obj.assert_messages = serde::Deserializable::deserialize(deserializer); + obj.recursive = serde::Deserializable::deserialize(deserializer); deserializer.decrease_container_depth(); return obj; } diff --git a/noir/acvm-repo/acir/src/circuit/mod.rs b/noir/acvm-repo/acir/src/circuit/mod.rs index b248b30b1d9..ccfb19bbf05 100644 --- a/noir/acvm-repo/acir/src/circuit/mod.rs +++ b/noir/acvm-repo/acir/src/circuit/mod.rs @@ -39,6 +39,11 @@ pub struct Circuit { // c++ code at the moment when it is, due to OpcodeLocation needing a comparison // implementation which is never generated. pub assert_messages: Vec<(OpcodeLocation, String)>, + + /// States whether the backend should use a SNARK recursion friendly prover. + /// If implemented by a backend, this means that proofs generated with this circuit + /// will be friendly for recursively verifying inside of another SNARK. + pub recursive: bool, } impl Circuit { @@ -318,6 +323,7 @@ mod tests { public_parameters: PublicInputs(BTreeSet::from_iter(vec![Witness(2), Witness(12)])), return_values: PublicInputs(BTreeSet::from_iter(vec![Witness(4), Witness(12)])), assert_messages: Default::default(), + recursive: false, }; fn read_write(circuit: Circuit) -> (Circuit, Circuit) { @@ -348,6 +354,7 @@ mod tests { public_parameters: PublicInputs(BTreeSet::from_iter(vec![Witness(2)])), return_values: PublicInputs(BTreeSet::from_iter(vec![Witness(2)])), assert_messages: Default::default(), + recursive: false, }; let json = serde_json::to_string_pretty(&circuit).unwrap(); diff --git a/noir/compiler/integration-tests/test/browser/compile_prove_verify.test.ts b/noir/compiler/integration-tests/test/browser/compile_prove_verify.test.ts index 0a829def09e..dba51895bb8 100644 --- a/noir/compiler/integration-tests/test/browser/compile_prove_verify.test.ts +++ b/noir/compiler/integration-tests/test/browser/compile_prove_verify.test.ts @@ -59,11 +59,11 @@ test_cases.forEach((testInfo) => { // JS Proving - const proofWithPublicInputs = await program.generateFinalProof(inputs); + const proofWithPublicInputs = await program.generateProof(inputs); // JS verification - const verified = await program.verifyFinalProof(proofWithPublicInputs); + const verified = await program.verifyProof(proofWithPublicInputs); expect(verified, 'Proof fails verification in JS').to.be.true; }); diff --git a/noir/compiler/integration-tests/test/browser/recursion.test.ts b/noir/compiler/integration-tests/test/browser/recursion.test.ts index 80199de5701..a8927aa6a75 100644 --- a/noir/compiler/integration-tests/test/browser/recursion.test.ts +++ b/noir/compiler/integration-tests/test/browser/recursion.test.ts @@ -19,7 +19,7 @@ await newABICoder(); await initACVM(); const base_relative_path = '../../../../..'; -const circuit_main = 'test_programs/execution_success/assert_statement'; +const circuit_main = 'test_programs/execution_success/assert_statement_recursive'; const circuit_recursion = 'compiler/integration-tests/circuits/recursion'; async function getCircuit(projectPath: string) { @@ -48,15 +48,15 @@ describe('It compiles noir program code, receiving circuit bytes and abi object. const { witness: main_witnessUint8Array } = await new Noir(main_program).execute(main_inputs); - const main_proof = await main_backend.generateIntermediateProof(main_witnessUint8Array); - const main_verification = await main_backend.verifyIntermediateProof(main_proof); + const main_proof = await main_backend.generateProof(main_witnessUint8Array); + const main_verification = await main_backend.verifyProof(main_proof); logger.debug('main_verification', main_verification); expect(main_verification).to.be.true; const numPublicInputs = 1; - const { proofAsFields, vkAsFields, vkHash } = await main_backend.generateIntermediateProofArtifacts( + const { proofAsFields, vkAsFields, vkHash } = await main_backend.generateRecursiveProofArtifacts( main_proof, numPublicInputs, ); @@ -76,20 +76,20 @@ describe('It compiles noir program code, receiving circuit bytes and abi object. const { witness: recursion_witnessUint8Array } = await new Noir(recursion_program).execute(recursion_inputs); - const recursion_proof = await recursion_backend.generateFinalProof(recursion_witnessUint8Array); + const recursion_proof = await recursion_backend.generateProof(recursion_witnessUint8Array); // Causes an "unreachable" error. // Due to the fact that it's a non-recursive proof? // // const recursion_numPublicInputs = 1; - // const { proofAsFields: recursion_proofAsFields } = await recursion_backend.generateIntermediateProofArtifacts( + // const { proofAsFields: recursion_proofAsFields } = await recursion_backend.generateRecursiveProofArtifacts( // recursion_proof, // recursion_numPublicInputs, // ); // // logger.debug('recursion_proofAsFields', recursion_proofAsFields); - const recursion_verification = await recursion_backend.verifyFinalProof(recursion_proof); + const recursion_verification = await recursion_backend.verifyProof(recursion_proof); logger.debug('recursion_verification', recursion_verification); diff --git a/noir/compiler/integration-tests/test/node/onchain_recursive_verification.test.ts b/noir/compiler/integration-tests/test/node/onchain_recursive_verification.test.ts index 9cdd80edc15..6147f770f16 100644 --- a/noir/compiler/integration-tests/test/node/onchain_recursive_verification.test.ts +++ b/noir/compiler/integration-tests/test/node/onchain_recursive_verification.test.ts @@ -16,7 +16,7 @@ it(`smart contract can verify a recursive proof`, async () => { const fm = createFileManager(basePath); const innerCompilationResult = await compile( fm, - join(basePath, './test_programs/execution_success/assert_statement'), + join(basePath, './test_programs/execution_success/assert_statement_recursive'), ); if (!('program' in innerCompilationResult)) { throw new Error('Compilation failed'); @@ -38,17 +38,17 @@ it(`smart contract can verify a recursive proof`, async () => { const inner = new Noir(innerProgram); const inner_prover_toml = readFileSync( - join(basePath, `./test_programs/execution_success/assert_statement/Prover.toml`), + join(basePath, `./test_programs/execution_success/assert_statement_recursive/Prover.toml`), ).toString(); const inner_inputs = toml.parse(inner_prover_toml); const { witness: main_witness } = await inner.execute(inner_inputs); - const intermediate_proof = await inner_backend.generateIntermediateProof(main_witness); + const intermediate_proof = await inner_backend.generateProof(main_witness); - expect(await inner_backend.verifyIntermediateProof(intermediate_proof)).to.be.true; + expect(await inner_backend.verifyProof(intermediate_proof)).to.be.true; - const { proofAsFields, vkAsFields, vkHash } = await inner_backend.generateIntermediateProofArtifacts( + const { proofAsFields, vkAsFields, vkHash } = await inner_backend.generateRecursiveProofArtifacts( intermediate_proof, 1, // 1 public input ); @@ -65,8 +65,8 @@ it(`smart contract can verify a recursive proof`, async () => { key_hash: vkHash, }; - const recursion_proof = await recursion.generateFinalProof(recursion_inputs); - expect(await recursion.verifyFinalProof(recursion_proof)).to.be.true; + const recursion_proof = await recursion.generateProof(recursion_inputs); + expect(await recursion.verifyProof(recursion_proof)).to.be.true; // Smart contract verification diff --git a/noir/compiler/integration-tests/test/node/smart_contract_verifier.test.ts b/noir/compiler/integration-tests/test/node/smart_contract_verifier.test.ts index d870956ea7a..79a0520da32 100644 --- a/noir/compiler/integration-tests/test/node/smart_contract_verifier.test.ts +++ b/noir/compiler/integration-tests/test/node/smart_contract_verifier.test.ts @@ -46,11 +46,11 @@ test_cases.forEach((testInfo) => { const prover_toml = readFileSync(resolve(`${base_relative_path}/${test_case}/Prover.toml`)).toString(); const inputs = toml.parse(prover_toml); - const proofData = await program.generateFinalProof(inputs); + const proofData = await program.generateProof(inputs); // JS verification - const verified = await program.verifyFinalProof(proofData); + const verified = await program.verifyProof(proofData); expect(verified, 'Proof fails verification in JS').to.be.true; // Smart contract verification diff --git a/noir/compiler/noirc_evaluator/src/ssa.rs b/noir/compiler/noirc_evaluator/src/ssa.rs index 0e3076923e0..108737f1f75 100644 --- a/noir/compiler/noirc_evaluator/src/ssa.rs +++ b/noir/compiler/noirc_evaluator/src/ssa.rs @@ -85,6 +85,7 @@ pub fn create_circuit( enable_brillig_logging: bool, ) -> Result<(Circuit, DebugInfo, Vec, Vec, Vec), RuntimeError> { let func_sig = program.main_function_signature.clone(); + let recursive = program.recursive; let mut generated_acir = optimize_into_acir(program, enable_ssa_logging, enable_brillig_logging)?; let opcodes = generated_acir.take_opcodes(); @@ -111,6 +112,7 @@ pub fn create_circuit( public_parameters, return_values, assert_messages: assert_messages.into_iter().collect(), + recursive, }; // This converts each im::Vector in the BTreeMap to a Vec diff --git a/noir/compiler/noirc_frontend/src/ast/function.rs b/noir/compiler/noirc_frontend/src/ast/function.rs index f20fc54b101..46f0ac0fa0f 100644 --- a/noir/compiler/noirc_frontend/src/ast/function.rs +++ b/noir/compiler/noirc_frontend/src/ast/function.rs @@ -29,6 +29,7 @@ pub enum FunctionKind { Builtin, Normal, Oracle, + Recursive, } impl NoirFunction { @@ -106,6 +107,7 @@ impl From for NoirFunction { Some(FunctionAttribute::Foreign(_)) => FunctionKind::LowLevel, Some(FunctionAttribute::Test { .. }) => FunctionKind::Normal, Some(FunctionAttribute::Oracle(_)) => FunctionKind::Oracle, + Some(FunctionAttribute::Recursive) => FunctionKind::Recursive, None => FunctionKind::Normal, }; diff --git a/noir/compiler/noirc_frontend/src/hir/resolution/errors.rs b/noir/compiler/noirc_frontend/src/hir/resolution/errors.rs index 7bd4de77e84..0752838c82e 100644 --- a/noir/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/noir/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -84,6 +84,8 @@ pub enum ResolverError { InvalidTypeForEntryPoint { span: Span }, #[error("Nested slices are not supported")] NestedSlices { span: Span }, + #[error("#[recursive] attribute is only allowed on entry points to a program")] + MisplacedRecursiveAttribute { ident: Ident }, #[error("Usage of the `#[foreign]` or `#[builtin]` function attributes are not allowed outside of the Noir standard library")] LowLevelFunctionOutsideOfStdlib { ident: Ident }, } @@ -313,6 +315,18 @@ impl From for Diagnostic { "Try to use a constant sized array instead".into(), span, ), + ResolverError::MisplacedRecursiveAttribute { ident } => { + let name = &ident.0.contents; + + let mut diag = Diagnostic::simple_error( + format!("misplaced #[recursive] attribute on function {name} rather than the main function"), + "misplaced #[recursive] attribute".to_string(), + ident.0.span(), + ); + + diag.add_note("The `#[recursive]` attribute specifies to the backend whether it should use a prover which generates proofs that are friendly for recursive verification in another circuit".to_owned()); + diag + } ResolverError::LowLevelFunctionOutsideOfStdlib { ident } => Diagnostic::simple_error( "Definition of low-level function outside of standard library".into(), "Usage of the `#[foreign]` or `#[builtin]` function attributes are not allowed outside of the Noir standard library".into(), diff --git a/noir/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/noir/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 8243b684c8a..f025f817b09 100644 --- a/noir/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/noir/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -414,7 +414,7 @@ impl<'a> Resolver<'a> { FunctionKind::Builtin | FunctionKind::LowLevel | FunctionKind::Oracle => { HirFunction::empty() } - FunctionKind::Normal => { + FunctionKind::Normal | FunctionKind::Recursive => { let expr_id = self.intern_block(func.def.body); self.interner.push_expr_location(expr_id, func.def.span, self.file); HirFunction::unchecked_from_expr(expr_id) @@ -923,6 +923,12 @@ impl<'a> Resolver<'a> { { self.push_err(ResolverError::NecessaryPub { ident: func.name_ident().clone() }); } + // '#[recursive]' attribute is only allowed for entry point functions + if !self.is_entry_point_function(func) && func.kind == FunctionKind::Recursive { + self.push_err(ResolverError::MisplacedRecursiveAttribute { + ident: func.name_ident().clone(), + }); + } if !self.distinct_allowed(func) && func.def.return_distinctness != Distinctness::DuplicationAllowed diff --git a/noir/compiler/noirc_frontend/src/hir_def/function.rs b/noir/compiler/noirc_frontend/src/hir_def/function.rs index 9fff301f5f7..78f44696b72 100644 --- a/noir/compiler/noirc_frontend/src/hir_def/function.rs +++ b/noir/compiler/noirc_frontend/src/hir_def/function.rs @@ -127,7 +127,7 @@ impl FuncMeta { pub fn can_ignore_return_type(&self) -> bool { match self.kind { FunctionKind::LowLevel | FunctionKind::Builtin | FunctionKind::Oracle => true, - FunctionKind::Normal => false, + FunctionKind::Normal | FunctionKind::Recursive => false, } } diff --git a/noir/compiler/noirc_frontend/src/lexer/token.rs b/noir/compiler/noirc_frontend/src/lexer/token.rs index 835a0baae3f..5d08ab03ad3 100644 --- a/noir/compiler/noirc_frontend/src/lexer/token.rs +++ b/noir/compiler/noirc_frontend/src/lexer/token.rs @@ -491,6 +491,7 @@ impl Attribute { Attribute::Function(FunctionAttribute::Oracle(name.to_string())) } ["test"] => Attribute::Function(FunctionAttribute::Test(TestScope::None)), + ["recursive"] => Attribute::Function(FunctionAttribute::Recursive), ["test", name] => { validate(name)?; let malformed_scope = @@ -541,6 +542,7 @@ pub enum FunctionAttribute { Builtin(String), Oracle(String), Test(TestScope), + Recursive, } impl FunctionAttribute { @@ -578,6 +580,7 @@ impl fmt::Display for FunctionAttribute { FunctionAttribute::Foreign(ref k) => write!(f, "#[foreign({k})]"), FunctionAttribute::Builtin(ref k) => write!(f, "#[builtin({k})]"), FunctionAttribute::Oracle(ref k) => write!(f, "#[oracle({k})]"), + FunctionAttribute::Recursive => write!(f, "#[recursive]"), } } } @@ -621,6 +624,7 @@ impl AsRef for FunctionAttribute { FunctionAttribute::Builtin(string) => string, FunctionAttribute::Oracle(string) => string, FunctionAttribute::Test { .. } => "", + FunctionAttribute::Recursive => "", } } } diff --git a/noir/compiler/noirc_frontend/src/monomorphization/ast.rs b/noir/compiler/noirc_frontend/src/monomorphization/ast.rs index 42a618e7d77..515d9710882 100644 --- a/noir/compiler/noirc_frontend/src/monomorphization/ast.rs +++ b/noir/compiler/noirc_frontend/src/monomorphization/ast.rs @@ -246,6 +246,8 @@ pub struct Program { pub return_distinctness: Distinctness, pub return_location: Option, pub return_visibility: Visibility, + /// Indicates to a backend whether a SNARK-friendly prover should be used. + pub recursive: bool, } impl Program { @@ -255,6 +257,7 @@ impl Program { return_distinctness: Distinctness, return_location: Option, return_visibility: Visibility, + recursive: bool, ) -> Program { Program { functions, @@ -262,6 +265,7 @@ impl Program { return_distinctness, return_location, return_visibility, + recursive, } } diff --git a/noir/compiler/noirc_frontend/src/monomorphization/mod.rs b/noir/compiler/noirc_frontend/src/monomorphization/mod.rs index 67b246a02ce..0334e01af5d 100644 --- a/noir/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -117,6 +117,7 @@ pub fn monomorphize(main: node_interner::FuncId, interner: &NodeInterner) -> Pro meta.return_distinctness, monomorphizer.return_location, meta.return_visibility, + meta.kind == FunctionKind::Recursive, ) } @@ -195,6 +196,9 @@ impl<'interner> Monomorphizer<'interner> { _ => unreachable!("Oracle function must have an oracle attribute"), } } + FunctionKind::Recursive => { + unreachable!("Only main can be specified as recursive, which should already be checked"); + } } } } diff --git a/noir/test_programs/execution_success/assert_statement_recursive/Nargo.toml b/noir/test_programs/execution_success/assert_statement_recursive/Nargo.toml new file mode 100644 index 00000000000..2a5b02cad00 --- /dev/null +++ b/noir/test_programs/execution_success/assert_statement_recursive/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "assert_statement_recursive" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] \ No newline at end of file diff --git a/noir/test_programs/execution_success/assert_statement_recursive/Prover.toml b/noir/test_programs/execution_success/assert_statement_recursive/Prover.toml new file mode 100644 index 00000000000..5d1dc99124f --- /dev/null +++ b/noir/test_programs/execution_success/assert_statement_recursive/Prover.toml @@ -0,0 +1,2 @@ +x = "3" +y = "3" diff --git a/noir/test_programs/execution_success/assert_statement_recursive/src/main.nr b/noir/test_programs/execution_success/assert_statement_recursive/src/main.nr new file mode 100644 index 00000000000..687a0d324ba --- /dev/null +++ b/noir/test_programs/execution_success/assert_statement_recursive/src/main.nr @@ -0,0 +1,11 @@ +// Tests a very simple program. +// +// The features being tested is assertion +// This is the same as the `assert_statement` test except we specify +// that the backend should use a prover which will construct proofs +// friendly to recursive verification in another SNARK. +#[recursive] +fn main(x: Field, y: pub Field) { + assert(x == y, "x and y are not equal"); + assert_eq(x, y, "x and y are not equal"); +} \ No newline at end of file diff --git a/noir/tooling/backend_interface/src/cli/prove.rs b/noir/tooling/backend_interface/src/cli/prove.rs index c12e516db57..c63d8afab54 100644 --- a/noir/tooling/backend_interface/src/cli/prove.rs +++ b/noir/tooling/backend_interface/src/cli/prove.rs @@ -13,7 +13,6 @@ use super::string_from_stderr; /// The proof will be written to the specified output file. pub(crate) struct ProveCommand { pub(crate) crs_path: PathBuf, - pub(crate) is_recursive: bool, pub(crate) bytecode_path: PathBuf, pub(crate) witness_path: PathBuf, } @@ -33,10 +32,6 @@ impl ProveCommand { .arg("-o") .arg("-"); - if self.is_recursive { - command.arg("-r"); - } - let output = command.output()?; if output.status.success() { Ok(output.stdout) @@ -61,7 +56,7 @@ fn prove_command() -> Result<(), BackendError> { std::fs::File::create(&witness_path).expect("file should be created"); let crs_path = backend.backend_directory(); - let prove_command = ProveCommand { crs_path, bytecode_path, witness_path, is_recursive: false }; + let prove_command = ProveCommand { crs_path, bytecode_path, witness_path }; let proof = prove_command.run(backend.binary_path())?; assert_eq!(proof, "proof".as_bytes()); diff --git a/noir/tooling/backend_interface/src/cli/verify.rs b/noir/tooling/backend_interface/src/cli/verify.rs index a31f476d84c..1a4ba50b7de 100644 --- a/noir/tooling/backend_interface/src/cli/verify.rs +++ b/noir/tooling/backend_interface/src/cli/verify.rs @@ -6,7 +6,6 @@ use crate::BackendError; /// to verify a proof pub(crate) struct VerifyCommand { pub(crate) crs_path: PathBuf, - pub(crate) is_recursive: bool, pub(crate) proof_path: PathBuf, pub(crate) vk_path: PathBuf, } @@ -24,10 +23,6 @@ impl VerifyCommand { .arg("-k") .arg(self.vk_path); - if self.is_recursive { - command.arg("-r"); - } - let output = command.output()?; // We currently do not distinguish between an invalid proof and an error inside the backend. @@ -64,18 +59,12 @@ fn verify_command() -> Result<(), BackendError> { write_vk_command.run(backend.binary_path())?; - let prove_command = ProveCommand { - crs_path: crs_path.clone(), - is_recursive: false, - bytecode_path, - witness_path, - }; + let prove_command = ProveCommand { crs_path: crs_path.clone(), bytecode_path, witness_path }; let proof = prove_command.run(backend.binary_path())?; write_to_file(&proof, &proof_path); - let verify_command = - VerifyCommand { crs_path, is_recursive: false, proof_path, vk_path: vk_path_output }; + let verify_command = VerifyCommand { crs_path, proof_path, vk_path: vk_path_output }; let verified = verify_command.run(backend.binary_path())?; assert!(verified); diff --git a/noir/tooling/backend_interface/src/proof_system.rs b/noir/tooling/backend_interface/src/proof_system.rs index 595cd7e2020..9369c91fa94 100644 --- a/noir/tooling/backend_interface/src/proof_system.rs +++ b/noir/tooling/backend_interface/src/proof_system.rs @@ -55,7 +55,6 @@ impl Backend { &self, circuit: &Circuit, witness_values: WitnessMap, - is_recursive: bool, ) -> Result, BackendError> { let binary_path = self.assert_binary_exists()?; self.assert_correct_version()?; @@ -76,13 +75,9 @@ impl Backend { write_to_file(&serialized_circuit, &bytecode_path); // Create proof and store it in the specified path - let proof_with_public_inputs = ProveCommand { - crs_path: self.crs_directory(), - is_recursive, - bytecode_path, - witness_path, - } - .run(binary_path)?; + let proof_with_public_inputs = + ProveCommand { crs_path: self.crs_directory(), bytecode_path, witness_path } + .run(binary_path)?; let proof = bb_abstraction_leaks::remove_public_inputs( circuit.public_inputs().0.len(), @@ -97,7 +92,6 @@ impl Backend { proof: &[u8], public_inputs: WitnessMap, circuit: &Circuit, - is_recursive: bool, ) -> Result { let binary_path = self.assert_binary_exists()?; self.assert_correct_version()?; @@ -127,8 +121,7 @@ impl Backend { .run(binary_path)?; // Verify the proof - VerifyCommand { crs_path: self.crs_directory(), is_recursive, proof_path, vk_path } - .run(binary_path) + VerifyCommand { crs_path: self.crs_directory(), proof_path, vk_path }.run(binary_path) } pub fn get_intermediate_proof_artifacts( diff --git a/noir/tooling/backend_interface/src/smart_contract.rs b/noir/tooling/backend_interface/src/smart_contract.rs index 2548079f8e3..524832c6308 100644 --- a/noir/tooling/backend_interface/src/smart_contract.rs +++ b/noir/tooling/backend_interface/src/smart_contract.rs @@ -56,6 +56,7 @@ mod tests { public_parameters: PublicInputs::default(), return_values: PublicInputs::default(), assert_messages: Default::default(), + recursive: false, }; let contract = get_mock_backend()?.eth_contract(&circuit)?; diff --git a/noir/tooling/nargo_cli/src/cli/prove_cmd.rs b/noir/tooling/nargo_cli/src/cli/prove_cmd.rs index 1d20e97af85..cc39b0535bc 100644 --- a/noir/tooling/nargo_cli/src/cli/prove_cmd.rs +++ b/noir/tooling/nargo_cli/src/cli/prove_cmd.rs @@ -138,12 +138,11 @@ pub(crate) fn prove_package( Format::Toml, )?; - let proof = backend.prove(&compiled_program.circuit, solved_witness, false)?; + let proof = backend.prove(&compiled_program.circuit, solved_witness)?; if check_proof { let public_inputs = public_abi.encode(&public_inputs, return_value)?; - let valid_proof = - backend.verify(&proof, public_inputs, &compiled_program.circuit, false)?; + let valid_proof = backend.verify(&proof, public_inputs, &compiled_program.circuit)?; if !valid_proof { return Err(CliError::InvalidProof("".into())); diff --git a/noir/tooling/nargo_cli/src/cli/verify_cmd.rs b/noir/tooling/nargo_cli/src/cli/verify_cmd.rs index ea4aaa051bb..66b88a22f2a 100644 --- a/noir/tooling/nargo_cli/src/cli/verify_cmd.rs +++ b/noir/tooling/nargo_cli/src/cli/verify_cmd.rs @@ -102,7 +102,7 @@ fn verify_package( let proof = load_hex_data(&proof_path)?; - let valid_proof = backend.verify(&proof, public_inputs, &compiled_program.circuit, false)?; + let valid_proof = backend.verify(&proof, public_inputs, &compiled_program.circuit)?; if valid_proof { Ok(()) diff --git a/noir/tooling/noir_js/src/program.ts b/noir/tooling/noir_js/src/program.ts index 809943727eb..8d80ec3a247 100644 --- a/noir/tooling/noir_js/src/program.ts +++ b/noir/tooling/noir_js/src/program.ts @@ -67,13 +67,13 @@ export class Noir { * * @example * ```typescript - * async generateFinalProof(input) + * async generateProof(input) * ``` * */ - async generateFinalProof(inputs: InputMap, foreignCallHandler?: ForeignCallHandler): Promise { + async generateProof(inputs: InputMap, foreignCallHandler?: ForeignCallHandler): Promise { const { witness } = await this.execute(inputs, foreignCallHandler); - return this.getBackend().generateFinalProof(witness); + return this.getBackend().generateProof(witness); } /** @@ -84,11 +84,11 @@ export class Noir { * * @example * ```typescript - * async verifyFinalProof(proof) + * async verifyProof(proof) * ``` * */ - async verifyFinalProof(proofData: ProofData): Promise { - return this.getBackend().verifyFinalProof(proofData); + async verifyProof(proofData: ProofData): Promise { + return this.getBackend().verifyProof(proofData); } } diff --git a/noir/tooling/noir_js/test/node/e2e.test.ts b/noir/tooling/noir_js/test/node/e2e.test.ts index 33d64377b06..8921314e8ea 100644 --- a/noir/tooling/noir_js/test/node/e2e.test.ts +++ b/noir/tooling/noir_js/test/node/e2e.test.ts @@ -21,10 +21,10 @@ it('end-to-end proof creation and verification (outer)', async () => { // // Proof creation const prover = new Backend(assert_lt_program); - const proof = await prover.generateFinalProof(witness); + const proof = await prover.generateProof(witness); // Proof verification - const isValid = await prover.verifyFinalProof(proof); + const isValid = await prover.verifyProof(proof); expect(isValid).to.be.true; }); @@ -40,13 +40,14 @@ it('end-to-end proof creation and verification (outer) -- Program API', async () // Initialize program const program = new Noir(assert_lt_program, backend); // Generate proof - const proof = await program.generateFinalProof(inputs); + const proof = await program.generateProof(inputs); // Proof verification - const isValid = await program.verifyFinalProof(proof); + const isValid = await program.verifyProof(proof); expect(isValid).to.be.true; }); +// TODO: maybe switch to using assert_statement_recursive here to test both options it('end-to-end proof creation and verification (inner)', async () => { // Noir.Js part const inputs = { @@ -62,10 +63,10 @@ it('end-to-end proof creation and verification (inner)', async () => { // // Proof creation const prover = new Backend(assert_lt_program); - const proof = await prover.generateIntermediateProof(witness); + const proof = await prover.generateProof(witness); // Proof verification - const isValid = await prover.verifyIntermediateProof(proof); + const isValid = await prover.verifyProof(proof); expect(isValid).to.be.true; }); @@ -83,10 +84,10 @@ it('end-to-end proving and verification with different instances', async () => { // bb.js part const prover = new Backend(assert_lt_program); - const proof = await prover.generateFinalProof(witness); + const proof = await prover.generateProof(witness); const verifier = new Backend(assert_lt_program); - const proof_is_valid = await verifier.verifyFinalProof(proof); + const proof_is_valid = await verifier.verifyProof(proof); expect(proof_is_valid).to.be.true; }); @@ -115,14 +116,14 @@ it('[BUG] -- bb.js null function or function signature mismatch (outer-inner) ', const prover = new Backend(assert_lt_program); // Create a proof using both proving systems, the majority of the time // one would only use outer proofs. - const proofOuter = await prover.generateFinalProof(witness); - const _proofInner = await prover.generateIntermediateProof(witness); + const proofOuter = await prover.generateProof(witness); + const _proofInner = await prover.generateProof(witness); // Proof verification // - const isValidOuter = await prover.verifyFinalProof(proofOuter); + const isValidOuter = await prover.verifyProof(proofOuter); expect(isValidOuter).to.be.true; // We can also try verifying an inner proof and it will fail. - const isValidInner = await prover.verifyIntermediateProof(_proofInner); + const isValidInner = await prover.verifyProof(_proofInner); expect(isValidInner).to.be.true; }); diff --git a/noir/tooling/noir_js_backend_barretenberg/src/index.ts b/noir/tooling/noir_js_backend_barretenberg/src/index.ts index 61094a3451f..4d5b6389404 100644 --- a/noir/tooling/noir_js_backend_barretenberg/src/index.ts +++ b/noir/tooling/noir_js_backend_barretenberg/src/index.ts @@ -47,47 +47,15 @@ export class BarretenbergBackend implements Backend { } } - /** - * Generate a final proof. This is the proof for the circuit which will verify - * intermediate proofs and or can be seen as the proof created for regular circuits. - */ - async generateFinalProof(decompressedWitness: Uint8Array): Promise { - // The settings for this proof are the same as the settings for a "normal" proof - // i.e. one that is not in the recursive setting. - const makeEasyToVerifyInCircuit = false; - return this.generateProof(decompressedWitness, makeEasyToVerifyInCircuit); - } - - /** - * Generates an intermediate proof. This is the proof that can be verified - * in another circuit. - * - * This is sometimes referred to as a recursive proof. - * We avoid this terminology as the only property of this proof - * that matters is the fact that it is easy to verify in another circuit. - * We _could_ choose to verify this proof outside of a circuit just as easily. - * - * @example - * ```typescript - * const intermediateProof = await backend.generateIntermediateProof(witness); - * ``` - */ - async generateIntermediateProof(witness: Uint8Array): Promise { - // We set `makeEasyToVerifyInCircuit` to true, which will tell the backend to - // generate the proof using components that will make the proof - // easier to verify in a circuit. - const makeEasyToVerifyInCircuit = true; - return this.generateProof(witness, makeEasyToVerifyInCircuit); - } - - /** @ignore */ - async generateProof(compressedWitness: Uint8Array, makeEasyToVerifyInCircuit: boolean): Promise { + /** @description Generates a proof */ + async generateProof(compressedWitness: Uint8Array): Promise { await this.instantiate(); + // TODO: Change once `@aztec/bb.js` version is updated to use methods without isRecursive flag const proofWithPublicInputs = await this.api.acirCreateProof( this.acirComposer, this.acirUncompressedBytecode, gunzip(compressedWitness), - makeEasyToVerifyInCircuit, + false, ); const splitIndex = proofWithPublicInputs.length - numBytesInProofWithoutPublicInputs; @@ -105,17 +73,17 @@ export class BarretenbergBackend implements Backend { * Instead of passing the proof and verification key as a byte array, we pass them * as fields which makes it cheaper to verify in a circuit. * - * The proof that is passed here will have been created using the `generateIntermediateProof` - * method. + * The proof that is passed here will have been created using a circuit + * that has the #[recursive] attribute on its `main` method. * * The number of public inputs denotes how many public inputs are in the inner proof. * * @example * ```typescript - * const artifacts = await backend.generateIntermediateProofArtifacts(proof, numOfPublicInputs); + * const artifacts = await backend.generateRecursiveProofArtifacts(proof, numOfPublicInputs); * ``` */ - async generateIntermediateProofArtifacts( + async generateRecursiveProofArtifacts( proofData: ProofData, numOfPublicInputs = 0, ): Promise<{ @@ -143,31 +111,13 @@ export class BarretenbergBackend implements Backend { }; } - async verifyFinalProof(proofData: ProofData): Promise { + /** @description Verifies a proof */ + async verifyProof(proofData: ProofData): Promise { const proof = reconstructProofWithPublicInputs(proofData); - const makeEasyToVerifyInCircuit = false; - const verified = await this.verifyProof(proof, makeEasyToVerifyInCircuit); - return verified; - } - - /** - * - * @example - * ```typescript - * const isValidIntermediate = await backend.verifyIntermediateProof(proof); - * ``` - */ - async verifyIntermediateProof(proofData: ProofData): Promise { - const proof = reconstructProofWithPublicInputs(proofData); - const makeEasyToVerifyInCircuit = true; - return this.verifyProof(proof, makeEasyToVerifyInCircuit); - } - - /** @ignore */ - async verifyProof(proof: Uint8Array, makeEasyToVerifyInCircuit: boolean): Promise { await this.instantiate(); await this.api.acirInitVerificationKey(this.acirComposer); - return await this.api.acirVerifyProof(this.acirComposer, proof, makeEasyToVerifyInCircuit); + // TODO: Change once `@aztec/bb.js` version is updated to use methods without isRecursive flag + return await this.api.acirVerifyProof(this.acirComposer, proof, false); } async destroy(): Promise { diff --git a/noir/tooling/noir_js_types/src/types.ts b/noir/tooling/noir_js_types/src/types.ts index ee4921bd606..84148c5d855 100644 --- a/noir/tooling/noir_js_types/src/types.ts +++ b/noir/tooling/noir_js_types/src/types.ts @@ -4,18 +4,14 @@ export { Abi, WitnessMap } from '@noir-lang/noirc_abi'; export interface Backend { /** - * @description Generates a final proof (not meant to be verified in another circuit) */ - generateFinalProof(decompressedWitness: Uint8Array): Promise; - - /** - * @description Generates an intermediate proof (meant to be verified in another circuit) */ - generateIntermediateProof(decompressedWitness: Uint8Array): Promise; + * @description Generates a proof */ + generateProof(decompressedWitness: Uint8Array): Promise; /** * * @description Retrieves the artifacts from a proof in the Field format */ - generateIntermediateProofArtifacts( + generateRecursiveProofArtifacts( proofData: ProofData, numOfPublicInputs: number, ): Promise<{ @@ -28,11 +24,8 @@ export interface Backend { }>; /** - * @description Verifies a final proof */ - verifyFinalProof(proofData: ProofData): Promise; - - /** @description Verifies an intermediate proof */ - verifyIntermediateProof(proofData: ProofData): Promise; + * @description Verifies a proof */ + verifyProof(proofData: ProofData): Promise; /** * @description Destroys the backend */ diff --git a/yarn-project/acir-simulator/src/acvm/deserialize.ts b/yarn-project/acir-simulator/src/acvm/deserialize.ts index a49f060e2e7..57a9e437fc5 100644 --- a/yarn-project/acir-simulator/src/acvm/deserialize.ts +++ b/yarn-project/acir-simulator/src/acvm/deserialize.ts @@ -1,31 +1,4 @@ -import { - CallContext, - ContractDeploymentData, - ContractStorageRead, - ContractStorageUpdateRequest, - HEADER_LENGTH, - Header, - MAX_NEW_COMMITMENTS_PER_CALL, - MAX_NEW_L2_TO_L1_MSGS_PER_CALL, - MAX_NEW_NULLIFIERS_PER_CALL, - MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL, - MAX_PRIVATE_CALL_STACK_LENGTH_PER_CALL, - MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, - MAX_PUBLIC_DATA_READS_PER_CALL, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, - MAX_READ_REQUESTS_PER_CALL, - NUM_FIELDS_PER_SHA256, - NullifierKeyValidationRequest, - PrivateCircuitPublicInputs, - PublicCircuitPublicInputs, - RETURN_VALUES_LENGTH, - SideEffect, - SideEffectLinkedToNoteHash, -} from '@aztec/circuits.js'; -import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { EthAddress } from '@aztec/foundation/eth-address'; -import { Fr, Point } from '@aztec/foundation/fields'; -import { FieldReader, Tuple } from '@aztec/foundation/serialize'; +import { Fr } from '@aztec/foundation/fields'; import { getReturnWitness } from '@noir-lang/acvm_js'; @@ -44,6 +17,7 @@ export function fromACVMField(field: ACVMField): Fr { * Converts a field to a number. * @param fr - The field to convert. * @returns The number. + * TODO(#4102): Nuke this once block number is big int. */ export function frToNumber(fr: Fr): number { return Number(fr.value); @@ -60,142 +34,3 @@ export function extractReturnWitness(acir: Buffer, partialWitness: ACVMWitness): const sortedKeys = [...returnWitness.keys()].sort((a, b) => a - b); return sortedKeys.map(key => returnWitness.get(key)!).map(fromACVMField); } - -/** - * Create a reader for the public inputs of the ACVM generated partial witness. - */ -function createPublicInputsReader(witness: ACVMWitness, acir: Buffer) { - const fields = extractReturnWitness(acir, witness); - return new FieldReader(fields); -} - -/** - * Extracts the public inputs from the ACVM generated partial witness. - * @param partialWitness - The partial witness. - * @param acir - The ACIR bytecode. - * @returns The public inputs. - */ -export function extractPrivateCircuitPublicInputs( - partialWitness: ACVMWitness, - acir: Buffer, -): PrivateCircuitPublicInputs { - const witnessReader = createPublicInputsReader(partialWitness, acir); - - const callContext = witnessReader.readObject(CallContext); - const argsHash = witnessReader.readField(); - const returnValues = witnessReader.readFieldArray(RETURN_VALUES_LENGTH); - const readRequests = witnessReader.readArray(MAX_READ_REQUESTS_PER_CALL, SideEffect); - const nullifierKeyValidationRequests = witnessReader.readArray( - MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL, - NullifierKeyValidationRequest, - ); - const newCommitments = witnessReader.readArray(MAX_NEW_COMMITMENTS_PER_CALL, SideEffect); - const newNullifiers = witnessReader.readArray(MAX_NEW_NULLIFIERS_PER_CALL, SideEffectLinkedToNoteHash); - const privateCallStack = witnessReader.readFieldArray(MAX_PRIVATE_CALL_STACK_LENGTH_PER_CALL); - const publicCallStack = witnessReader.readFieldArray(MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL); - const newL2ToL1Msgs = witnessReader.readFieldArray(MAX_NEW_L2_TO_L1_MSGS_PER_CALL); - const endSideEffectCounter = witnessReader.readField(); - - const encryptedLogsHash = witnessReader.readFieldArray(NUM_FIELDS_PER_SHA256); - const unencryptedLogsHash = witnessReader.readFieldArray(NUM_FIELDS_PER_SHA256); - const encryptedLogPreimagesLength = witnessReader.readField(); - const unencryptedLogPreimagesLength = witnessReader.readField(); - - const header = Header.fromFieldArray(witnessReader.readFieldArray(HEADER_LENGTH)); - - const contractDeploymentData = new ContractDeploymentData( - new Point(witnessReader.readField(), witnessReader.readField()), - witnessReader.readField(), - witnessReader.readField(), - witnessReader.readField(), - EthAddress.fromField(witnessReader.readField()), - ); - - const chainId = witnessReader.readField(); - const version = witnessReader.readField(); - - return new PrivateCircuitPublicInputs( - callContext, - argsHash, - returnValues, - readRequests, - nullifierKeyValidationRequests, - newCommitments, - newNullifiers, - privateCallStack, - publicCallStack, - newL2ToL1Msgs, - endSideEffectCounter, - encryptedLogsHash, - unencryptedLogsHash, - encryptedLogPreimagesLength, - unencryptedLogPreimagesLength, - header, - contractDeploymentData, - chainId, - version, - ); -} - -/** - * Extracts the public circuit public inputs from the ACVM generated partial witness. - * @param partialWitness - The partial witness. - * @param acir - The ACIR bytecode. - * @returns The public inputs. - */ -export function extractPublicCircuitPublicInputs(partialWitness: ACVMWitness, acir: Buffer): PublicCircuitPublicInputs { - const witnessReader = createPublicInputsReader(partialWitness, acir); - - const callContext = witnessReader.readObject(CallContext); - - const argsHash = witnessReader.readField(); - const returnValues = witnessReader.readFieldArray(RETURN_VALUES_LENGTH); - - const contractStorageUpdateRequests = new Array(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL).fill( - ContractStorageUpdateRequest.empty(), - ); - for (let i = 0; i < MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL; i++) { - const request = new ContractStorageUpdateRequest( - witnessReader.readField(), - witnessReader.readField(), - witnessReader.readField(), - ); - contractStorageUpdateRequests[i] = request; - } - const contractStorageReads = new Array(MAX_PUBLIC_DATA_READS_PER_CALL).fill(ContractStorageRead.empty()); - for (let i = 0; i < MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL; i++) { - const request = new ContractStorageRead(witnessReader.readField(), witnessReader.readField()); - contractStorageReads[i] = request; - } - - const publicCallStack = witnessReader.readFieldArray(MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL); - const newCommitments = witnessReader.readArray(MAX_NEW_COMMITMENTS_PER_CALL, SideEffect); - const newNullifiers = witnessReader.readArray(MAX_NEW_NULLIFIERS_PER_CALL, SideEffectLinkedToNoteHash); - const newL2ToL1Msgs = witnessReader.readFieldArray(MAX_NEW_L2_TO_L1_MSGS_PER_CALL); - - const unencryptedLogsHash = witnessReader.readFieldArray(NUM_FIELDS_PER_SHA256); - const unencryptedLogPreimagesLength = witnessReader.readField(); - - const header = Header.fromFieldArray(witnessReader.readFieldArray(HEADER_LENGTH)); - - const proverAddress = AztecAddress.fromField(witnessReader.readField()); - - return new PublicCircuitPublicInputs( - callContext, - argsHash, - returnValues, - contractStorageUpdateRequests as Tuple< - ContractStorageUpdateRequest, - typeof MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL - >, - contractStorageReads as Tuple, - publicCallStack, - newCommitments, - newNullifiers, - newL2ToL1Msgs, - unencryptedLogsHash, - unencryptedLogPreimagesLength, - header, - proverAddress, - ); -} diff --git a/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts b/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts index 22d3d9dd04c..984d16c7992 100644 --- a/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts +++ b/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts @@ -8,13 +8,7 @@ import { createDebugLogger } from '@aztec/foundation/log'; import { ACVMField } from '../acvm_types.js'; import { frToNumber, fromACVMField } from '../deserialize.js'; -import { - toACVMField, - toACVMHeader, - toAcvmCallPrivateStackItem, - toAcvmEnqueuePublicFunctionResult, - toAcvmL1ToL2MessageLoadOracleInputs, -} from '../serialize.js'; +import { toACVMField, toAcvmEnqueuePublicFunctionResult } from '../serialize.js'; import { acvmFieldMessageToString, oracleDebugCallToFormattedStr } from './debug.js'; import { TypedOracle } from './typed_oracle.js'; @@ -132,7 +126,7 @@ export class Oracle { if (!header) { throw new Error(`Block header not found for block ${parsedBlockNumber}.`); } - return toACVMHeader(header); + return header.toFields().map(toACVMField); } async getAuthWitness([messageHash]: ACVMField[]): Promise { @@ -226,8 +220,8 @@ export class Oracle { } async getL1ToL2Message([msgKey]: ACVMField[]): Promise { - const { ...message } = await this.typedOracle.getL1ToL2Message(fromACVMField(msgKey)); - return toAcvmL1ToL2MessageLoadOracleInputs(message); + const message = await this.typedOracle.getL1ToL2Message(fromACVMField(msgKey)); + return message.toFields().map(toACVMField); } async getPortalContractAddress([aztecAddress]: ACVMField[]): Promise { @@ -297,7 +291,7 @@ export class Oracle { fromACVMField(argsHash), frToNumber(fromACVMField(sideffectCounter)), ); - return toAcvmCallPrivateStackItem(callStackItem); + return callStackItem.toFields().map(toACVMField); } async callPublicFunction( diff --git a/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts b/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts index ffa9ac1fbfc..e19424a24ca 100644 --- a/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts +++ b/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts @@ -1,14 +1,22 @@ import { CompleteAddress, + L1ToL2Message, MerkleTreeId, Note, NoteStatus, NullifierMembershipWitness, PublicDataWitness, PublicKey, + SiblingPath, UnencryptedL2Log, } from '@aztec/circuit-types'; -import { GrumpkinPrivateKey, Header, PrivateCallStackItem, PublicCallRequest } from '@aztec/circuits.js'; +import { + GrumpkinPrivateKey, + Header, + L1_TO_L2_MSG_TREE_HEIGHT, + PrivateCallStackItem, + PublicCallRequest, +} from '@aztec/circuits.js'; import { FunctionSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; @@ -48,23 +56,19 @@ export interface NoteData { index?: bigint; } -/** - * The data for L1 to L2 Messages provided by other data sources. - */ -export interface MessageLoadOracleInputs { - /** - * An collapsed array of fields containing all of the l1 to l2 message components. - * `l1ToL2Message.toFieldArray()` -\> [sender, chainId, recipient, version, content, secretHash, deadline, fee] - */ - message: Fr[]; - /** - * The path in the merkle tree to the message. - */ - siblingPath: Fr[]; - /** - * The index of the message commitment in the merkle tree. - */ - index: bigint; +export class MessageLoadOracleInputs { + constructor( + /** The message. */ + public message: L1ToL2Message, + /** The index of the message commitment in the merkle tree. */ + public index: bigint, + /** The path in the merkle tree to the message. */ + public siblingPath: SiblingPath, + ) {} + + toFields(): Fr[] { + return [...this.message.toFieldArray(), new Fr(this.index), ...this.siblingPath.toFieldArray()]; + } } /** @@ -155,7 +159,7 @@ export abstract class TypedOracle { throw new Error('Not available.'); } - getL1ToL2Message(_msgKey: Fr): Promise { + getL1ToL2Message(_msgKey: Fr): Promise> { throw new Error('Not available.'); } diff --git a/yarn-project/acir-simulator/src/acvm/serialize.ts b/yarn-project/acir-simulator/src/acvm/serialize.ts index c2c7440d8a9..28ca4b3e27f 100644 --- a/yarn-project/acir-simulator/src/acvm/serialize.ts +++ b/yarn-project/acir-simulator/src/acvm/serialize.ts @@ -1,19 +1,9 @@ -import { - CallContext, - ContractDeploymentData, - FunctionData, - GlobalVariables, - Header, - PrivateCallStackItem, - PrivateCircuitPublicInputs, - PublicCallRequest, -} from '@aztec/circuits.js'; +import { PublicCallRequest } from '@aztec/circuits.js'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; import { ACVMField } from './acvm_types.js'; -import { MessageLoadOracleInputs } from './oracle/typed_oracle.js'; /** * Adapts the buffer to the field size. @@ -53,125 +43,6 @@ export function toACVMField( // Utilities to write TS classes to ACVM Field arrays // In the order that the ACVM expects them -/** - * Converts a function data to ACVM fields. - * @param functionData - The function data to convert. - * @returns The ACVM fields. - */ -export function toACVMFunctionData(functionData: FunctionData): ACVMField[] { - return [ - toACVMField(functionData.selector.toBuffer()), - toACVMField(functionData.isInternal), - toACVMField(functionData.isPrivate), - toACVMField(functionData.isConstructor), - ]; -} - -/** - * Converts a call context to ACVM fields. - * @param callContext - The call context to convert. - * @returns The ACVM fields. - */ -export function toACVMCallContext(callContext: CallContext): ACVMField[] { - return [ - toACVMField(callContext.msgSender), - toACVMField(callContext.storageContractAddress), - toACVMField(callContext.portalContractAddress), - toACVMField(callContext.functionSelector.toField()), - toACVMField(callContext.isDelegateCall), - toACVMField(callContext.isStaticCall), - toACVMField(callContext.isContractDeployment), - toACVMField(callContext.startSideEffectCounter), - ]; -} - -/** - * Converts a contract deployment data to ACVM fields. - * @param contractDeploymentData - The contract deployment data to convert. - * @returns The ACVM fields. - */ -export function toACVMContractDeploymentData(contractDeploymentData: ContractDeploymentData): ACVMField[] { - return [ - toACVMField(contractDeploymentData.publicKey.x), - toACVMField(contractDeploymentData.publicKey.y), - toACVMField(contractDeploymentData.initializationHash), - toACVMField(contractDeploymentData.contractClassId), - toACVMField(contractDeploymentData.contractAddressSalt), - toACVMField(contractDeploymentData.portalContractAddress), - ]; -} - -/** - * Converts a block header into ACVM fields. - * @param header - The block header object to convert. - * @returns The ACVM fields. - */ -export function toACVMHeader(header: Header): ACVMField[] { - return header.toFieldArray().map(toACVMField); -} - -/** - * Converts global variables into ACVM fields - * @param globalVariables - The global variables object to convert. - * @returns The ACVM fields - */ -export function toACVMGlobalVariables(globalVariables: GlobalVariables): ACVMField[] { - return [ - toACVMField(globalVariables.chainId), - toACVMField(globalVariables.version), - toACVMField(globalVariables.blockNumber), - toACVMField(globalVariables.timestamp), - ]; -} - -/** - * Converts the public inputs structure to ACVM fields. - * @param publicInputs - The public inputs to convert. - * @returns The ACVM fields. - */ -export function toACVMPublicInputs(publicInputs: PrivateCircuitPublicInputs): ACVMField[] { - return [ - ...toACVMCallContext(publicInputs.callContext), - toACVMField(publicInputs.argsHash), - - ...publicInputs.returnValues.map(toACVMField), - ...publicInputs.readRequests.flatMap(x => x.toFields()).map(toACVMField), - ...publicInputs.nullifierKeyValidationRequests.flatMap(x => x.toFields()).map(toACVMField), - ...publicInputs.newCommitments.flatMap(x => x.toFields()).map(toACVMField), - ...publicInputs.newNullifiers.flatMap(x => x.toFields()).map(toACVMField), - ...publicInputs.privateCallStackHashes.map(toACVMField), - ...publicInputs.publicCallStackHashes.map(toACVMField), - ...publicInputs.newL2ToL1Msgs.map(toACVMField), - toACVMField(publicInputs.endSideEffectCounter), - ...publicInputs.encryptedLogsHash.map(toACVMField), - ...publicInputs.unencryptedLogsHash.map(toACVMField), - - toACVMField(publicInputs.encryptedLogPreimagesLength), - toACVMField(publicInputs.unencryptedLogPreimagesLength), - - ...toACVMHeader(publicInputs.historicalHeader), - - ...toACVMContractDeploymentData(publicInputs.contractDeploymentData), - - toACVMField(publicInputs.chainId), - toACVMField(publicInputs.version), - ]; -} - -/** - * Converts a private call stack item to ACVM fields. - * @param item - The private call stack item to convert. - * @returns The ACVM fields. - */ -export function toAcvmCallPrivateStackItem(item: PrivateCallStackItem): ACVMField[] { - return [ - toACVMField(item.contractAddress), - ...toACVMFunctionData(item.functionData), - ...toACVMPublicInputs(item.publicInputs), - toACVMField(item.isExecutionRequest), - ]; -} - /** * Converts a public call stack item with the request for executing a public function to * a set of ACVM fields accepted by the enqueue_public_function_call_oracle Aztec.nr function. @@ -182,24 +53,11 @@ export function toAcvmCallPrivateStackItem(item: PrivateCallStackItem): ACVMFiel */ export function toAcvmEnqueuePublicFunctionResult(item: PublicCallRequest): ACVMField[] { return [ - toACVMField(item.contractAddress), - ...toACVMFunctionData(item.functionData), - ...toACVMCallContext(item.callContext), - toACVMField(item.getArgsHash()), - ]; -} - -/** - * Converts the result of loading messages to ACVM fields. - * @param messageLoadOracleInputs - The result of loading messages to convert. - * @returns The Message Oracle Fields. - */ -export function toAcvmL1ToL2MessageLoadOracleInputs(messageLoadOracleInputs: MessageLoadOracleInputs): ACVMField[] { - return [ - ...messageLoadOracleInputs.message.map(f => toACVMField(f)), - toACVMField(messageLoadOracleInputs.index), - ...messageLoadOracleInputs.siblingPath.map(f => toACVMField(f)), - ]; + item.contractAddress.toField(), + ...item.functionData.toFields(), + ...item.callContext.toFields(), + item.getArgsHash(), + ].map(toACVMField); } /** diff --git a/yarn-project/acir-simulator/src/avm/avm_context.ts b/yarn-project/acir-simulator/src/avm/avm_context.ts index 0fcb86cfde6..9816fda32d8 100644 --- a/yarn-project/acir-simulator/src/avm/avm_context.ts +++ b/yarn-project/acir-simulator/src/avm/avm_context.ts @@ -6,8 +6,10 @@ import { AvmMachineState } from './avm_machine_state.js'; import { AvmMessageCallResult } from './avm_message_call_result.js'; import { AvmInterpreterError, executeAvm } from './interpreter/index.js'; import { AvmJournal } from './journal/journal.js'; -import { decodeBytecode } from './opcodes/decode_bytecode.js'; -import { Instruction } from './opcodes/index.js'; +import { Instruction } from './opcodes/instruction.js'; +import { decodeFromBytecode } from './serialization/bytecode_serialization.js'; + +// FIXME: dependency cycle. /** * Avm Executor manages the execution of the AVM @@ -47,7 +49,7 @@ export class AvmContext { throw new NoBytecodeFoundInterpreterError(this.executionEnvironment.address); } - const instructions: Instruction[] = decodeBytecode(bytecode); + const instructions: Instruction[] = decodeFromBytecode(bytecode); const machineState = new AvmMachineState(this.executionEnvironment); return executeAvm(machineState, this.journal, instructions); diff --git a/yarn-project/acir-simulator/src/avm/index.test.ts b/yarn-project/acir-simulator/src/avm/index.test.ts index f085bc0bc00..9826a89d8e5 100644 --- a/yarn-project/acir-simulator/src/avm/index.test.ts +++ b/yarn-project/acir-simulator/src/avm/index.test.ts @@ -4,12 +4,12 @@ import { AvmTestContractArtifact } from '@aztec/noir-contracts'; import { mock } from 'jest-mock-extended'; import { AvmMachineState } from './avm_machine_state.js'; +import { TypeTag } from './avm_memory_types.js'; import { initExecutionEnvironment } from './fixtures/index.js'; import { executeAvm } from './interpreter/interpreter.js'; import { AvmJournal } from './journal/journal.js'; -import { decodeBytecode } from './opcodes/decode_bytecode.js'; -import { encodeToBytecode } from './opcodes/encode_to_bytecode.js'; -import { Opcode } from './opcodes/opcodes.js'; +import { Add, CalldataCopy, Return } from './opcodes/index.js'; +import { decodeFromBytecode, encodeToBytecode } from './serialization/bytecode_serialization.js'; describe('avm', () => { it('Should execute bytecode that performs basic addition', async () => { @@ -17,17 +17,14 @@ describe('avm', () => { const journal = mock(); // Construct bytecode - const calldataCopyArgs = [0, 2, 0]; - const addArgs = [0, 1, 2]; - const returnArgs = [2, 1]; - - const calldataCopyBytecode = encodeToBytecode(Opcode.CALLDATACOPY, calldataCopyArgs); - const addBytecode = encodeToBytecode(Opcode.ADD, addArgs); - const returnBytecode = encodeToBytecode(Opcode.RETURN, returnArgs); - const fullBytecode = Buffer.concat([calldataCopyBytecode, addBytecode, returnBytecode]); + const bytecode = encodeToBytecode([ + new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ 0, /*copySize=*/ 2, /*dstOffset=*/ 0), + new Add(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2), + new Return(/*indirect=*/ 0, /*returnOffset=*/ 2, /*copySize=*/ 1), + ]); // Decode bytecode into instructions - const instructions = decodeBytecode(fullBytecode); + const instructions = decodeFromBytecode(bytecode); // Execute instructions const context = new AvmMachineState(initExecutionEnvironment({ calldata })); @@ -41,7 +38,8 @@ describe('avm', () => { }); describe('testing transpiled Noir contracts', () => { - it('Should execute contract function that performs addition', async () => { + // TODO(https://github.com/AztecProtocol/aztec-packages/issues/4361): sync wire format w/transpiler. + it.skip('Should execute contract function that performs addition', async () => { const calldata: Fr[] = [new Fr(1), new Fr(2)]; const journal = mock(); @@ -49,7 +47,7 @@ describe('avm', () => { const addArtifact = AvmTestContractArtifact.functions.find(f => f.name === 'avm_addArgsReturn')!; // Decode bytecode into instructions - const instructions = decodeBytecode(Buffer.from(addArtifact.bytecode, 'base64')); + const instructions = decodeFromBytecode(Buffer.from(addArtifact.bytecode, 'base64')); // Execute instructions const context = new AvmMachineState(initExecutionEnvironment({ calldata })); diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts index e5055cdf903..c0111f067ac 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.test.ts @@ -3,6 +3,7 @@ import { Fr } from '@aztec/foundation/fields'; import { MockProxy, mock } from 'jest-mock-extended'; import { AvmMachineState } from '../avm_machine_state.js'; +import { TypeTag } from '../avm_memory_types.js'; import { initExecutionEnvironment } from '../fixtures/index.js'; import { AvmJournal } from '../journal/journal.js'; import { Add } from '../opcodes/arithmetic.js'; @@ -22,9 +23,9 @@ describe('interpreter', () => { const calldata: Fr[] = [new Fr(1), new Fr(2)]; const instructions: Instruction[] = [ - new CalldataCopy(/*cdOffset=*/ 0, /*copySize=*/ 2, /*dstOffset=*/ 0), - new Add(/*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2), - new Return(/*returnOffset=*/ 2, /*copySize=*/ 1), + new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ 0, /*copySize=*/ 2, /*dstOffset=*/ 0), + new Add(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2), + new Return(/*indirect=*/ 0, /*returnOffset=*/ 2, /*copySize=*/ 1), ]; const machineState = new AvmMachineState(initExecutionEnvironment({ calldata })); diff --git a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts index 86385078cac..fb23425f8b8 100644 --- a/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts +++ b/yarn-project/acir-simulator/src/avm/interpreter/interpreter.ts @@ -3,7 +3,7 @@ import { strict as assert } from 'assert'; import { AvmMachineState } from '../avm_machine_state.js'; import { AvmMessageCallResult } from '../avm_message_call_result.js'; import { AvmJournal } from '../journal/index.js'; -import { Instruction } from '../opcodes/index.js'; +import { Instruction, InstructionExecutionError } from '../opcodes/instruction.js'; /** * Run the avm @@ -36,14 +36,13 @@ export async function executeAvm( } return AvmMessageCallResult.success(returnData); - } catch (_e) { - if (!(_e instanceof AvmInterpreterError)) { - throw _e; + } catch (e) { + if (!(e instanceof AvmInterpreterError || e instanceof InstructionExecutionError)) { + throw e; } - const revertReason: AvmInterpreterError = _e; const revertData = machineState.getReturnData(); - return AvmMessageCallResult.revert(revertData, revertReason); + return AvmMessageCallResult.revert(revertData, /*revertReason=*/ e); } } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.test.ts index fd24b23eda4..0163b8bea21 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.test.ts @@ -18,56 +18,114 @@ describe('Accrued Substate', () => { machineState = new AvmMachineState(initExecutionEnvironment()); }); - it('Should append a new note hash correctly', async () => { - const value = new Field(69n); - machineState.memory.set(0, value); - - await new EmitNoteHash(0).execute(machineState, journal); - - const journalState = journal.flush(); - const expected = [value.toFr()]; - expect(journalState.newNoteHashes).toEqual(expected); + describe('EmitNoteHash', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + EmitNoteHash.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // dstOffset + ]); + const inst = new EmitNoteHash(/*indirect=*/ 0x01, /*dstOffset=*/ 0x12345678); + + expect(EmitNoteHash.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('Should append a new note hash correctly', async () => { + const value = new Field(69n); + machineState.memory.set(0, value); + + await new EmitNoteHash(/*indirect=*/ 0, /*offset=*/ 0).execute(machineState, journal); + + const journalState = journal.flush(); + const expected = [value.toFr()]; + expect(journalState.newNoteHashes).toEqual(expected); + }); }); - it('Should append a new nullifier correctly', async () => { - const value = new Field(69n); - machineState.memory.set(0, value); + describe('EmitNullifier', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + EmitNullifier.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // dstOffset + ]); + const inst = new EmitNullifier(/*indirect=*/ 0x01, /*dstOffset=*/ 0x12345678); + + expect(EmitNullifier.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('Should append a new nullifier correctly', async () => { + const value = new Field(69n); + machineState.memory.set(0, value); + + await new EmitNullifier(/*indirect=*/ 0, /*offset=*/ 0).execute(machineState, journal); + + const journalState = journal.flush(); + const expected = [value.toFr()]; + expect(journalState.newNullifiers).toEqual(expected); + }); + }); - await new EmitNullifier(0).execute(machineState, journal); + describe('EmitUnencryptedLog', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + EmitUnencryptedLog.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // offset + ...Buffer.from('a2345678', 'hex'), // length + ]); + const inst = new EmitUnencryptedLog(/*indirect=*/ 0x01, /*dstOffset=*/ 0x12345678, /*length=*/ 0xa2345678); - const journalState = journal.flush(); - const expected = [value.toFr()]; - expect(journalState.newNullifiers).toEqual(expected); - }); + expect(EmitUnencryptedLog.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); - it('Should append unencrypted logs correctly', async () => { - const startOffset = 0; + it('Should append unencrypted logs correctly', async () => { + const startOffset = 0; - const values = [new Field(69n), new Field(420n), new Field(Field.MODULUS - 1n)]; - machineState.memory.setSlice(0, values); + const values = [new Field(69n), new Field(420n), new Field(Field.MODULUS - 1n)]; + machineState.memory.setSlice(0, values); - const length = values.length; + const length = values.length; - await new EmitUnencryptedLog(startOffset, length).execute(machineState, journal); + await new EmitUnencryptedLog(/*indirect=*/ 0, /*offset=*/ startOffset, length).execute(machineState, journal); - const journalState = journal.flush(); - const expected = values.map(v => v.toFr()); - expect(journalState.newLogs).toEqual([expected]); + const journalState = journal.flush(); + const expected = values.map(v => v.toFr()); + expect(journalState.newLogs).toEqual([expected]); + }); }); - it('Should append l1 to l2 messages correctly', async () => { - const startOffset = 0; + describe('SendL2ToL1Message', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + SendL2ToL1Message.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // offset + ...Buffer.from('a2345678', 'hex'), // length + ]); + const inst = new SendL2ToL1Message(/*indirect=*/ 0x01, /*dstOffset=*/ 0x12345678, /*length=*/ 0xa2345678); + + expect(SendL2ToL1Message.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('Should append l1 to l2 messages correctly', async () => { + const startOffset = 0; - const values = [new Field(69n), new Field(420n), new Field(Field.MODULUS - 1n)]; - machineState.memory.setSlice(0, values); + const values = [new Field(69n), new Field(420n), new Field(Field.MODULUS - 1n)]; + machineState.memory.setSlice(0, values); - const length = values.length; + const length = values.length; - await new SendL2ToL1Message(startOffset, length).execute(machineState, journal); + await new SendL2ToL1Message(/*indirect=*/ 0, /*offset=*/ startOffset, length).execute(machineState, journal); - const journalState = journal.flush(); - const expected = values.map(v => v.toFr()); - expect(journalState.newLogs).toEqual([expected]); + const journalState = journal.flush(); + const expected = values.map(v => v.toFr()); + expect(journalState.newL1Messages).toEqual([expected]); + }); }); it('All substate instructions should fail within a static call', async () => { @@ -75,15 +133,14 @@ describe('Accrued Substate', () => { machineState = new AvmMachineState(executionEnvironment); const instructions = [ - new EmitNoteHash(0), - new EmitNullifier(0), - new EmitUnencryptedLog(0, 1), - new SendL2ToL1Message(0, 1), + new EmitNoteHash(/*indirect=*/ 0, /*offset=*/ 0), + new EmitNullifier(/*indirect=*/ 0, /*offset=*/ 0), + new EmitUnencryptedLog(/*indirect=*/ 0, /*offset=*/ 0, 1), + new SendL2ToL1Message(/*indirect=*/ 0, /*offset=*/ 0, 1), ]; for (const instruction of instructions) { - const inst = () => instruction.execute(machineState, journal); - await expect(inst()).rejects.toThrowError(StaticCallStorageAlterError); + await expect(instruction.execute(machineState, journal)).rejects.toThrow(StaticCallStorageAlterError); } }); }); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.ts b/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.ts index de54edaa0c7..1e85dde5d20 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/accrued_substate.ts @@ -1,13 +1,16 @@ import { AvmMachineState } from '../avm_machine_state.js'; import { AvmJournal } from '../journal/journal.js'; +import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; import { Instruction } from './instruction.js'; import { StaticCallStorageAlterError } from './storage.js'; export class EmitNoteHash extends Instruction { static type: string = 'EMITNOTEHASH'; - static numberOfOperands = 1; + static readonly opcode: Opcode = Opcode.EMITNOTEHASH; + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat = [OperandType.UINT8, OperandType.UINT8, OperandType.UINT32]; - constructor(private noteHashOffset: number) { + constructor(private indirect: number, private noteHashOffset: number) { super(); } @@ -25,9 +28,11 @@ export class EmitNoteHash extends Instruction { export class EmitNullifier extends Instruction { static type: string = 'EMITNULLIFIER'; - static numberOfOperands = 1; + static readonly opcode: Opcode = Opcode.EMITNULLIFIER; + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat = [OperandType.UINT8, OperandType.UINT8, OperandType.UINT32]; - constructor(private nullifierOffset: number) { + constructor(private indirect: number, private nullifierOffset: number) { super(); } @@ -45,9 +50,11 @@ export class EmitNullifier extends Instruction { export class EmitUnencryptedLog extends Instruction { static type: string = 'EMITUNENCRYPTEDLOG'; - static numberOfOperands = 2; + static readonly opcode: Opcode = Opcode.EMITUNENCRYPTEDLOG; + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat = [OperandType.UINT8, OperandType.UINT8, OperandType.UINT32, OperandType.UINT32]; - constructor(private logOffset: number, private logSize: number) { + constructor(private indirect: number, private logOffset: number, private logSize: number) { super(); } @@ -64,10 +71,12 @@ export class EmitUnencryptedLog extends Instruction { } export class SendL2ToL1Message extends Instruction { - static type: string = 'EMITUNENCRYPTEDLOG'; - static numberOfOperands = 2; + static type: string = 'SENDL2TOL1MSG'; + static readonly opcode: Opcode = Opcode.SENDL2TOL1MSG; + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat = [OperandType.UINT8, OperandType.UINT8, OperandType.UINT32, OperandType.UINT32]; - constructor(private msgOffset: number, private msgSize: number) { + constructor(private indirect: number, private msgOffset: number, private msgSize: number) { super(); } @@ -77,7 +86,7 @@ export class SendL2ToL1Message extends Instruction { } const msg = machineState.memory.getSlice(this.msgOffset, this.msgSize).map(f => f.toFr()); - journal.writeLog(msg); + journal.writeL1Message(msg); this.incrementPc(machineState); } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.test.ts index a9911f8d82d..f54db2d82a7 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.test.ts @@ -1,7 +1,7 @@ import { MockProxy, mock } from 'jest-mock-extended'; import { AvmMachineState } from '../avm_machine_state.js'; -import { Field } from '../avm_memory_types.js'; +import { Field, TypeTag } from '../avm_memory_types.js'; import { initExecutionEnvironment } from '../fixtures/index.js'; import { AvmJournal } from '../journal/journal.js'; import { Add, Div, Mul, Sub } from './arithmetic.js'; @@ -16,6 +16,27 @@ describe('Arithmetic Instructions', () => { }); describe('Add', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + Add.opcode, // opcode + 0x01, // indirect + TypeTag.FIELD, // inTag + ...Buffer.from('12345678', 'hex'), // aOffset + ...Buffer.from('23456789', 'hex'), // bOffset + ...Buffer.from('3456789a', 'hex'), // dstOffset + ]); + const inst = new Add( + /*indirect=*/ 0x01, + /*inTag=*/ TypeTag.FIELD, + /*aOffset=*/ 0x12345678, + /*bOffset=*/ 0x23456789, + /*dstOffset=*/ 0x3456789a, + ); + + expect(Add.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + it('Should add correctly over field elements', async () => { const a = new Field(1n); const b = new Field(2n); @@ -23,7 +44,13 @@ describe('Arithmetic Instructions', () => { machineState.memory.set(0, a); machineState.memory.set(1, b); - await new Add(0, 1, 2).execute(machineState, journal); + await new Add( + /*indirect=*/ 0, + /*inTag=*/ TypeTag.FIELD, + /*aOffset=*/ 0, + /*bOffset=*/ 1, + /*dstOffset=*/ 2, + ).execute(machineState, journal); const expected = new Field(3n); const actual = machineState.memory.get(2); @@ -37,7 +64,13 @@ describe('Arithmetic Instructions', () => { machineState.memory.set(0, a); machineState.memory.set(1, b); - await new Add(0, 1, 2).execute(machineState, journal); + await new Add( + /*indirect=*/ 0, + /*inTag=*/ TypeTag.FIELD, + /*aOffset=*/ 0, + /*bOffset=*/ 1, + /*dstOffset=*/ 2, + ).execute(machineState, journal); const expected = new Field(0n); const actual = machineState.memory.get(2); @@ -46,6 +79,27 @@ describe('Arithmetic Instructions', () => { }); describe('Sub', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + Sub.opcode, // opcode + 0x01, // indirect + TypeTag.FIELD, // inTag + ...Buffer.from('12345678', 'hex'), // aOffset + ...Buffer.from('23456789', 'hex'), // bOffset + ...Buffer.from('3456789a', 'hex'), // dstOffset + ]); + const inst = new Sub( + /*indirect=*/ 0x01, + /*inTag=*/ TypeTag.FIELD, + /*aOffset=*/ 0x12345678, + /*bOffset=*/ 0x23456789, + /*dstOffset=*/ 0x3456789a, + ); + + expect(Sub.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + it('Should subtract correctly over field elements', async () => { const a = new Field(1n); const b = new Field(2n); @@ -53,7 +107,13 @@ describe('Arithmetic Instructions', () => { machineState.memory.set(0, a); machineState.memory.set(1, b); - await new Sub(0, 1, 2).execute(machineState, journal); + await new Sub( + /*indirect=*/ 0, + /*inTag=*/ TypeTag.FIELD, + /*aOffset=*/ 0, + /*bOffset=*/ 1, + /*dstOffset=*/ 2, + ).execute(machineState, journal); const expected = new Field(Field.MODULUS - 1n); const actual = machineState.memory.get(2); @@ -62,6 +122,27 @@ describe('Arithmetic Instructions', () => { }); describe('Mul', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + Mul.opcode, // opcode + 0x01, // indirect + TypeTag.FIELD, // inTag + ...Buffer.from('12345678', 'hex'), // aOffset + ...Buffer.from('23456789', 'hex'), // bOffset + ...Buffer.from('3456789a', 'hex'), // dstOffset + ]); + const inst = new Mul( + /*indirect=*/ 0x01, + /*inTag=*/ TypeTag.FIELD, + /*aOffset=*/ 0x12345678, + /*bOffset=*/ 0x23456789, + /*dstOffset=*/ 0x3456789a, + ); + + expect(Mul.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + it('Should multiply correctly over field elements', async () => { const a = new Field(2n); const b = new Field(3n); @@ -69,7 +150,13 @@ describe('Arithmetic Instructions', () => { machineState.memory.set(0, a); machineState.memory.set(1, b); - await new Mul(0, 1, 2).execute(machineState, journal); + await new Mul( + /*indirect=*/ 0, + /*inTag=*/ TypeTag.FIELD, + /*aOffset=*/ 0, + /*bOffset=*/ 1, + /*dstOffset=*/ 2, + ).execute(machineState, journal); const expected = new Field(6n); const actual = machineState.memory.get(2); @@ -83,7 +170,13 @@ describe('Arithmetic Instructions', () => { machineState.memory.set(0, a); machineState.memory.set(1, b); - await new Mul(0, 1, 2).execute(machineState, journal); + await new Mul( + /*indirect=*/ 0, + /*inTag=*/ TypeTag.FIELD, + /*aOffset=*/ 0, + /*bOffset=*/ 1, + /*dstOffset=*/ 2, + ).execute(machineState, journal); const expected = new Field(Field.MODULUS - 3n); const actual = machineState.memory.get(2); @@ -92,6 +185,27 @@ describe('Arithmetic Instructions', () => { }); describe('Div', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + Div.opcode, // opcode + 0x01, // indirect + TypeTag.FIELD, // inTag + ...Buffer.from('12345678', 'hex'), // aOffset + ...Buffer.from('23456789', 'hex'), // bOffset + ...Buffer.from('3456789a', 'hex'), // dstOffset + ]); + const inst = new Div( + /*indirect=*/ 0x01, + /*inTag=*/ TypeTag.FIELD, + /*aOffset=*/ 0x12345678, + /*bOffset=*/ 0x23456789, + /*dstOffset=*/ 0x3456789a, + ); + + expect(Div.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + it('Should perform field division', async () => { const a = new Field(2n); const b = new Field(3n); @@ -99,9 +213,14 @@ describe('Arithmetic Instructions', () => { machineState.memory.set(0, a); machineState.memory.set(1, b); - await new Div(0, 1, 2).execute(machineState, journal); + await new Div( + /*indirect=*/ 0, + /*inTag=*/ TypeTag.FIELD, + /*aOffset=*/ 0, + /*bOffset=*/ 1, + /*dstOffset=*/ 2, + ).execute(machineState, journal); - // Note const actual = machineState.memory.get(2); const recovered = actual.mul(b); expect(recovered).toEqual(a); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts index 0b1910f0dd7..71a571b45dd 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/arithmetic.ts @@ -1,13 +1,14 @@ import { AvmMachineState } from '../avm_machine_state.js'; import { AvmJournal } from '../journal/index.js'; -import { Instruction } from './instruction.js'; +import { Opcode } from '../serialization/instruction_serialization.js'; +import { ThreeOperandInstruction } from './instruction_impl.js'; -export class Add extends Instruction { - static type: string = 'ADD'; - static numberOfOperands = 3; +export class Add extends ThreeOperandInstruction { + static readonly type: string = 'ADD'; + static readonly opcode = Opcode.ADD; - constructor(private aOffset: number, private bOffset: number, private dstOffset: number) { - super(); + constructor(indirect: number, inTag: number, aOffset: number, bOffset: number, dstOffset: number) { + super(indirect, inTag, aOffset, bOffset, dstOffset); } async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { @@ -21,12 +22,12 @@ export class Add extends Instruction { } } -export class Sub extends Instruction { - static type: string = 'SUB'; - static numberOfOperands = 3; +export class Sub extends ThreeOperandInstruction { + static readonly type: string = 'SUB'; + static readonly opcode = Opcode.SUB; - constructor(private aOffset: number, private bOffset: number, private dstOffset: number) { - super(); + constructor(indirect: number, inTag: number, aOffset: number, bOffset: number, dstOffset: number) { + super(indirect, inTag, aOffset, bOffset, dstOffset); } async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { @@ -40,12 +41,12 @@ export class Sub extends Instruction { } } -export class Mul extends Instruction { +export class Mul extends ThreeOperandInstruction { static type: string = 'MUL'; - static numberOfOperands = 3; + static readonly opcode = Opcode.MUL; - constructor(private aOffset: number, private bOffset: number, private dstOffset: number) { - super(); + constructor(indirect: number, inTag: number, aOffset: number, bOffset: number, dstOffset: number) { + super(indirect, inTag, aOffset, bOffset, dstOffset); } async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { @@ -59,12 +60,12 @@ export class Mul extends Instruction { } } -export class Div extends Instruction { +export class Div extends ThreeOperandInstruction { static type: string = 'DIV'; - static numberOfOperands = 3; + static readonly opcode = Opcode.DIV; - constructor(private aOffset: number, private bOffset: number, private dstOffset: number) { - super(); + constructor(indirect: number, inTag: number, aOffset: number, bOffset: number, dstOffset: number) { + super(indirect, inTag, aOffset, bOffset, dstOffset); } async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { diff --git a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.test.ts index ae8ce802724..d41c34611e0 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.test.ts @@ -15,45 +15,153 @@ describe('Bitwise instructions', () => { journal = mock(); }); - it('Should AND correctly over integral types', async () => { - machineState.memory.set(0, new Uint32(0b11111110010011100100n)); - machineState.memory.set(1, new Uint32(0b11100100111001001111n)); + describe('AND', () => { + it('Should deserialize correctly', () => { + const buf = Buffer.from([ + And.opcode, // opcode + 0x01, // indirect + TypeTag.UINT64, // inTag + ...Buffer.from('12345678', 'hex'), // aOffset + ...Buffer.from('23456789', 'hex'), // bOffset + ...Buffer.from('3456789a', 'hex'), // dstOffset + ]); + const inst = new And( + /*indirect=*/ 0x01, + /*inTag=*/ TypeTag.UINT64, + /*aOffset=*/ 0x12345678, + /*bOffset=*/ 0x23456789, + /*dstOffset=*/ 0x3456789a, + ); + + expect(And.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('Should AND correctly over integral types', async () => { + machineState.memory.set(0, new Uint32(0b11111110010011100100n)); + machineState.memory.set(1, new Uint32(0b11100100111001001111n)); - await new And(TypeTag.UINT32, 0, 1, 2).execute(machineState, journal); + await new And( + /*indirect=*/ 0, + /*inTag=*/ TypeTag.UINT32, + /*aOffset=*/ 0, + /*bOffset=*/ 1, + /*dstOffset=*/ 2, + ).execute(machineState, journal); - const actual = machineState.memory.get(2); - expect(actual).toEqual(new Uint32(0b11100100010001000100n)); + const actual = machineState.memory.get(2); + expect(actual).toEqual(new Uint32(0b11100100010001000100n)); + }); }); - it('Should OR correctly over integral types', async () => { - const a = new Uint32(0b11111110010011100100n); - const b = new Uint32(0b11100100111001001111n); + describe('OR', () => { + it('Should deserialize correctly', () => { + const buf = Buffer.from([ + Or.opcode, // opcode + 0x01, // indirect + TypeTag.UINT64, // inTag + ...Buffer.from('12345678', 'hex'), // aOffset + ...Buffer.from('23456789', 'hex'), // bOffset + ...Buffer.from('3456789a', 'hex'), // dstOffset + ]); + const inst = new Or( + /*indirect=*/ 0x01, + /*inTag=*/ TypeTag.UINT64, + /*aOffset=*/ 0x12345678, + /*bOffset=*/ 0x23456789, + /*dstOffset=*/ 0x3456789a, + ); + + expect(Or.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + it('Should OR correctly over integral types', async () => { + const a = new Uint32(0b11111110010011100100n); + const b = new Uint32(0b11100100111001001111n); - await new Or(TypeTag.UINT32, 0, 1, 2).execute(machineState, journal); + machineState.memory.set(0, a); + machineState.memory.set(1, b); - const expected = new Uint32(0b11111110111011101111n); - const actual = machineState.memory.get(2); - expect(actual).toEqual(expected); + await new Or( + /*indirect=*/ 0, + /*inTag=*/ TypeTag.UINT32, + /*aOffset=*/ 0, + /*bOffset=*/ 1, + /*dstOffset=*/ 2, + ).execute(machineState, journal); + + const expected = new Uint32(0b11111110111011101111n); + const actual = machineState.memory.get(2); + expect(actual).toEqual(expected); + }); }); - it('Should XOR correctly over integral types', async () => { - const a = new Uint32(0b11111110010011100100n); - const b = new Uint32(0b11100100111001001111n); + describe('XOR', () => { + it('Should deserialize correctly', () => { + const buf = Buffer.from([ + Xor.opcode, // opcode + 0x01, // indirect + TypeTag.UINT64, // inTag + ...Buffer.from('12345678', 'hex'), // aOffset + ...Buffer.from('23456789', 'hex'), // bOffset + ...Buffer.from('3456789a', 'hex'), // dstOffset + ]); + const inst = new Xor( + /*indirect=*/ 0x01, + /*inTag=*/ TypeTag.UINT64, + /*aOffset=*/ 0x12345678, + /*bOffset=*/ 0x23456789, + /*dstOffset=*/ 0x3456789a, + ); + + expect(Xor.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + it('Should XOR correctly over integral types', async () => { + const a = new Uint32(0b11111110010011100100n); + const b = new Uint32(0b11100100111001001111n); - await new Xor(TypeTag.UINT32, 0, 1, 2).execute(machineState, journal); + machineState.memory.set(0, a); + machineState.memory.set(1, b); + + await new Xor( + /*indirect=*/ 0, + /*inTag=*/ TypeTag.UINT32, + /*aOffset=*/ 0, + /*bOffset=*/ 1, + /*dstOffset=*/ 2, + ).execute(machineState, journal); - const expected = new Uint32(0b00011010101010101011n); - const actual = machineState.memory.get(2); - expect(actual).toEqual(expected); + const expected = new Uint32(0b00011010101010101011n); + const actual = machineState.memory.get(2); + expect(actual).toEqual(expected); + }); }); describe('SHR', () => { + it('Should deserialize correctly', () => { + const buf = Buffer.from([ + Shr.opcode, // opcode + 0x01, // indirect + TypeTag.UINT64, // inTag + ...Buffer.from('12345678', 'hex'), // aOffset + ...Buffer.from('23456789', 'hex'), // bOffset + ...Buffer.from('3456789a', 'hex'), // dstOffset + ]); + const inst = new Shr( + /*indirect=*/ 0x01, + /*inTag=*/ TypeTag.UINT64, + /*aOffset=*/ 0x12345678, + /*bOffset=*/ 0x23456789, + /*dstOffset=*/ 0x3456789a, + ); + + expect(Shr.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + it('Should shift correctly 0 positions over integral types', async () => { const a = new Uint32(0b11111110010011100100n); const b = new Uint32(0n); @@ -61,7 +169,13 @@ describe('Bitwise instructions', () => { machineState.memory.set(0, a); machineState.memory.set(1, b); - await new Shr(TypeTag.UINT32, 0, 1, 2).execute(machineState, journal); + await new Shr( + /*indirect=*/ 0, + /*inTag=*/ TypeTag.UINT32, + /*aOffset=*/ 0, + /*bOffset=*/ 1, + /*dstOffset=*/ 2, + ).execute(machineState, journal); const expected = a; const actual = machineState.memory.get(2); @@ -75,7 +189,13 @@ describe('Bitwise instructions', () => { machineState.memory.set(0, a); machineState.memory.set(1, b); - await new Shr(TypeTag.UINT32, 0, 1, 2).execute(machineState, journal); + await new Shr( + /*indirect=*/ 0, + /*inTag=*/ TypeTag.UINT32, + /*aOffset=*/ 0, + /*bOffset=*/ 1, + /*dstOffset=*/ 2, + ).execute(machineState, journal); const expected = new Uint32(0b00111111100100111001n); const actual = machineState.memory.get(2); @@ -89,7 +209,13 @@ describe('Bitwise instructions', () => { machineState.memory.set(0, a); machineState.memory.set(1, b); - await new Shr(TypeTag.UINT32, 0, 1, 2).execute(machineState, journal); + await new Shr( + /*indirect=*/ 0, + /*inTag=*/ TypeTag.UINT32, + /*aOffset=*/ 0, + /*bOffset=*/ 1, + /*dstOffset=*/ 2, + ).execute(machineState, journal); const expected = new Uint32(0b01n); const actual = machineState.memory.get(2); @@ -98,6 +224,27 @@ describe('Bitwise instructions', () => { }); describe('SHL', () => { + it('Should deserialize correctly', () => { + const buf = Buffer.from([ + Shl.opcode, // opcode + 0x01, // indirect + TypeTag.UINT64, // inTag + ...Buffer.from('12345678', 'hex'), // aOffset + ...Buffer.from('23456789', 'hex'), // bOffset + ...Buffer.from('3456789a', 'hex'), // dstOffset + ]); + const inst = new Shl( + /*indirect=*/ 0x01, + /*inTag=*/ TypeTag.UINT64, + /*aOffset=*/ 0x12345678, + /*bOffset=*/ 0x23456789, + /*dstOffset=*/ 0x3456789a, + ); + + expect(Shl.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + it('Should shift correctly 0 positions over integral types', async () => { const a = new Uint32(0b11111110010011100100n); const b = new Uint32(0n); @@ -105,7 +252,13 @@ describe('Bitwise instructions', () => { machineState.memory.set(0, a); machineState.memory.set(1, b); - await new Shl(TypeTag.UINT32, 0, 1, 2).execute(machineState, journal); + await new Shl( + /*indirect=*/ 0, + /*inTag=*/ TypeTag.UINT32, + /*aOffset=*/ 0, + /*bOffset=*/ 1, + /*dstOffset=*/ 2, + ).execute(machineState, journal); const expected = a; const actual = machineState.memory.get(2); @@ -119,7 +272,13 @@ describe('Bitwise instructions', () => { machineState.memory.set(0, a); machineState.memory.set(1, b); - await new Shl(TypeTag.UINT32, 0, 1, 2).execute(machineState, journal); + await new Shl( + /*indirect=*/ 0, + /*inTag=*/ TypeTag.UINT32, + /*aOffset=*/ 0, + /*bOffset=*/ 1, + /*dstOffset=*/ 2, + ).execute(machineState, journal); const expected = new Uint32(0b1111111001001110010000n); const actual = machineState.memory.get(2); @@ -133,7 +292,13 @@ describe('Bitwise instructions', () => { machineState.memory.set(0, a); machineState.memory.set(1, b); - await new Shl(TypeTag.UINT16, 0, 1, 2).execute(machineState, journal); + await new Shl( + /*indirect=*/ 0, + /*inTag=*/ TypeTag.UINT16, + /*aOffset=*/ 0, + /*bOffset=*/ 1, + /*dstOffset=*/ 2, + ).execute(machineState, journal); const expected = new Uint16(0n); const actual = machineState.memory.get(2); @@ -147,7 +312,13 @@ describe('Bitwise instructions', () => { machineState.memory.set(0, a); machineState.memory.set(1, b); - await new Shl(TypeTag.UINT16, 0, 1, 2).execute(machineState, journal); + await new Shl( + /*indirect=*/ 0, + /*inTag=*/ TypeTag.UINT16, + /*aOffset=*/ 0, + /*bOffset=*/ 1, + /*dstOffset=*/ 2, + ).execute(machineState, journal); const expected = new Uint16(0b1001001110011100n); const actual = machineState.memory.get(2); @@ -155,15 +326,39 @@ describe('Bitwise instructions', () => { }); }); - it('Should NOT correctly over integral types', async () => { - const a = new Uint16(0b0110010011100100n); + describe('NOT', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + Not.opcode, // opcode + 0x01, // indirect + TypeTag.UINT64, // inTag + ...Buffer.from('12345678', 'hex'), // aOffset + ...Buffer.from('3456789a', 'hex'), // dstOffset + ]); + const inst = new Not( + /*indirect=*/ 0x01, + /*inTag=*/ TypeTag.UINT64, + /*aOffset=*/ 0x12345678, + /*dstOffset=*/ 0x3456789a, + ); + + expect(Not.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); - machineState.memory.set(0, a); + it('Should NOT correctly over integral types', async () => { + const a = new Uint16(0b0110010011100100n); - await new Not(TypeTag.UINT16, 0, 1).execute(machineState, journal); + machineState.memory.set(0, a); - const expected = new Uint16(0b1001101100011011n); // high bits! - const actual = machineState.memory.get(1); - expect(actual).toEqual(expected); + await new Not(/*indirect=*/ 0, /*inTag=*/ TypeTag.UINT16, /*aOffset=*/ 0, /*dstOffset=*/ 1).execute( + machineState, + journal, + ); + + const expected = new Uint16(0b1001101100011011n); // high bits! + const actual = machineState.memory.get(1); + expect(actual).toEqual(expected); + }); }); }); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts index e439a8bd447..f30be9b5053 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/bitwise.ts @@ -1,14 +1,16 @@ import { AvmMachineState } from '../avm_machine_state.js'; -import { IntegralValue, TypeTag } from '../avm_memory_types.js'; +import { IntegralValue } from '../avm_memory_types.js'; import { AvmJournal } from '../journal/index.js'; +import { Opcode } from '../serialization/instruction_serialization.js'; import { Instruction } from './instruction.js'; +import { ThreeOperandInstruction, TwoOperandInstruction } from './instruction_impl.js'; -export class And extends Instruction { - static type: string = 'AND'; - static numberOfOperands = 3; +export class And extends ThreeOperandInstruction { + static readonly type: string = 'AND'; + static readonly opcode = Opcode.AND; - constructor(private inTag: TypeTag, private aOffset: number, private bOffset: number, private dstOffset: number) { - super(); + constructor(indirect: number, inTag: number, aOffset: number, bOffset: number, dstOffset: number) { + super(indirect, inTag, aOffset, bOffset, dstOffset); } async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { @@ -24,12 +26,12 @@ export class And extends Instruction { } } -export class Or extends Instruction { - static type: string = 'OR'; - static numberOfOperands = 3; +export class Or extends ThreeOperandInstruction { + static readonly type: string = 'OR'; + static readonly opcode = Opcode.OR; - constructor(private inTag: TypeTag, private aOffset: number, private bOffset: number, private dstOffset: number) { - super(); + constructor(indirect: number, inTag: number, aOffset: number, bOffset: number, dstOffset: number) { + super(indirect, inTag, aOffset, bOffset, dstOffset); } async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { @@ -45,12 +47,12 @@ export class Or extends Instruction { } } -export class Xor extends Instruction { - static type: string = 'XOR'; - static numberOfOperands = 3; +export class Xor extends ThreeOperandInstruction { + static readonly type: string = 'XOR'; + static readonly opcode = Opcode.XOR; - constructor(private inTag: TypeTag, private aOffset: number, private bOffset: number, private dstOffset: number) { - super(); + constructor(indirect: number, inTag: number, aOffset: number, bOffset: number, dstOffset: number) { + super(indirect, inTag, aOffset, bOffset, dstOffset); } async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { @@ -66,12 +68,12 @@ export class Xor extends Instruction { } } -export class Not extends Instruction { - static type: string = 'NOT'; - static numberOfOperands = 2; +export class Not extends TwoOperandInstruction { + static readonly type: string = 'NOT'; + static readonly opcode = Opcode.NOT; - constructor(private inTag: TypeTag, private aOffset: number, private dstOffset: number) { - super(); + constructor(indirect: number, inTag: number, aOffset: number, dstOffset: number) { + super(indirect, inTag, aOffset, dstOffset); } async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { @@ -86,12 +88,12 @@ export class Not extends Instruction { } } -export class Shl extends Instruction { - static type: string = 'SHL'; - static numberOfOperands = 3; +export class Shl extends ThreeOperandInstruction { + static readonly type: string = 'SHL'; + static readonly opcode = Opcode.SHL; - constructor(private inTag: TypeTag, private aOffset: number, private bOffset: number, private dstOffset: number) { - super(); + constructor(indirect: number, inTag: number, aOffset: number, bOffset: number, dstOffset: number) { + super(indirect, inTag, aOffset, bOffset, dstOffset); } async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { @@ -107,12 +109,12 @@ export class Shl extends Instruction { } } -export class Shr extends Instruction { - static type: string = 'SHR'; - static numberOfOperands = 3; +export class Shr extends ThreeOperandInstruction { + static readonly type: string = 'SHR'; + static readonly opcode = Opcode.SHR; - constructor(private inTag: TypeTag, private aOffset: number, private bOffset: number, private dstOffset: number) { - super(); + constructor(indirect: number, inTag: number, aOffset: number, bOffset: number, dstOffset: number) { + super(indirect, inTag, aOffset, bOffset, dstOffset); } async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { diff --git a/yarn-project/acir-simulator/src/avm/opcodes/comparators.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/comparators.test.ts index ff604dbf2cb..b707b138aa7 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/comparators.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/comparators.test.ts @@ -17,13 +17,34 @@ describe('Comparators', () => { }); describe('Eq', () => { + it('Should deserialize correctly', () => { + const buf = Buffer.from([ + Eq.opcode, // opcode + 0x01, // indirect + TypeTag.UINT64, // inTag + ...Buffer.from('12345678', 'hex'), // aOffset + ...Buffer.from('23456789', 'hex'), // bOffset + ...Buffer.from('3456789a', 'hex'), // dstOffset + ]); + const inst = new Eq( + /*indirect=*/ 0x01, + /*inTag=*/ TypeTag.UINT64, + /*aOffset=*/ 0x12345678, + /*bOffset=*/ 0x23456789, + /*dstOffset=*/ 0x3456789a, + ); + + expect(Eq.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + it('Works on integral types', async () => { machineState.memory.setSlice(0, [new Uint32(1), new Uint32(2), new Uint32(3), new Uint32(1)]); [ - new Eq(TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10), - new Eq(TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 11), - new Eq(TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 3, /*dstOffset=*/ 12), + new Eq(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10), + new Eq(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 11), + new Eq(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 3, /*dstOffset=*/ 12), ].forEach(i => i.execute(machineState, journal)); const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4); @@ -34,9 +55,9 @@ describe('Comparators', () => { machineState.memory.setSlice(0, [new Field(1), new Field(2), new Field(3), new Field(1)]); [ - new Eq(TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10), - new Eq(TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 11), - new Eq(TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 3, /*dstOffset=*/ 12), + new Eq(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10), + new Eq(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 11), + new Eq(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 3, /*dstOffset=*/ 12), ].forEach(i => i.execute(machineState, journal)); const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4); @@ -47,10 +68,10 @@ describe('Comparators', () => { machineState.memory.setSlice(0, [new Field(1), new Uint32(2), new Uint16(3)]); const ops = [ - new Eq(TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10), - new Eq(TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 10), - new Eq(TypeTag.UINT16, /*aOffset=*/ 1, /*bOffset=*/ 2, /*dstOffset=*/ 10), - new Eq(TypeTag.UINT16, /*aOffset=*/ 1, /*bOffset=*/ 1, /*dstOffset=*/ 10), + new Eq(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10), + new Eq(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 10), + new Eq(/*indirect=*/ 0, TypeTag.UINT16, /*aOffset=*/ 1, /*bOffset=*/ 2, /*dstOffset=*/ 10), + new Eq(/*indirect=*/ 0, TypeTag.UINT16, /*aOffset=*/ 1, /*bOffset=*/ 1, /*dstOffset=*/ 10), ]; for (const o of ops) { @@ -60,13 +81,34 @@ describe('Comparators', () => { }); describe('Lt', () => { + it('Should deserialize correctly', () => { + const buf = Buffer.from([ + Lt.opcode, // opcode + 0x01, // indirect + TypeTag.UINT64, // inTag + ...Buffer.from('12345678', 'hex'), // aOffset + ...Buffer.from('23456789', 'hex'), // bOffset + ...Buffer.from('3456789a', 'hex'), // dstOffset + ]); + const inst = new Lt( + /*indirect=*/ 0x01, + /*inTag=*/ TypeTag.UINT64, + /*aOffset=*/ 0x12345678, + /*bOffset=*/ 0x23456789, + /*dstOffset=*/ 0x3456789a, + ); + + expect(Lt.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + it('Works on integral types', async () => { machineState.memory.setSlice(0, [new Uint32(1), new Uint32(2), new Uint32(0)]); [ - new Lt(TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 0, /*dstOffset=*/ 10), - new Lt(TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 11), - new Lt(TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 12), + new Lt(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 0, /*dstOffset=*/ 10), + new Lt(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 11), + new Lt(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 12), ].forEach(i => i.execute(machineState, journal)); const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4); @@ -77,9 +119,9 @@ describe('Comparators', () => { machineState.memory.setSlice(0, [new Field(1), new Field(2), new Field(0)]); [ - new Lt(TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 0, /*dstOffset=*/ 10), - new Lt(TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 11), - new Lt(TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 12), + new Lt(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 0, /*dstOffset=*/ 10), + new Lt(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 11), + new Lt(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 12), ].forEach(i => i.execute(machineState, journal)); const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4); @@ -90,10 +132,10 @@ describe('Comparators', () => { machineState.memory.setSlice(0, [new Field(1), new Uint32(2), new Uint16(3)]); const ops = [ - new Lt(TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10), - new Lt(TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 10), - new Lt(TypeTag.UINT16, /*aOffset=*/ 1, /*bOffset=*/ 2, /*dstOffset=*/ 10), - new Lt(TypeTag.UINT16, /*aOffset=*/ 1, /*bOffset=*/ 1, /*dstOffset=*/ 10), + new Lt(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10), + new Lt(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 10), + new Lt(/*indirect=*/ 0, TypeTag.UINT16, /*aOffset=*/ 1, /*bOffset=*/ 2, /*dstOffset=*/ 10), + new Lt(/*indirect=*/ 0, TypeTag.UINT16, /*aOffset=*/ 1, /*bOffset=*/ 1, /*dstOffset=*/ 10), ]; for (const o of ops) { @@ -103,13 +145,34 @@ describe('Comparators', () => { }); describe('Lte', () => { + it('Should deserialize correctly', () => { + const buf = Buffer.from([ + Lte.opcode, // opcode + 0x01, // indirect + TypeTag.UINT64, // inTag + ...Buffer.from('12345678', 'hex'), // aOffset + ...Buffer.from('23456789', 'hex'), // bOffset + ...Buffer.from('3456789a', 'hex'), // dstOffset + ]); + const inst = new Lte( + /*indirect=*/ 0x01, + /*inTag=*/ TypeTag.UINT64, + /*aOffset=*/ 0x12345678, + /*bOffset=*/ 0x23456789, + /*dstOffset=*/ 0x3456789a, + ); + + expect(Lte.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + it('Works on integral types', async () => { machineState.memory.setSlice(0, [new Uint32(1), new Uint32(2), new Uint32(0)]); [ - new Lte(TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 0, /*dstOffset=*/ 10), - new Lte(TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 11), - new Lte(TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 12), + new Lte(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 0, /*dstOffset=*/ 10), + new Lte(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 11), + new Lte(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 12), ].forEach(i => i.execute(machineState, journal)); const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4); @@ -120,9 +183,9 @@ describe('Comparators', () => { machineState.memory.setSlice(0, [new Field(1), new Field(2), new Field(0)]); [ - new Lte(TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 0, /*dstOffset=*/ 10), - new Lte(TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 11), - new Lte(TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 12), + new Lte(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 0, /*dstOffset=*/ 10), + new Lte(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 11), + new Lte(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 12), ].forEach(i => i.execute(machineState, journal)); const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 4); @@ -133,10 +196,10 @@ describe('Comparators', () => { machineState.memory.setSlice(0, [new Field(1), new Uint32(2), new Uint16(3)]); const ops = [ - new Lte(TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10), - new Lte(TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 10), - new Lte(TypeTag.UINT16, /*aOffset=*/ 1, /*bOffset=*/ 2, /*dstOffset=*/ 10), - new Lte(TypeTag.UINT16, /*aOffset=*/ 1, /*bOffset=*/ 1, /*dstOffset=*/ 10), + new Lte(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 10), + new Lte(/*indirect=*/ 0, TypeTag.UINT32, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 10), + new Lte(/*indirect=*/ 0, TypeTag.UINT16, /*aOffset=*/ 1, /*bOffset=*/ 2, /*dstOffset=*/ 10), + new Lte(/*indirect=*/ 0, TypeTag.UINT16, /*aOffset=*/ 1, /*bOffset=*/ 1, /*dstOffset=*/ 10), ]; for (const o of ops) { diff --git a/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts b/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts index f89d450dbb9..a4cffa19d4a 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/comparators.ts @@ -1,14 +1,15 @@ import { AvmMachineState } from '../avm_machine_state.js'; -import { TypeTag } from '../avm_memory_types.js'; import { AvmJournal } from '../journal/index.js'; +import { Opcode } from '../serialization/instruction_serialization.js'; import { Instruction } from './instruction.js'; +import { ThreeOperandInstruction } from './instruction_impl.js'; -export class Eq extends Instruction { - static type: string = 'EQ'; - static numberOfOperands = 3; +export class Eq extends ThreeOperandInstruction { + static readonly type: string = 'EQ'; + static readonly opcode = Opcode.EQ; - constructor(private inTag: TypeTag, private aOffset: number, private bOffset: number, private dstOffset: number) { - super(); + constructor(indirect: number, inTag: number, aOffset: number, bOffset: number, dstOffset: number) { + super(indirect, inTag, aOffset, bOffset, dstOffset); } async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { @@ -25,12 +26,12 @@ export class Eq extends Instruction { } } -export class Lt extends Instruction { - static type: string = 'Lt'; - static numberOfOperands = 3; +export class Lt extends ThreeOperandInstruction { + static readonly type: string = 'LT'; + static readonly opcode = Opcode.LT; - constructor(private inTag: TypeTag, private aOffset: number, private bOffset: number, private dstOffset: number) { - super(); + constructor(indirect: number, inTag: number, aOffset: number, bOffset: number, dstOffset: number) { + super(indirect, inTag, aOffset, bOffset, dstOffset); } async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { @@ -47,12 +48,12 @@ export class Lt extends Instruction { } } -export class Lte extends Instruction { - static type: string = 'LTE'; - static numberOfOperands = 3; +export class Lte extends ThreeOperandInstruction { + static readonly type: string = 'LTE'; + static readonly opcode = Opcode.LTE; - constructor(private inTag: TypeTag, private aOffset: number, private bOffset: number, private dstOffset: number) { - super(); + constructor(indirect: number, inTag: number, aOffset: number, bOffset: number, dstOffset: number) { + super(indirect, inTag, aOffset, bOffset, dstOffset); } async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { diff --git a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts index 7c04e08f246..82879c619b9 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.test.ts @@ -3,15 +3,11 @@ import { Fr } from '@aztec/foundation/fields'; import { MockProxy, mock } from 'jest-mock-extended'; import { AvmMachineState } from '../avm_machine_state.js'; -import { Field, TypeTag, Uint16 } from '../avm_memory_types.js'; +import { Field, Uint16 } from '../avm_memory_types.js'; import { initExecutionEnvironment } from '../fixtures/index.js'; import { AvmJournal } from '../journal/journal.js'; -import { Add, Mul, Sub } from './arithmetic.js'; -import { And, Not, Or, Shl, Shr, Xor } from './bitwise.js'; -import { Eq, Lt, Lte } from './comparators.js'; import { InternalCall, InternalReturn, Jump, JumpI, Return, Revert } from './control_flow.js'; import { InstructionExecutionError } from './instruction.js'; -import { CMov, CalldataCopy, Cast, Mov, Set } from './memory.js'; describe('Control Flow Opcodes', () => { let journal: MockProxy; @@ -22,7 +18,18 @@ describe('Control Flow Opcodes', () => { machineState = new AvmMachineState(initExecutionEnvironment()); }); - describe('Jumps', () => { + describe('JUMP', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + Jump.opcode, // opcode + ...Buffer.from('12345678', 'hex'), // loc + ]); + const inst = new Jump(/*loc=*/ 0x12345678); + + expect(Jump.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + it('Should implement JUMP', async () => { const jumpLocation = 22; @@ -32,6 +39,21 @@ describe('Control Flow Opcodes', () => { await instruction.execute(machineState, journal); expect(machineState.pc).toBe(jumpLocation); }); + }); + + describe('JUMPI', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + JumpI.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // loc + ...Buffer.from('a2345678', 'hex'), // condOffset + ]); + const inst = new JumpI(/*indirect=*/ 1, /*loc=*/ 0x12345678, /*condOffset=*/ 0xa2345678); + + expect(JumpI.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); it('Should implement JUMPI - truthy', async () => { const jumpLocation = 22; @@ -42,12 +64,12 @@ describe('Control Flow Opcodes', () => { machineState.memory.set(0, new Uint16(1n)); machineState.memory.set(1, new Uint16(2n)); - const instruction = new JumpI(jumpLocation, 0); + const instruction = new JumpI(/*indirect=*/ 0, jumpLocation, /*condOffset=*/ 0); await instruction.execute(machineState, journal); expect(machineState.pc).toBe(jumpLocation); // Truthy can be greater than 1 - const instruction1 = new JumpI(jumpLocation1, 1); + const instruction1 = new JumpI(/*indirect=*/ 0, jumpLocation1, /*condOffset=*/ 1); await instruction1.execute(machineState, journal); expect(machineState.pc).toBe(jumpLocation1); }); @@ -59,10 +81,23 @@ describe('Control Flow Opcodes', () => { machineState.memory.set(0, new Uint16(0n)); - const instruction = new JumpI(jumpLocation, 0); + const instruction = new JumpI(/*indirect=*/ 0, jumpLocation, /*condOffset=*/ 0); await instruction.execute(machineState, journal); expect(machineState.pc).toBe(1); }); + }); + + describe('INTERNALCALL and RETURN', () => { + it('INTERNALCALL Should (de)serialize correctly', () => { + const buf = Buffer.from([ + InternalCall.opcode, // opcode + ...Buffer.from('12345678', 'hex'), // loc + ]); + const inst = new InternalCall(/*loc=*/ 0x12345678); + + expect(InternalCall.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); it('Should implement Internal Call and Return', async () => { const jumpLocation = 22; @@ -79,6 +114,13 @@ describe('Control Flow Opcodes', () => { expect(machineState.pc).toBe(1); }); + it('Should error if Internal Return is called without a corresponding Internal Call', async () => { + const returnInstruction = () => new InternalReturn().execute(machineState, journal); + await expect(returnInstruction()).rejects.toThrow(InstructionExecutionError); + }); + }); + + describe('General flow', () => { it('Should chain series of control flow instructions', async () => { const jumpLocation0 = 22; const jumpLocation1 = 69; @@ -113,47 +155,22 @@ describe('Control Flow Opcodes', () => { expect(machineState.pc).toBe(expectedPcs[i]); } }); + }); - it('Should error if Internal Return is called without a corresponding Internal Call', async () => { - const returnInstruction = () => new InternalReturn().execute(machineState, journal); - await expect(returnInstruction()).rejects.toThrow(InstructionExecutionError); - }); - - it('Should increment PC on All other Instructions', async () => { - const instructions = [ - new Add(0, 1, 2), - new Sub(0, 1, 2), - new Mul(0, 1, 2), - new Lt(TypeTag.UINT16, 0, 1, 2), - new Lte(TypeTag.UINT16, 0, 1, 2), - new Eq(TypeTag.UINT16, 0, 1, 2), - new Xor(TypeTag.UINT16, 0, 1, 2), - new And(TypeTag.UINT16, 0, 1, 2), - new Or(TypeTag.UINT16, 0, 1, 2), - new Shl(TypeTag.UINT16, 0, 1, 2), - new Shr(TypeTag.UINT16, 0, 1, 2), - new Not(TypeTag.UINT16, 0, 2), - new CalldataCopy(0, 1, 2), - new Set(TypeTag.UINT16, 0n, 1), - new Mov(0, 1), - new CMov(0, 1, 2, 3), - new Cast(TypeTag.UINT16, 0, 1), - ]; - - for (const instruction of instructions) { - // Use a fresh machine state each run - const innerMachineState = new AvmMachineState(initExecutionEnvironment()); - innerMachineState.memory.set(0, new Uint16(4n)); - innerMachineState.memory.set(1, new Uint16(8n)); - innerMachineState.memory.set(2, new Uint16(12n)); - expect(innerMachineState.pc).toBe(0); - - await instruction.execute(innerMachineState, journal); - } + describe('RETURN', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + Return.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // returnOffset + ...Buffer.from('a2345678', 'hex'), // copySize + ]); + const inst = new Return(/*indirect=*/ 0x01, /*returnOffset=*/ 0x12345678, /*copySize=*/ 0xa2345678); + + expect(Return.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); }); - }); - describe('Halting Opcodes', () => { it('Should return data from the return opcode', async () => { const returnData = [new Fr(1n), new Fr(2n), new Fr(3n)]; @@ -161,13 +178,28 @@ describe('Control Flow Opcodes', () => { machineState.memory.set(1, new Field(2n)); machineState.memory.set(2, new Field(3n)); - const instruction = new Return(0, returnData.length); + const instruction = new Return(/*indirect=*/ 0, /*returnOffset=*/ 0, returnData.length); await instruction.execute(machineState, journal); expect(machineState.getReturnData()).toEqual(returnData); expect(machineState.halted).toBe(true); expect(machineState.reverted).toBe(false); }); + }); + + describe('REVERT', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + Revert.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // returnOffset + ...Buffer.from('a2345678', 'hex'), // retSize + ]); + const inst = new Revert(/*indirect=*/ 0x01, /*returnOffset=*/ 0x12345678, /*retSize=*/ 0xa2345678); + + expect(Revert.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); it('Should return data and revert from the revert opcode', async () => { const returnData = [new Fr(1n), new Fr(2n), new Fr(3n)]; @@ -176,7 +208,7 @@ describe('Control Flow Opcodes', () => { machineState.memory.set(1, new Field(2n)); machineState.memory.set(2, new Field(3n)); - const instruction = new Revert(0, returnData.length); + const instruction = new Revert(/*indirect=*/ 0, /*returnOffset=*/ 0, returnData.length); await instruction.execute(machineState, journal); expect(machineState.getReturnData()).toEqual(returnData); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts index c890fc700e3..1527a677116 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/control_flow.ts @@ -1,13 +1,21 @@ import { AvmMachineState } from '../avm_machine_state.js'; import { IntegralValue } from '../avm_memory_types.js'; import { AvmJournal } from '../journal/journal.js'; +import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; import { Instruction, InstructionExecutionError } from './instruction.js'; export class Return extends Instruction { static type: string = 'RETURN'; - static numberOfOperands = 2; - - constructor(private returnOffset: number, private copySize: number) { + static readonly opcode: Opcode = Opcode.RETURN; + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat: OperandType[] = [ + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT32, + OperandType.UINT32, + ]; + + constructor(private indirect: number, private returnOffset: number, private copySize: number) { super(); } @@ -22,9 +30,16 @@ export class Return extends Instruction { export class Revert extends Instruction { static type: string = 'RETURN'; - static numberOfOperands = 2; - - constructor(private returnOffset: number, private retSize: number) { + static readonly opcode: Opcode = Opcode.REVERT; + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat: OperandType[] = [ + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT32, + OperandType.UINT32, + ]; + + constructor(private indirect: number, private returnOffset: number, private retSize: number) { super(); } @@ -40,7 +55,9 @@ export class Revert extends Instruction { export class Jump extends Instruction { static type: string = 'JUMP'; - static numberOfOperands = 1; + static readonly opcode: Opcode = Opcode.JUMP; + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat: OperandType[] = [OperandType.UINT8, OperandType.UINT32]; constructor(private jumpOffset: number) { super(); @@ -53,9 +70,17 @@ export class Jump extends Instruction { export class JumpI extends Instruction { static type: string = 'JUMPI'; - static numberOfOperands = 1; + static readonly opcode: Opcode = Opcode.JUMPI; + + // Instruction wire format with opcode. + static readonly wireFormat: OperandType[] = [ + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT32, + OperandType.UINT32, + ]; - constructor(private jumpOffset: number, private condOffset: number) { + constructor(private indirect: number, private loc: number, private condOffset: number) { super(); } @@ -66,28 +91,32 @@ export class JumpI extends Instruction { if (condition.toBigInt() == 0n) { this.incrementPc(machineState); } else { - machineState.pc = this.jumpOffset; + machineState.pc = this.loc; } } } export class InternalCall extends Instruction { - static type: string = 'INTERNALCALL'; - static numberOfOperands = 1; + static readonly type: string = 'INTERNALCALL'; + static readonly opcode: Opcode = Opcode.INTERNALCALL; + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat: OperandType[] = [OperandType.UINT8, OperandType.UINT32]; - constructor(private jumpOffset: number) { + constructor(private loc: number) { super(); } async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { machineState.internalCallStack.push(machineState.pc + 1); - machineState.pc = this.jumpOffset; + machineState.pc = this.loc; } } export class InternalReturn extends Instruction { - static type: string = 'INTERNALRETURN'; - static numberOfOperands = 0; + static readonly type: string = 'INTERNALRETURN'; + static readonly opcode: Opcode = Opcode.INTERNALRETURN; + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat: OperandType[] = [OperandType.UINT8]; constructor() { super(); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/decode_bytecode.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/decode_bytecode.test.ts deleted file mode 100644 index 57502440e2d..00000000000 --- a/yarn-project/acir-simulator/src/avm/opcodes/decode_bytecode.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Add, Sub } from './arithmetic.js'; -import { decodeBytecode } from './decode_bytecode.js'; -import { AVM_OPCODE_BYTE_LENGTH, AVM_OPERAND_BYTE_LENGTH, Instruction } from './instruction.js'; - -describe('Avm Decoder', () => { - const toByte = (num: number): Buffer => { - const buf = Buffer.alloc(AVM_OPCODE_BYTE_LENGTH); - buf.writeUInt8(num); - return buf; - }; - const to4Byte = (num: number): Buffer => { - const buf = Buffer.alloc(AVM_OPERAND_BYTE_LENGTH); - buf.writeUInt32BE(num); - return buf; - }; - - it('Should read bytecode buffer into a list of opcodes', () => { - const opcode = 1; - const opcode2 = 2; - const a = 1; - const b = 2; - const c = 3; - - const ops = toByte(opcode); - const ops2 = toByte(opcode2); - const as = to4Byte(a); - const bs = to4Byte(b); - const cs = to4Byte(c); - const bytecode = Buffer.concat([ops, as, bs, cs, ops2, as, bs, cs]); - - const expectedInstructions: Instruction[] = [new Add(a, b, c), new Sub(a, b, c)]; - - const instructions = decodeBytecode(bytecode); - expect(instructions).toEqual(expectedInstructions); - }); -}); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/decode_bytecode.ts b/yarn-project/acir-simulator/src/avm/opcodes/decode_bytecode.ts deleted file mode 100644 index 33533f31abb..00000000000 --- a/yarn-project/acir-simulator/src/avm/opcodes/decode_bytecode.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { AVM_OPCODE_BYTE_LENGTH, AVM_OPERAND_BYTE_LENGTH, Instruction } from './instruction.js'; -import { INSTRUCTION_SET } from './instruction_set.js'; -import { Opcode } from './opcodes.js'; - -/** - * Convert a buffer of bytecode into an array of instructions - * @param bytecode - Buffer of bytecode - * @returns Bytecode decoded into an ordered array of Instructions - */ -export function decodeBytecode(bytecode: Buffer): Instruction[] { - let bytePtr = 0; - const bytecodeLength = bytecode.length; - - const instructions: Instruction[] = []; - - while (bytePtr < bytecodeLength) { - const opcodeByte = bytecode[bytePtr]; - bytePtr += AVM_OPCODE_BYTE_LENGTH; - if (!(opcodeByte in Opcode)) { - throw new Error(`Opcode 0x${opcodeByte.toString(16)} not implemented`); - } - const opcode = opcodeByte as Opcode; - - const instructionType = INSTRUCTION_SET.get(opcode); - if (instructionType === undefined) { - throw new Error(`Opcode 0x${opcode.toString(16)} not implemented`); - } - const numberOfOperands = instructionType.numberOfOperands; - const operands: number[] = []; - for (let i = 0; i < numberOfOperands; i++) { - // TODO: support constants which might not be u32s - const operand = bytecode.readUInt32BE(bytePtr); - bytePtr += AVM_OPERAND_BYTE_LENGTH; - operands.push(operand); - } - - instructions.push(new instructionType(...operands)); - } - - return instructions; -} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/encode_to_bytecode.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/encode_to_bytecode.test.ts deleted file mode 100644 index 8b1ea033dee..00000000000 --- a/yarn-project/acir-simulator/src/avm/opcodes/encode_to_bytecode.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { encodeToBytecode } from './encode_to_bytecode.js'; -import { AVM_OPCODE_BYTE_LENGTH, AVM_OPERAND_BYTE_LENGTH } from './instruction.js'; -import { Opcode } from './opcodes.js'; - -describe('Avm Encoder', () => { - const toByte = (num: number): Buffer => { - const buf = Buffer.alloc(AVM_OPCODE_BYTE_LENGTH); - buf.writeUInt8(num); - return buf; - }; - const to4Byte = (num: number): Buffer => { - const buf = Buffer.alloc(AVM_OPERAND_BYTE_LENGTH); - buf.writeUInt32BE(num); - return buf; - }; - - it('Should properly encode instructions into bytecode buffers', () => { - const addArgs = [0, 1, 2]; - const subArgs = [3, 4, 5]; - - const addBytecode = encodeToBytecode(Opcode.ADD, addArgs); - const subBytecode = encodeToBytecode(Opcode.SUB, subArgs); - - const expectedAddBytecode = Buffer.concat([toByte(Opcode.ADD), to4Byte(0), to4Byte(1), to4Byte(2)]); - const expectedSubBytecode = Buffer.concat([toByte(Opcode.SUB), to4Byte(3), to4Byte(4), to4Byte(5)]); - - expect(addBytecode).toEqual(expectedAddBytecode); - expect(subBytecode).toEqual(expectedSubBytecode); - }); -}); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/encode_to_bytecode.ts b/yarn-project/acir-simulator/src/avm/opcodes/encode_to_bytecode.ts deleted file mode 100644 index 105c0f808ab..00000000000 --- a/yarn-project/acir-simulator/src/avm/opcodes/encode_to_bytecode.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { AVM_OPCODE_BYTE_LENGTH, AVM_OPERAND_BYTE_LENGTH } from './instruction.js'; -import { INSTRUCTION_SET } from './instruction_set.js'; -import { Opcode } from './opcodes.js'; - -/** - * Encode an instruction (opcode & arguments) to bytecode. - * @param opcode - the opcode to encode - * @param args - the arguments to encode - * @returns the bytecode for this one instruction - */ -export function encodeToBytecode(opcode: Opcode, args: number[]): Buffer { - const instructionType = INSTRUCTION_SET.get(opcode); - if (instructionType === undefined) { - throw new Error(`Opcode 0x${opcode.toString(16)} not implemented`); - } - - const numberOfOperands = instructionType.numberOfOperands; - if (args.length !== numberOfOperands) { - throw new Error( - `Opcode 0x${opcode.toString(16)} expects ${numberOfOperands} arguments, but ${args.length} were provided`, - ); - } - - const bytecode = Buffer.alloc(AVM_OPCODE_BYTE_LENGTH + numberOfOperands * AVM_OPERAND_BYTE_LENGTH); - - let bytePtr = 0; - bytecode.writeUInt8(opcode as number, bytePtr); - bytePtr += AVM_OPCODE_BYTE_LENGTH; - for (let i = 0; i < args.length; i++) { - bytecode.writeUInt32BE(args[i], bytePtr); - bytePtr += AVM_OPERAND_BYTE_LENGTH; - } - return bytecode; -} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/environment_getters.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/environment_getters.test.ts index 4dd6b0abb85..2acf4729546 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/environment_getters.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/environment_getters.test.ts @@ -37,44 +37,156 @@ describe('Environment getters instructions', () => { expect(actual).toEqual(value); }; - it('Should read address correctly', async () => { - const address = new Fr(123456n); - await envGetterTest('address', address, new Address(0)); + describe('Address', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + Address.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // dstOffset + ]); + const inst = new Address(/*indirect=*/ 0x01, /*dstOffset=*/ 0x12345678); + + expect(Address.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('Should read address correctly', async () => { + const address = new Fr(123456n); + await envGetterTest('address', address, new Address(/*indirect=*/ 0, /*dstOffset=*/ 0)); + }); }); - it('Should read storage address correctly', async () => { - const address = new Fr(123456n); - await envGetterTest('storageAddress', address, new StorageAddress(0)); + describe('StorageAddress', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + StorageAddress.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // dstOffset + ]); + const inst = new StorageAddress(/*indirect=*/ 0x01, /*dstOffset=*/ 0x12345678); + + expect(StorageAddress.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('Should read storage address correctly', async () => { + const address = new Fr(123456n); + await envGetterTest('storageAddress', address, new StorageAddress(/*indirect=*/ 0, /*dstOffset=*/ 0)); + }); }); - it('Should read Portal correctly', async () => { - const portal = new Fr(123456n); - await envGetterTest('portal', portal, new Portal(0)); + describe('Portal', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + Portal.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // dstOffset + ]); + const inst = new Portal(/*indirect=*/ 0x01, /*dstOffset=*/ 0x12345678); + + expect(Portal.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('Should read Portal correctly', async () => { + const portal = new Fr(123456n); + await envGetterTest('portal', portal, new Portal(/*indirect=*/ 0, /*dstOffset=*/ 0)); + }); }); - it('Should read FeePerL1Gas correctly', async () => { - const feePerL1Gas = new Fr(123456n); - await envGetterTest('feePerL1Gas', feePerL1Gas, new FeePerL1Gas(0)); + describe('FeePerL1Gas', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + FeePerL1Gas.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // dstOffset + ]); + const inst = new FeePerL1Gas(/*indirect=*/ 0x01, /*dstOffset=*/ 0x12345678); + + expect(FeePerL1Gas.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('Should read FeePerL1Gas correctly', async () => { + const feePerL1Gas = new Fr(123456n); + await envGetterTest('feePerL1Gas', feePerL1Gas, new FeePerL1Gas(/*indirect=*/ 0, /*dstOffset=*/ 0)); + }); }); - it('Should read FeePerL2Gas correctly', async () => { - const feePerL2Gas = new Fr(123456n); - await envGetterTest('feePerL2Gas', feePerL2Gas, new FeePerL2Gas(0)); + describe('FeePerL2Gas', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + FeePerL2Gas.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // dstOffset + ]); + const inst = new FeePerL2Gas(/*indirect=*/ 0x01, /*dstOffset=*/ 0x12345678); + + expect(FeePerL2Gas.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('Should read FeePerL2Gas correctly', async () => { + const feePerL2Gas = new Fr(123456n); + await envGetterTest('feePerL2Gas', feePerL2Gas, new FeePerL2Gas(/*indirect=*/ 0, /*dstOffset=*/ 0)); + }); }); - it('Should read FeePerDAGas correctly', async () => { - const feePerDaGas = new Fr(123456n); - await envGetterTest('feePerDaGas', feePerDaGas, new FeePerDAGas(0)); + describe('FeePerDAGas', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + FeePerDAGas.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // dstOffset + ]); + const inst = new FeePerDAGas(/*indirect=*/ 0x01, /*dstOffset=*/ 0x12345678); + + expect(FeePerDAGas.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('Should read FeePerDAGas correctly', async () => { + const feePerDaGas = new Fr(123456n); + await envGetterTest('feePerDaGas', feePerDaGas, new FeePerDAGas(/*indirect=*/ 0, /*dstOffset=*/ 0)); + }); }); - it('Should read Origin correctly', async () => { - const origin = new Fr(123456n); - await envGetterTest('origin', origin, new Origin(0)); + describe('Origin', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + Origin.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // dstOffset + ]); + const inst = new Origin(/*indirect=*/ 0x01, /*dstOffset=*/ 0x12345678); + + expect(Origin.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('Should read Origin correctly', async () => { + const origin = new Fr(123456n); + await envGetterTest('origin', origin, new Origin(/*indirect=*/ 0, /*dstOffset=*/ 0)); + }); }); - it('Should read Sender correctly', async () => { - const sender = new Fr(123456n); - await envGetterTest('sender', sender, new Sender(0)); + describe('Sender', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + Sender.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // dstOffset + ]); + const inst = new Sender(/*indirect=*/ 0x01, /*dstOffset=*/ 0x12345678); + + expect(Sender.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('Should read Sender correctly', async () => { + const sender = new Fr(123456n); + await envGetterTest('sender', sender, new Sender(/*indirect=*/ 0, /*dstOffset=*/ 0)); + }); }); describe('Global Variables', () => { @@ -88,24 +200,80 @@ describe('Environment getters instructions', () => { expect(actual).toEqual(value); }; - it('Should read chainId', async () => { - const chainId = new Fr(123456n); - await readGlobalVariableTest('chainId', chainId, new ChainId(0)); + describe('chainId', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + ChainId.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // dstOffset + ]); + const inst = new ChainId(/*indirect=*/ 0x01, /*dstOffset=*/ 0x12345678); + + expect(ChainId.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('Should read chainId', async () => { + const chainId = new Fr(123456n); + await readGlobalVariableTest('chainId', chainId, new ChainId(/*indirect=*/ 0, /*dstOffset=*/ 0)); + }); }); - it('Should read version', async () => { - const version = new Fr(123456n); - await readGlobalVariableTest('version', version, new Version(0)); + describe('version', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + Version.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // dstOffset + ]); + const inst = new Version(/*indirect=*/ 0x01, /*dstOffset=*/ 0x12345678); + + expect(Version.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('Should read version', async () => { + const version = new Fr(123456n); + await readGlobalVariableTest('version', version, new Version(/*indirect=*/ 0, /*dstOffset=*/ 0)); + }); }); - it('Should read block number', async () => { - const blockNumber = new Fr(123456n); - await readGlobalVariableTest('blockNumber', blockNumber, new BlockNumber(0)); + describe('block', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + BlockNumber.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // dstOffset + ]); + const inst = new BlockNumber(/*indirect=*/ 0x01, /*dstOffset=*/ 0x12345678); + + expect(BlockNumber.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('Should read block number', async () => { + const blockNumber = new Fr(123456n); + await readGlobalVariableTest('blockNumber', blockNumber, new BlockNumber(/*indirect=*/ 0, /*dstOffset=*/ 0)); + }); }); - it('Should read timestamp', async () => { - const timestamp = new Fr(123456n); - await readGlobalVariableTest('timestamp', timestamp, new Timestamp(0)); + describe('timestamp', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + Timestamp.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // dstOffset + ]); + const inst = new Timestamp(/*indirect=*/ 0x01, /*dstOffset=*/ 0x12345678); + + expect(Timestamp.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('Should read timestamp', async () => { + const timestamp = new Fr(123456n); + await readGlobalVariableTest('timestamp', timestamp, new Timestamp(/*indirect=*/ 0, /*dstOffset=*/ 0)); + }); }); }); }); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/environment_getters.ts b/yarn-project/acir-simulator/src/avm/opcodes/environment_getters.ts index 18e97575b96..8dc59330e94 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/environment_getters.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/environment_getters.ts @@ -1,211 +1,136 @@ +import { AvmExecutionEnvironment } from '../avm_execution_environment.js'; import { AvmMachineState } from '../avm_machine_state.js'; import { Field } from '../avm_memory_types.js'; import { AvmJournal } from '../journal/journal.js'; +import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; import { Instruction } from './instruction.js'; -export class Address extends Instruction { - static type: string = 'ADDRESS'; - static numberOfOperands = 1; +abstract class GetterInstruction extends Instruction { + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat: OperandType[] = [OperandType.UINT8, OperandType.UINT8, OperandType.UINT32]; - constructor(private destOffset: number) { + constructor(protected indirect: number, protected dstOffset: number) { super(); } async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const { address } = machineState.executionEnvironment; - - machineState.memory.set(this.destOffset, new Field(address)); + const res = new Field(this.getIt(machineState.executionEnvironment)); + machineState.memory.set(this.dstOffset, res); this.incrementPc(machineState); } + + protected abstract getIt(env: AvmExecutionEnvironment): any; } -export class StorageAddress extends Instruction { - static type: string = 'STORAGEADDRESS'; - static numberOfOperands = 1; +export class Address extends GetterInstruction { + static type: string = 'ADDRESS'; + static readonly opcode: Opcode = Opcode.ADDRESS; - constructor(private destOffset: number) { - super(); + protected getIt(env: AvmExecutionEnvironment): any { + return env.address; } +} - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const { storageAddress } = machineState.executionEnvironment; +export class StorageAddress extends GetterInstruction { + static type: string = 'STORAGEADDRESS'; + static readonly opcode: Opcode = Opcode.STORAGEADDRESS; - machineState.memory.set(this.destOffset, new Field(storageAddress)); - this.incrementPc(machineState); + protected getIt(env: AvmExecutionEnvironment): any { + return env.storageAddress; } } -export class Sender extends Instruction { +export class Sender extends GetterInstruction { static type: string = 'SENDER'; - static numberOfOperands = 1; + static readonly opcode: Opcode = Opcode.SENDER; - constructor(private destOffset: number) { - super(); - } - - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const { sender } = machineState.executionEnvironment; - - machineState.memory.set(this.destOffset, new Field(sender)); - - this.incrementPc(machineState); + protected getIt(env: AvmExecutionEnvironment): any { + return env.sender; } } -export class Origin extends Instruction { +export class Origin extends GetterInstruction { static type: string = 'ORIGIN'; - static numberOfOperands = 1; - - constructor(private destOffset: number) { - super(); - } + static readonly opcode: Opcode = Opcode.ORIGIN; - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const { origin } = machineState.executionEnvironment; - - machineState.memory.set(this.destOffset, new Field(origin)); - - this.incrementPc(machineState); + protected getIt(env: AvmExecutionEnvironment): any { + return env.origin; } } -export class FeePerL1Gas extends Instruction { +export class FeePerL1Gas extends GetterInstruction { static type: string = 'FEEPERL1GAS'; - static numberOfOperands = 1; - - constructor(private destOffset: number) { - super(); - } - - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const { feePerL1Gas } = machineState.executionEnvironment; - - machineState.memory.set(this.destOffset, new Field(feePerL1Gas)); + static readonly opcode: Opcode = Opcode.FEEPERL1GAS; - this.incrementPc(machineState); + protected getIt(env: AvmExecutionEnvironment): any { + return env.feePerL1Gas; } } -export class FeePerL2Gas extends Instruction { +export class FeePerL2Gas extends GetterInstruction { static type: string = 'FEEPERL2GAS'; - static numberOfOperands = 1; - - constructor(private destOffset: number) { - super(); - } - - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const { feePerL2Gas } = machineState.executionEnvironment; - - machineState.memory.set(this.destOffset, new Field(feePerL2Gas)); + static readonly opcode: Opcode = Opcode.FEEPERL2GAS; - this.incrementPc(machineState); + protected getIt(env: AvmExecutionEnvironment): any { + return env.feePerL2Gas; } } -export class FeePerDAGas extends Instruction { +export class FeePerDAGas extends GetterInstruction { static type: string = 'FEEPERDAGAS'; - static numberOfOperands = 1; - - constructor(private destOffset: number) { - super(); - } - - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const { feePerDaGas } = machineState.executionEnvironment; + static readonly opcode: Opcode = Opcode.FEEPERDAGAS; - machineState.memory.set(this.destOffset, new Field(feePerDaGas)); - - this.incrementPc(machineState); + protected getIt(env: AvmExecutionEnvironment): any { + return env.feePerDaGas; } } -export class Portal extends Instruction { +export class Portal extends GetterInstruction { static type: string = 'PORTAL'; - static numberOfOperands = 1; - - constructor(private destOffset: number) { - super(); - } - - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const { portal } = machineState.executionEnvironment; + static readonly opcode: Opcode = Opcode.PORTAL; - machineState.memory.set(this.destOffset, new Field(portal.toField())); - - this.incrementPc(machineState); + protected getIt(env: AvmExecutionEnvironment): any { + return env.portal.toField(); } } -export class ChainId extends Instruction { +export class ChainId extends GetterInstruction { static type: string = 'CHAINID'; - static numberOfOperands = 1; - - constructor(private destOffset: number) { - super(); - } + static readonly opcode: Opcode = Opcode.CHAINID; - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const { chainId } = machineState.executionEnvironment.globals; - - machineState.memory.set(this.destOffset, new Field(chainId)); - - this.incrementPc(machineState); + protected getIt(env: AvmExecutionEnvironment): any { + return env.globals.chainId; } } -export class Version extends Instruction { +export class Version extends GetterInstruction { static type: string = 'VERSION'; - static numberOfOperands = 1; - - constructor(private destOffset: number) { - super(); - } - - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const { version } = machineState.executionEnvironment.globals; - - machineState.memory.set(this.destOffset, new Field(version)); + static readonly opcode: Opcode = Opcode.VERSION; - this.incrementPc(machineState); + protected getIt(env: AvmExecutionEnvironment): any { + return env.globals.version; } } -export class BlockNumber extends Instruction { +export class BlockNumber extends GetterInstruction { static type: string = 'BLOCKNUMBER'; - static numberOfOperands = 1; - - constructor(private destOffset: number) { - super(); - } - - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const { blockNumber } = machineState.executionEnvironment.globals; - - machineState.memory.set(this.destOffset, new Field(blockNumber)); + static readonly opcode: Opcode = Opcode.BLOCKNUMBER; - this.incrementPc(machineState); + protected getIt(env: AvmExecutionEnvironment): any { + return env.globals.blockNumber; } } -export class Timestamp extends Instruction { +export class Timestamp extends GetterInstruction { static type: string = 'TIMESTAMP'; - static numberOfOperands = 1; - - constructor(private destOffset: number) { - super(); - } - - async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const { timestamp } = machineState.executionEnvironment.globals; + static readonly opcode: Opcode = Opcode.TIMESTAMP; - machineState.memory.set(this.destOffset, new Field(timestamp)); - - this.incrementPc(machineState); + protected getIt(env: AvmExecutionEnvironment): any { + return env.globals.timestamp; } } -// export class Coinbase extends Instruction { +// export class Coinbase extends GetterInstruction { // static type: string = 'COINBASE'; // static numberOfOperands = 1; @@ -223,7 +148,7 @@ export class Timestamp extends Instruction { // } // // TODO: are these even needed within the block? (both block gas limit variables - why does the execution env care?) -// export class BlockL1GasLimit extends Instruction { +// export class BlockL1GasLimit extends GetterInstruction { // static type: string = 'BLOCKL1GASLIMIT'; // static numberOfOperands = 1; @@ -240,7 +165,7 @@ export class Timestamp extends Instruction { // } // } -// export class BlockL2GasLimit extends Instruction { +// export class BlockL2GasLimit extends GetterInstruction { // static type: string = 'BLOCKL2GASLIMIT'; // static numberOfOperands = 1; @@ -257,7 +182,7 @@ export class Timestamp extends Instruction { // } // } -// export class BlockDAGasLimit extends Instruction { +// export class BlockDAGasLimit extends GetterInstruction { // static type: string = 'BLOCKDAGASLIMIT'; // static numberOfOperands = 1; diff --git a/yarn-project/acir-simulator/src/avm/opcodes/external_calls.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/external_calls.test.ts index 50c29128ec5..2395cc32479 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/external_calls.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/external_calls.test.ts @@ -9,9 +9,12 @@ import { Field } from '../avm_memory_types.js'; import { initExecutionEnvironment } from '../fixtures/index.js'; import { HostStorage } from '../journal/host_storage.js'; import { AvmJournal } from '../journal/journal.js'; -import { encodeToBytecode } from './encode_to_bytecode.js'; -import { Call } from './external_calls.js'; -import { Opcode } from './opcodes.js'; +import { encodeToBytecode } from '../serialization/bytecode_serialization.js'; +import { Return } from './control_flow.js'; +import { Call, StaticCall } from './external_calls.js'; +import { Instruction } from './instruction.js'; +import { CalldataCopy } from './memory.js'; +import { SStore } from './storage.js'; describe('External Calls', () => { let machineState: AvmMachineState; @@ -31,44 +34,68 @@ describe('External Calls', () => { }); describe('Call', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + Call.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // gasOffset + ...Buffer.from('a2345678', 'hex'), // addrOffset + ...Buffer.from('b2345678', 'hex'), // argsOffset + ...Buffer.from('c2345678', 'hex'), // argsSize + ...Buffer.from('d2345678', 'hex'), // retOffset + ...Buffer.from('e2345678', 'hex'), // retSize + ...Buffer.from('f2345678', 'hex'), // successOffset + ]); + const inst = new Call( + /*indirect=*/ 0x01, + /*gasOffset=*/ 0x12345678, + /*addrOffset=*/ 0xa2345678, + /*argsOffset=*/ 0xb2345678, + /*argsSize=*/ 0xc2345678, + /*retOffset=*/ 0xd2345678, + /*retSize=*/ 0xe2345678, + /*successOffset=*/ 0xf2345678, + ); + + expect(Call.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + // TODO(https://github.com/AztecProtocol/aztec-packages/issues/3992): gas not implemented it('Should execute a call correctly', async () => { const gasOffset = 0; const gas = Fr.zero(); - const addrOffset = 1; const addr = new Fr(123456n); - const argsOffset = 2; const args = [new Field(1n), new Field(2n), new Field(3n)]; const argsSize = args.length; - const retOffset = 8; const retSize = 2; - const successOffset = 7; + const otherContextInstructionsBytecode = encodeToBytecode([ + new CalldataCopy(/*indirect=*/ 0, /*csOffset=*/ 0, /*copySize=*/ argsSize, /*dstOffset=*/ 0), + new SStore(/*indirect=*/ 0, /*srcOffset=*/ 0, /*slotOffset=*/ 0), + new Return(/*indirect=*/ 0, /*retOffset=*/ 0, /*size=*/ 2), + ]); machineState.memory.set(0, new Field(gas)); machineState.memory.set(1, new Field(addr)); machineState.memory.setSlice(2, args); - - const otherContextInstructions: [Opcode, any[]][] = [ - // Place [1,2,3] into memory - [Opcode.CALLDATACOPY, [/*value=*/ 0, /*copySize=*/ argsSize, /*dstOffset=*/ 0]], - // Store 1 into slot 1 - [Opcode.SSTORE, [/*slotOffset=*/ 0, /*dataOffset=*/ 0]], - // Return [1,2] from memory - [Opcode.RETURN, [/*retOffset=*/ 0, /*size=*/ 2]], - ]; - - const otherContextInstructionsBytecode = Buffer.concat( - otherContextInstructions.map(([opcode, args]) => encodeToBytecode(opcode, args)), - ); jest .spyOn(journal.hostStorage.contractsDb, 'getBytecode') .mockReturnValue(Promise.resolve(otherContextInstructionsBytecode)); - const instruction = new Call(gasOffset, addrOffset, argsOffset, argsSize, retOffset, retSize, successOffset); + const instruction = new Call( + /*indirect=*/ 0, + gasOffset, + addrOffset, + argsOffset, + argsSize, + retOffset, + retSize, + successOffset, + ); await instruction.execute(machineState, journal); const successValue = machineState.memory.get(successOffset); @@ -91,6 +118,33 @@ describe('External Calls', () => { }); describe('Static Call', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + StaticCall.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // gasOffset + ...Buffer.from('a2345678', 'hex'), // addrOffset + ...Buffer.from('b2345678', 'hex'), // argsOffset + ...Buffer.from('c2345678', 'hex'), // argsSize + ...Buffer.from('d2345678', 'hex'), // retOffset + ...Buffer.from('e2345678', 'hex'), // retSize + ...Buffer.from('f2345678', 'hex'), // successOffset + ]); + const inst = new StaticCall( + /*indirect=*/ 0x01, + /*gasOffset=*/ 0x12345678, + /*addrOffset=*/ 0xa2345678, + /*argsOffset=*/ 0xb2345678, + /*argsSize=*/ 0xc2345678, + /*retOffset=*/ 0xd2345678, + /*retSize=*/ 0xe2345678, + /*successOffset=*/ 0xf2345678, + ); + + expect(StaticCall.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + it('Should fail if a static call attempts to touch storage', async () => { const gasOffset = 0; const gas = new Field(0); @@ -108,20 +162,27 @@ describe('External Calls', () => { machineState.memory.set(1, addr); machineState.memory.setSlice(2, args); - const otherContextInstructions: [Opcode, any[]][] = [ - // Place [1,2,3] into memory - [Opcode.CALLDATACOPY, [/*value=*/ 0, /*copySize=*/ argsSize, /*dstOffset=*/ 0]], - [Opcode.SSTORE, [/*slotOffset*/ 1, /*dataOffset=*/ 0]], + const otherContextInstructions: Instruction[] = [ + new CalldataCopy(/*indirect=*/ 0, /*csOffset=*/ 0, /*copySize=*/ argsSize, /*dstOffset=*/ 0), + new SStore(/*indirect=*/ 0, /*srcOffset=*/ 1, /*slotOffset=*/ 0), ]; - const otherContextInstructionsBytecode = Buffer.concat( - otherContextInstructions.map(([opcode, args]) => encodeToBytecode(opcode, args)), - ); + const otherContextInstructionsBytecode = encodeToBytecode(otherContextInstructions); + jest .spyOn(journal.hostStorage.contractsDb, 'getBytecode') .mockReturnValue(Promise.resolve(otherContextInstructionsBytecode)); - const instruction = new Call(gasOffset, addrOffset, argsOffset, argsSize, retOffset, retSize, successOffset); + const instruction = new StaticCall( + /*indirect=*/ 0, + gasOffset, + addrOffset, + argsOffset, + argsSize, + retOffset, + retSize, + successOffset, + ); await instruction.execute(machineState, journal); // No revert has occurred, but the nested execution has failed diff --git a/yarn-project/acir-simulator/src/avm/opcodes/external_calls.ts b/yarn-project/acir-simulator/src/avm/opcodes/external_calls.ts index 280b1284f02..7d9fe5558fb 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/external_calls.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/external_calls.ts @@ -4,17 +4,31 @@ import { AvmContext } from '../avm_context.js'; import { AvmMachineState } from '../avm_machine_state.js'; import { Field } from '../avm_memory_types.js'; import { AvmJournal } from '../journal/journal.js'; +import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; import { Instruction } from './instruction.js'; export class Call extends Instruction { static type: string = 'CALL'; - static numberOfOperands = 7; + static readonly opcode: Opcode = Opcode.CALL; + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat: OperandType[] = [ + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT32, + OperandType.UINT32, + OperandType.UINT32, + OperandType.UINT32, + OperandType.UINT32, + OperandType.UINT32, + OperandType.UINT32, + ]; constructor( - private /* Unused due to no formal gas implementation at this moment */ _gasOffset: number, + private indirect: number, + private _gasOffset: number /* Unused due to no formal gas implementation at this moment */, private addrOffset: number, private argsOffset: number, - private argSize: number, + private argsSize: number, private retOffset: number, private retSize: number, private successOffset: number, @@ -25,7 +39,7 @@ export class Call extends Instruction { // TODO(https://github.com/AztecProtocol/aztec-packages/issues/3992): there is no concept of remaining / available gas at this moment async execute(machineState: AvmMachineState, journal: AvmJournal): Promise { const callAddress = machineState.memory.getAs(this.addrOffset); - const calldata = machineState.memory.getSlice(this.argsOffset, this.argSize).map(f => new Fr(f.toBigInt())); + const calldata = machineState.memory.getSlice(this.argsOffset, this.argsSize).map(f => new Fr(f.toBigInt())); const avmContext = AvmContext.prepExternalCallContext( new Fr(callAddress.toBigInt()), @@ -55,13 +69,26 @@ export class Call extends Instruction { export class StaticCall extends Instruction { static type: string = 'STATICCALL'; - static numberOfOperands = 7; + static readonly opcode: Opcode = Opcode.STATICCALL; + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat: OperandType[] = [ + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT32, + OperandType.UINT32, + OperandType.UINT32, + OperandType.UINT32, + OperandType.UINT32, + OperandType.UINT32, + OperandType.UINT32, + ]; constructor( - private /* Unused due to no formal gas implementation at this moment */ _gasOffset: number, + private indirect: number, + private _gasOffset: number /* Unused due to no formal gas implementation at this moment */, private addrOffset: number, private argsOffset: number, - private argSize: number, + private argsSize: number, private retOffset: number, private retSize: number, private successOffset: number, @@ -71,7 +98,7 @@ export class StaticCall extends Instruction { async execute(machineState: AvmMachineState, journal: AvmJournal): Promise { const callAddress = machineState.memory.get(this.addrOffset); - const calldata = machineState.memory.getSlice(this.argsOffset, this.argSize).map(f => new Fr(f.toBigInt())); + const calldata = machineState.memory.getSlice(this.argsOffset, this.argsSize).map(f => new Fr(f.toBigInt())); const avmContext = AvmContext.prepExternalStaticCallContext( new Fr(callAddress.toBigInt()), diff --git a/yarn-project/acir-simulator/src/avm/opcodes/index.ts b/yarn-project/acir-simulator/src/avm/opcodes/index.ts index 10ce6b4d0f7..2fa19f7b04a 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/index.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/index.ts @@ -1,5 +1,11 @@ export * from './arithmetic.js'; +export * from './bitwise.js'; export * from './control_flow.js'; export * from './instruction.js'; export * from './comparators.js'; export * from './memory.js'; +export * from './storage.js'; +// TODO(https://github.com/AztecProtocol/aztec-packages/issues/4359): dependency cycle +// export * from './external_calls.js'; +export * from './environment_getters.js'; +export * from './accrued_substate.js'; diff --git a/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts b/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts index 8eb73212fd2..ff5fdd5e96f 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts @@ -1,15 +1,17 @@ +import { assert } from 'console'; + import { AvmMachineState } from '../avm_machine_state.js'; import { TypeTag } from '../avm_memory_types.js'; import { AvmJournal } from '../journal/index.js'; - -export const AVM_OPERAND_BYTE_LENGTH = 4; // Keep in sync with cpp code -export const AVM_OPCODE_BYTE_LENGTH = 1; // Keep in sync with cpp code +import { BufferCursor } from '../serialization/buffer_cursor.js'; +import { OperandType, deserialize, serialize } from '../serialization/instruction_serialization.js'; /** - * Opcode base class + * Parent class for all AVM instructions. + * It's most important aspects are execution and (de)serialization. */ export abstract class Instruction { - abstract execute(machineState: AvmMachineState, journal: AvmJournal): Promise; + public abstract execute(machineState: AvmMachineState, journal: AvmJournal): Promise; incrementPc(machineState: AvmMachineState): void { machineState.pc++; @@ -35,6 +37,27 @@ export abstract class Instruction { checkTag(machineState, tag, offset); } } + + /** + * Deserializes a subclass of Instruction from a Buffer. + * If you want to use this, your subclass should specify a {@code static wireFormat: OperandType[]}. + * @param this Class object to deserialize to. + * @param buf Buffer to read from. + * @returns Constructed instance of Class. + */ + public static deserialize; wireFormat: OperandType[] }>( + this: T, + buf: BufferCursor | Buffer, + ): InstanceType { + const res = deserialize(buf, this.wireFormat); + const args = res.slice(1) as ConstructorParameters; // Remove opcode. + return new this(...args); + } + + public serialize(this: any): Buffer { + assert(this instanceof Instruction); + return serialize(this.constructor.wireFormat, this); + } } /** diff --git a/yarn-project/acir-simulator/src/avm/opcodes/instruction_impl.ts b/yarn-project/acir-simulator/src/avm/opcodes/instruction_impl.ts new file mode 100644 index 00000000000..fb8b8621a71 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/opcodes/instruction_impl.ts @@ -0,0 +1,52 @@ +import { OperandType } from '../serialization/instruction_serialization.js'; +import { Instruction } from './instruction.js'; + +/** + * Covers (de)serialization for an instruction with: + * indirect, inTag, and two UINT32s. + */ +export abstract class TwoOperandInstruction extends Instruction { + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat: OperandType[] = [ + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT32, + OperandType.UINT32, + ]; + + constructor( + protected indirect: number, + protected inTag: number, + protected aOffset: number, + protected dstOffset: number, + ) { + super(); + } +} + +/** + * Covers (de)serialization for an instruction with: + * indirect, inTag, and three UINT32s. + */ +export abstract class ThreeOperandInstruction extends Instruction { + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat: OperandType[] = [ + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT32, + OperandType.UINT32, + OperandType.UINT32, + ]; + + constructor( + protected indirect: number, + protected inTag: number, + protected aOffset: number, + protected bOffset: number, + protected dstOffset: number, + ) { + super(); + } +} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/instruction_set.ts b/yarn-project/acir-simulator/src/avm/opcodes/instruction_set.ts deleted file mode 100644 index e63fe02dc20..00000000000 --- a/yarn-project/acir-simulator/src/avm/opcodes/instruction_set.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { Add, Div, Mul, Sub } from './arithmetic.js'; -import { And, Not, Or, Shl, Shr, Xor } from './bitwise.js'; -import { InternalCall, InternalReturn, Jump, JumpI, Return } from './control_flow.js'; -// import { Call } from './external_calls.js'; -import { Instruction } from './instruction.js'; -import { CMov, CalldataCopy, Cast, Mov, Set } from './memory.js'; -import { Opcode } from './opcodes.js'; -//import { Eq, Lt, Lte } from './comparators.js'; -import { SLoad, SStore } from './storage.js'; - -type InstructionConstructor = new (...args: any[]) => Instruction; - -type InstructionConstructorAndMembers = InstructionConstructor & { - numberOfOperands: number; -}; - -export const INSTRUCTION_SET: Map = new Map( - new Array<[Opcode, InstructionConstructorAndMembers]>( - // Compute - // Compute - Arithmetic - [Opcode.ADD, Add], - [Opcode.SUB, Sub], - [Opcode.MUL, Mul], - [Opcode.DIV, Div], - //// Compute - Comparators - //[Opcode.EQ, Eq], - //[Opcode.LT, Lt], - //[Opcode.LTE, Lte], - //// Compute - Bitwise - [Opcode.AND, And], - [Opcode.OR, Or], - [Opcode.XOR, Xor], - [Opcode.NOT, Not], - [Opcode.SHL, Shl], - [Opcode.SHR, Shr], - //// Compute - Type Conversions - [Opcode.CAST, Cast], - - //// Execution Environment - //[Opcode.ADDRESS, Address], - //[Opcode.STORAGEADDRESS, Storageaddress], - //[Opcode.ORIGIN, Origin], - //[Opcode.SENDER, Sender], - //[Opcode.PORTAL, Portal], - //[Opcode.FEEPERL1GAS, Feeperl1gas], - //[Opcode.FEEPERL2GAS, Feeperl2gas], - //[Opcode.FEEPERDAGAS, Feeperdagas], - //[Opcode.CONTRACTCALLDEPTH, Contractcalldepth], - //// Execution Environment - Globals - //[Opcode.CHAINID, Chainid], - //[Opcode.VERSION, Version], - //[Opcode.BLOCKNUMBER, Blocknumber], - //[Opcode.TIMESTAMP, Timestamp], - //[Opcode.COINBASE, Coinbase], - //[Opcode.BLOCKL1GASLIMIT, Blockl1gaslimit], - //[Opcode.BLOCKL2GASLIMIT, Blockl2gaslimit], - //[Opcode.BLOCKDAGASLIMIT, Blockdagaslimit], - // Execution Environment - Calldata - [Opcode.CALLDATACOPY, CalldataCopy], - - //// Machine State - // Machine State - Gas - //[Opcode.L1GASLEFT, L1gasleft], - //[Opcode.L2GASLEFT, L2gasleft], - //[Opcode.DAGASLEFT, Dagasleft], - //// Machine State - Internal Control Flow - [Opcode.JUMP, Jump], - [Opcode.JUMPI, JumpI], - [Opcode.INTERNALCALL, InternalCall], - [Opcode.INTERNALRETURN, InternalReturn], - //// Machine State - Memory - [Opcode.SET, Set], - [Opcode.MOV, Mov], - [Opcode.CMOV, CMov], - - //// World State - //[Opcode.BLOCKHEADERBYNUMBER, Blockheaderbynumber], - [Opcode.SLOAD, SLoad], // Public Storage - [Opcode.SSTORE, SStore], // Public Storage - //[Opcode.READL1TOL2MSG, Readl1tol2msg], // Messages - //[Opcode.SENDL2TOL1MSG, Sendl2tol1msg], // Messages - //[Opcode.EMITNOTEHASH, Emitnotehash], // Notes & Nullifiers - //[Opcode.EMITNULLIFIER, Emitnullifier], // Notes & Nullifiers - - //// Accrued Substate - //[Opcode.EMITUNENCRYPTEDLOG, Emitunencryptedlog], - - //// Control Flow - Contract Calls - // [Opcode.CALL, Call], - //[Opcode.STATICCALL, Staticcall], - [Opcode.RETURN, Return], - //[Opcode.REVERT, Revert], - - //// Gadgets - //[Opcode.KECCAK, Keccak], - //[Opcode.POSEIDON, Poseidon], - ), -); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/memory.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/memory.test.ts index 1a10c5be15b..ce4e6165db4 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/memory.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/memory.test.ts @@ -18,8 +18,30 @@ describe('Memory instructions', () => { }); describe('SET', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + Set.opcode, // opcode + 0x01, // indirect + TypeTag.FIELD, // inTag + ...Buffer.from('12345678123456781234567812345678', 'hex'), // const (will be 128 bit) + ...Buffer.from('3456789a', 'hex'), // dstOffset + ]); + const inst = new Set( + /*indirect=*/ 0x01, + /*inTag=*/ TypeTag.FIELD, + /*value=*/ 0x12345678123456781234567812345678n, + /*dstOffset=*/ 0x3456789a, + ); + + expect(Set.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + it('should correctly set value and tag (uninitialized)', async () => { - await new Set(TypeTag.UINT16, /*value=*/ 1234n, /*offset=*/ 1).execute(machineState, journal); + await new Set(/*indirect=*/ 0, /*inTag=*/ TypeTag.UINT16, /*value=*/ 1234n, /*offset=*/ 1).execute( + machineState, + journal, + ); const actual = machineState.memory.get(1); const tag = machineState.memory.getTag(1); @@ -31,7 +53,10 @@ describe('Memory instructions', () => { it('should correctly set value and tag (overwriting)', async () => { machineState.memory.set(1, new Field(27)); - await new Set(TypeTag.UINT32, /*value=*/ 1234n, /*offset=*/ 1).execute(machineState, journal); + await new Set(/*indirect=*/ 0, /*inTag=*/ TypeTag.UINT32, /*value=*/ 1234n, /*offset=*/ 1).execute( + machineState, + journal, + ); const actual = machineState.memory.get(1); const tag = machineState.memory.getTag(1); @@ -42,6 +67,25 @@ describe('Memory instructions', () => { }); describe('CAST', () => { + it('Should deserialize correctly', () => { + const buf = Buffer.from([ + Cast.opcode, // opcode + 0x01, // indirect + TypeTag.FIELD, // dstTag + ...Buffer.from('12345678', 'hex'), // aOffset + ...Buffer.from('3456789a', 'hex'), // dstOffset + ]); + const inst = new Cast( + /*indirect=*/ 0x01, + /*dstTag=*/ TypeTag.FIELD, + /*aOffset=*/ 0x12345678, + /*dstOffset=*/ 0x3456789a, + ); + + expect(Cast.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + it('Should upcast between integral types', () => { machineState.memory.set(0, new Uint8(20n)); machineState.memory.set(1, new Uint16(65000n)); @@ -50,11 +94,11 @@ describe('Memory instructions', () => { machineState.memory.set(4, new Uint128(1n << 100n)); [ - new Cast(TypeTag.UINT16, /*aOffset=*/ 0, /*dstOffset=*/ 10), - new Cast(TypeTag.UINT32, /*aOffset=*/ 1, /*dstOffset=*/ 11), - new Cast(TypeTag.UINT64, /*aOffset=*/ 2, /*dstOffset=*/ 12), - new Cast(TypeTag.UINT128, /*aOffset=*/ 3, /*dstOffset=*/ 13), - new Cast(TypeTag.UINT128, /*aOffset=*/ 4, /*dstOffset=*/ 14), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT16, /*aOffset=*/ 0, /*dstOffset=*/ 10), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT32, /*aOffset=*/ 1, /*dstOffset=*/ 11), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT64, /*aOffset=*/ 2, /*dstOffset=*/ 12), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT128, /*aOffset=*/ 3, /*dstOffset=*/ 13), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT128, /*aOffset=*/ 4, /*dstOffset=*/ 14), ].forEach(i => i.execute(machineState, journal)); const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 5); @@ -77,11 +121,11 @@ describe('Memory instructions', () => { machineState.memory.set(4, new Uint128((1n << 100n) - 1n)); [ - new Cast(TypeTag.UINT8, /*aOffset=*/ 0, /*dstOffset=*/ 10), - new Cast(TypeTag.UINT8, /*aOffset=*/ 1, /*dstOffset=*/ 11), - new Cast(TypeTag.UINT16, /*aOffset=*/ 2, /*dstOffset=*/ 12), - new Cast(TypeTag.UINT32, /*aOffset=*/ 3, /*dstOffset=*/ 13), - new Cast(TypeTag.UINT64, /*aOffset=*/ 4, /*dstOffset=*/ 14), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT8, /*aOffset=*/ 0, /*dstOffset=*/ 10), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT8, /*aOffset=*/ 1, /*dstOffset=*/ 11), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT16, /*aOffset=*/ 2, /*dstOffset=*/ 12), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT32, /*aOffset=*/ 3, /*dstOffset=*/ 13), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT64, /*aOffset=*/ 4, /*dstOffset=*/ 14), ].forEach(i => i.execute(machineState, journal)); const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 5); @@ -104,11 +148,11 @@ describe('Memory instructions', () => { machineState.memory.set(4, new Uint128(1n << 100n)); [ - new Cast(TypeTag.FIELD, /*aOffset=*/ 0, /*dstOffset=*/ 10), - new Cast(TypeTag.FIELD, /*aOffset=*/ 1, /*dstOffset=*/ 11), - new Cast(TypeTag.FIELD, /*aOffset=*/ 2, /*dstOffset=*/ 12), - new Cast(TypeTag.FIELD, /*aOffset=*/ 3, /*dstOffset=*/ 13), - new Cast(TypeTag.FIELD, /*aOffset=*/ 4, /*dstOffset=*/ 14), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.FIELD, /*aOffset=*/ 0, /*dstOffset=*/ 10), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.FIELD, /*aOffset=*/ 1, /*dstOffset=*/ 11), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.FIELD, /*aOffset=*/ 2, /*dstOffset=*/ 12), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.FIELD, /*aOffset=*/ 3, /*dstOffset=*/ 13), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.FIELD, /*aOffset=*/ 4, /*dstOffset=*/ 14), ].forEach(i => i.execute(machineState, journal)); const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 5); @@ -131,11 +175,11 @@ describe('Memory instructions', () => { machineState.memory.set(4, new Field((1n << 200n) - 1n)); [ - new Cast(TypeTag.UINT8, /*aOffset=*/ 0, /*dstOffset=*/ 10), - new Cast(TypeTag.UINT16, /*aOffset=*/ 1, /*dstOffset=*/ 11), - new Cast(TypeTag.UINT32, /*aOffset=*/ 2, /*dstOffset=*/ 12), - new Cast(TypeTag.UINT64, /*aOffset=*/ 3, /*dstOffset=*/ 13), - new Cast(TypeTag.UINT128, /*aOffset=*/ 4, /*dstOffset=*/ 14), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT8, /*aOffset=*/ 0, /*dstOffset=*/ 10), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT16, /*aOffset=*/ 1, /*dstOffset=*/ 11), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT32, /*aOffset=*/ 2, /*dstOffset=*/ 12), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT64, /*aOffset=*/ 3, /*dstOffset=*/ 13), + new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.UINT128, /*aOffset=*/ 4, /*dstOffset=*/ 14), ].forEach(i => i.execute(machineState, journal)); const actual = machineState.memory.getSlice(/*offset=*/ 10, /*size=*/ 5); @@ -153,7 +197,10 @@ describe('Memory instructions', () => { it('Should cast between field elements', async () => { machineState.memory.set(0, new Field(12345678n)); - await new Cast(TypeTag.FIELD, /*aOffset=*/ 0, /*dstOffset=*/ 1).execute(machineState, journal); + await new Cast(/*indirect=*/ 0, /*dstTag=*/ TypeTag.FIELD, /*aOffset=*/ 0, /*dstOffset=*/ 1).execute( + machineState, + journal, + ); const actual = machineState.memory.get(1); expect(actual).toEqual(new Field(12345678n)); @@ -163,23 +210,36 @@ describe('Memory instructions', () => { }); describe('MOV', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + Mov.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // srcOffset + ...Buffer.from('3456789a', 'hex'), // dstOffset + ]); + const inst = new Mov(/*indirect=*/ 0x01, /*srcOffset=*/ 0x12345678, /*dstOffset=*/ 0x3456789a); + + expect(Mov.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + it('Should move integrals on different memory cells', async () => { - machineState.memory.set(1, new Uint16(27)); - await new Mov(/*offsetA=*/ 1, /*offsetA=*/ 2).execute(machineState, journal); + machineState.memory.set(0, new Uint16(27)); + await new Mov(/*indirect=*/ 0, /*srcOffset=*/ 0, /*dstOffset=*/ 1).execute(machineState, journal); - const actual = machineState.memory.get(2); - const tag = machineState.memory.getTag(2); + const actual = machineState.memory.get(1); + const tag = machineState.memory.getTag(1); expect(actual).toEqual(new Uint16(27n)); expect(tag).toEqual(TypeTag.UINT16); }); it('Should move field elements on different memory cells', async () => { - machineState.memory.set(1, new Field(27)); - await new Mov(/*offsetA=*/ 1, /*offsetA=*/ 2).execute(machineState, journal); + machineState.memory.set(0, new Field(27)); + await new Mov(/*indirect=*/ 0, /*srcOffset=*/ 0, /*dstOffset=*/ 1).execute(machineState, journal); - const actual = machineState.memory.get(2); - const tag = machineState.memory.getTag(2); + const actual = machineState.memory.get(1); + const tag = machineState.memory.getTag(1); expect(actual).toEqual(new Field(27n)); expect(tag).toEqual(TypeTag.FIELD); @@ -187,12 +247,33 @@ describe('Memory instructions', () => { }); describe('CMOV', () => { + it('Should deserialize correctly', () => { + const buf = Buffer.from([ + CMov.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // aOffset + ...Buffer.from('a2345678', 'hex'), // bOffset + ...Buffer.from('b2345678', 'hex'), // condOffset + ...Buffer.from('3456789a', 'hex'), // dstOffset + ]); + const inst = new CMov( + /*indirect=*/ 0x01, + /*aOffset=*/ 0x12345678, + /*bOffset=*/ 0xa2345678, + /*condOffset=*/ 0xb2345678, + /*dstOffset=*/ 0x3456789a, + ); + + expect(CMov.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + it('Should move A if COND is true, on different memory cells (integral condition)', async () => { machineState.memory.set(0, new Uint32(123)); // A machineState.memory.set(1, new Uint16(456)); // B machineState.memory.set(2, new Uint8(2)); // Condition - await new CMov(/*aOffset=*/ 0, /*bOffset=*/ 1, /*condOffset=*/ 2, /*dstOffset=*/ 3).execute( + await new CMov(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*condOffset=*/ 2, /*dstOffset=*/ 3).execute( machineState, journal, ); @@ -208,7 +289,7 @@ describe('Memory instructions', () => { machineState.memory.set(1, new Uint16(456)); // B machineState.memory.set(2, new Uint8(0)); // Condition - await new CMov(/*aOffset=*/ 0, /*bOffset=*/ 1, /*condOffset=*/ 2, /*dstOffset=*/ 3).execute( + await new CMov(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*condOffset=*/ 2, /*dstOffset=*/ 3).execute( machineState, journal, ); @@ -224,7 +305,7 @@ describe('Memory instructions', () => { machineState.memory.set(1, new Uint16(456)); // B machineState.memory.set(2, new Field(1)); // Condition - await new CMov(/*aOffset=*/ 0, /*bOffset=*/ 1, /*condOffset=*/ 2, /*dstOffset=*/ 3).execute( + await new CMov(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*condOffset=*/ 2, /*dstOffset=*/ 3).execute( machineState, journal, ); @@ -240,7 +321,7 @@ describe('Memory instructions', () => { machineState.memory.set(1, new Uint16(456)); // B machineState.memory.set(2, new Field(0)); // Condition - await new CMov(/*aOffset=*/ 0, /*bOffset=*/ 1, /*condOffset=*/ 2, /*dstOffset=*/ 3).execute( + await new CMov(/*indirect=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*condOffset=*/ 2, /*dstOffset=*/ 3).execute( machineState, journal, ); @@ -253,12 +334,34 @@ describe('Memory instructions', () => { }); describe('CALLDATACOPY', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + CalldataCopy.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // cdOffset + ...Buffer.from('23456789', 'hex'), // copysize + ...Buffer.from('3456789a', 'hex'), // dstOffset + ]); + const inst = new CalldataCopy( + /*indirect=*/ 0x01, + /*cdOffset=*/ 0x12345678, + /*copysize=*/ 0x23456789, + /*dstOffset=*/ 0x3456789a, + ); + + expect(CalldataCopy.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + it('Writes nothing if size is 0', async () => { const calldata = [new Fr(1n), new Fr(2n), new Fr(3n)]; machineState = new AvmMachineState(initExecutionEnvironment({ calldata })); machineState.memory.set(0, new Uint16(12)); // Some previous data to be overwritten - await new CalldataCopy(/*cdOffset=*/ 0, /*copySize=*/ 0, /*dstOffset=*/ 0).execute(machineState, journal); + await new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ 0, /*copySize=*/ 0, /*dstOffset=*/ 0).execute( + machineState, + journal, + ); const actual = machineState.memory.get(0); expect(actual).toEqual(new Uint16(12)); @@ -269,7 +372,10 @@ describe('Memory instructions', () => { machineState = new AvmMachineState(initExecutionEnvironment({ calldata })); machineState.memory.set(0, new Uint16(12)); // Some previous data to be overwritten - await new CalldataCopy(/*cdOffset=*/ 0, /*copySize=*/ 3, /*dstOffset=*/ 0).execute(machineState, journal); + await new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ 0, /*copySize=*/ 3, /*dstOffset=*/ 0).execute( + machineState, + journal, + ); const actual = machineState.memory.getSlice(/*offset=*/ 0, /*size=*/ 3); expect(actual).toEqual([new Field(1), new Field(2), new Field(3)]); @@ -280,7 +386,10 @@ describe('Memory instructions', () => { machineState = new AvmMachineState(initExecutionEnvironment({ calldata })); machineState.memory.set(0, new Uint16(12)); // Some previous data to be overwritten - await new CalldataCopy(/*cdOffset=*/ 1, /*copySize=*/ 2, /*dstOffset=*/ 0).execute(machineState, journal); + await new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ 1, /*copySize=*/ 2, /*dstOffset=*/ 0).execute( + machineState, + journal, + ); const actual = machineState.memory.getSlice(/*offset=*/ 0, /*size=*/ 2); expect(actual).toEqual([new Field(2), new Field(3)]); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts index 2524684dc02..31b05c95969 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/memory.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/memory.ts @@ -1,13 +1,23 @@ import { AvmMachineState } from '../avm_machine_state.js'; import { Field, TaggedMemory, TypeTag } from '../avm_memory_types.js'; import { AvmJournal } from '../journal/index.js'; +import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; import { Instruction } from './instruction.js'; +import { TwoOperandInstruction } from './instruction_impl.js'; export class Set extends Instruction { - static type: string = 'SET'; - static numberOfOperands = 3; - - constructor(private inTag: TypeTag, private value: bigint, private dstOffset: number) { + static readonly type: string = 'SET'; + static readonly opcode: Opcode = Opcode.SET; + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat: OperandType[] = [ + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT128, + OperandType.UINT32, + ]; + + constructor(private indirect: number, private inTag: number, private value: bigint, private dstOffset: number) { super(); } @@ -20,69 +30,99 @@ export class Set extends Instruction { } } -export class Cast extends Instruction { - static type: string = 'CAST'; - static numberOfOperands = 3; - - constructor(private dstTag: TypeTag, private aOffset: number, private dstOffset: number) { +export class CMov extends Instruction { + static readonly type: string = 'CMOV'; + static readonly opcode: Opcode = Opcode.CMOV; + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat: OperandType[] = [ + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT32, + OperandType.UINT32, + OperandType.UINT32, + OperandType.UINT32, + ]; + + constructor( + private indirect: number, + private aOffset: number, + private bOffset: number, + private condOffset: number, + private dstOffset: number, + ) { super(); } async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { const a = machineState.memory.get(this.aOffset); + const b = machineState.memory.get(this.bOffset); + const cond = machineState.memory.get(this.condOffset); - // TODO: consider not using toBigInt() - const casted = - this.dstTag == TypeTag.FIELD ? new Field(a.toBigInt()) : TaggedMemory.integralFromTag(a.toBigInt(), this.dstTag); - - machineState.memory.set(this.dstOffset, casted); + // TODO: reconsider toBigInt() here + machineState.memory.set(this.dstOffset, cond.toBigInt() > 0 ? a : b); this.incrementPc(machineState); } } -export class Mov extends Instruction { - static type: string = 'MOV'; - static numberOfOperands = 2; +export class Cast extends TwoOperandInstruction { + static readonly type: string = 'CAST'; + static readonly opcode = Opcode.CAST; - constructor(private aOffset: number, private dstOffset: number) { - super(); + constructor(indirect: number, dstTag: number, aOffset: number, dstOffset: number) { + super(indirect, dstTag, aOffset, dstOffset); } async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { const a = machineState.memory.get(this.aOffset); - machineState.memory.set(this.dstOffset, a); + // TODO: consider not using toBigInt() + const casted = + this.inTag == TypeTag.FIELD ? new Field(a.toBigInt()) : TaggedMemory.integralFromTag(a.toBigInt(), this.inTag); + + machineState.memory.set(this.dstOffset, casted); this.incrementPc(machineState); } } -export class CMov extends Instruction { - static type: string = 'CMOV'; - static numberOfOperands = 4; - - constructor(private aOffset: number, private bOffset: number, private condOffset: number, private dstOffset: number) { +export class Mov extends Instruction { + static readonly type: string = 'MOV'; + static readonly opcode: Opcode = Opcode.MOV; + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat: OperandType[] = [ + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT32, + OperandType.UINT32, + ]; + + constructor(private indirect: number, private srcOffset: number, private dstOffset: number) { super(); } async execute(machineState: AvmMachineState, _journal: AvmJournal): Promise { - const a = machineState.memory.get(this.aOffset); - const b = machineState.memory.get(this.bOffset); - const cond = machineState.memory.get(this.condOffset); + const a = machineState.memory.get(this.srcOffset); - // TODO: reconsider toBigInt() here - machineState.memory.set(this.dstOffset, cond.toBigInt() > 0 ? a : b); + machineState.memory.set(this.dstOffset, a); this.incrementPc(machineState); } } export class CalldataCopy extends Instruction { - static type: string = 'CALLDATACOPY'; - static numberOfOperands = 3; - - constructor(private cdOffset: number, private copySize: number, private dstOffset: number) { + static readonly type: string = 'CALLDATACOPY'; + static readonly opcode: Opcode = Opcode.CALLDATACOPY; + // Informs (de)serialization. See Instruction.deserialize. + static readonly wireFormat: OperandType[] = [ + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT32, + OperandType.UINT32, + OperandType.UINT32, + ]; + + constructor(private indirect: number, private cdOffset: number, private copySize: number, private dstOffset: number) { super(); } diff --git a/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts b/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts deleted file mode 100644 index 97bdfa3181d..00000000000 --- a/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * All AVM opcodes. (Keep in sync with cpp counterpart code AvmMini_opcode.hpp). - * Source: https://yp-aztec.netlify.app/docs/public-vm/instruction-set - */ -export enum Opcode { - // Compute - // Compute - Arithmetic - ADD = 0x00, - SUB = 0x01, - MUL = 0x02, - DIV = 0x03, - // Compute - Comparators - EQ = 0x04, - LT = 0x05, - LTE = 0x06, - // Compute - Bitwise - AND = 0x07, - OR = 0x08, - XOR = 0x09, - NOT = 0x0a, - SHL = 0x0b, - SHR = 0x0c, - // Compute - Type Conversions - CAST = 0x0d, - - // Execution Environment - ADDRESS = 0x0e, - STORAGEADDRESS = 0x0f, - ORIGIN = 0x10, - SENDER = 0x11, - PORTAL = 0x12, - FEEPERL1GAS = 0x13, - FEEPERL2GAS = 0x14, - FEEPERDAGAS = 0x15, - CONTRACTCALLDEPTH = 0x16, - // Execution Environment - Globals - CHAINID = 0x17, - VERSION = 0x18, - BLOCKNUMBER = 0x19, - TIMESTAMP = 0x1a, - COINBASE = 0x1b, - BLOCKL1GASLIMIT = 0x1c, - BLOCKL2GASLIMIT = 0x1d, - BLOCKDAGASLIMIT = 0x1e, - // Execution Environment - Calldata - CALLDATACOPY = 0x1f, - - // Machine State - // Machine State - Gas - L1GASLEFT = 0x20, - L2GASLEFT = 0x21, - DAGASLEFT = 0x22, - // Machine State - Internal Control Flow - JUMP = 0x23, - JUMPI = 0x24, - INTERNALCALL = 0x25, - INTERNALRETURN = 0x26, - // Machine State - Memory - SET = 0x27, - MOV = 0x28, - CMOV = 0x29, - - // World State - BLOCKHEADERBYNUMBER = 0x2a, - SLOAD = 0x2b, // Public Storage - SSTORE = 0x2c, // Public Storage - READL1TOL2MSG = 0x2d, // Messages - SENDL2TOL1MSG = 0x2e, // Messages - EMITNOTEHASH = 0x2f, // Notes & Nullifiers - EMITNULLIFIER = 0x30, // Notes & Nullifiers - - // Accrued Substate - EMITUNENCRYPTEDLOG = 0x31, - - // Control Flow - Contract Calls - CALL = 0x32, - STATICCALL = 0x33, - RETURN = 0x34, - REVERT = 0x35, - - // Gadgets - KECCAK = 0x36, - POSEIDON = 0x37, - - // Add new opcodes before this - TOTAL_OPCODES_NUMBER, -} diff --git a/yarn-project/acir-simulator/src/avm/opcodes/storage.test.ts b/yarn-project/acir-simulator/src/avm/opcodes/storage.test.ts index e62b0abc561..8f704b036a3 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/storage.test.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/storage.test.ts @@ -21,48 +21,79 @@ describe('Storage Instructions', () => { machineState = new AvmMachineState(executionEnvironment); }); - it('Sstore should Write into storage', async () => { - const a = new Field(1n); - const b = new Field(2n); - - machineState.memory.set(0, a); - machineState.memory.set(1, b); - - await new SStore(0, 1).execute(machineState, journal); - - expect(journal.writeStorage).toBeCalledWith(address, new Fr(a.toBigInt()), new Fr(b.toBigInt())); + describe('SSTORE', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + SStore.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // srcOffset + ...Buffer.from('a2345678', 'hex'), // slotOffset + ]); + const inst = new SStore(/*indirect=*/ 0x01, /*srcOffset=*/ 0x12345678, /*slotOffset=*/ 0xa2345678); + + expect(SStore.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); + + it('Sstore should Write into storage', async () => { + const a = new Field(1n); + const b = new Field(2n); + + machineState.memory.set(0, a); + machineState.memory.set(1, b); + + await new SStore(/*indirect=*/ 0, /*srcOffset=*/ 0, /*slotOffset=*/ 1).execute(machineState, journal); + + expect(journal.writeStorage).toHaveBeenCalledWith(address, new Fr(a.toBigInt()), new Fr(b.toBigInt())); + }); + + it('Should not be able to write to storage in a static call', async () => { + const executionEnvironment = initExecutionEnvironment({ isStaticCall: true }); + machineState = new AvmMachineState(executionEnvironment); + + const a = new Field(1n); + const b = new Field(2n); + + machineState.memory.set(0, a); + machineState.memory.set(1, b); + + const instruction = () => + new SStore(/*indirect=*/ 0, /*srcOffset=*/ 0, /*slotOffset=*/ 1).execute(machineState, journal); + await expect(instruction()).rejects.toThrow(StaticCallStorageAlterError); + }); }); - it('Should not be able to write to storage in a static call', async () => { - const executionEnvironment = initExecutionEnvironment({ isStaticCall: true }); - machineState = new AvmMachineState(executionEnvironment); + describe('SLOAD', () => { + it('Should (de)serialize correctly', () => { + const buf = Buffer.from([ + SLoad.opcode, // opcode + 0x01, // indirect + ...Buffer.from('12345678', 'hex'), // slotOffset + ...Buffer.from('a2345678', 'hex'), // dstOffset + ]); + const inst = new SLoad(/*indirect=*/ 0x01, /*slotOffset=*/ 0x12345678, /*dstOffset=*/ 0xa2345678); - const a = new Field(1n); - const b = new Field(2n); - - machineState.memory.set(0, a); - machineState.memory.set(1, b); - - const instruction = () => new SStore(0, 1).execute(machineState, journal); - await expect(instruction()).rejects.toThrowError(StaticCallStorageAlterError); - }); + expect(SLoad.deserialize(buf)).toEqual(inst); + expect(inst.serialize()).toEqual(buf); + }); - it('Sload should Read into storage', async () => { - // Mock response - const expectedResult = new Fr(1n); - journal.readStorage.mockReturnValueOnce(Promise.resolve(expectedResult)); + it('Sload should Read into storage', async () => { + // Mock response + const expectedResult = new Fr(1n); + journal.readStorage.mockReturnValueOnce(Promise.resolve(expectedResult)); - const a = new Field(1n); - const b = new Field(2n); + const a = new Field(1n); + const b = new Field(2n); - machineState.memory.set(0, a); - machineState.memory.set(1, b); + machineState.memory.set(0, a); + machineState.memory.set(1, b); - await new SLoad(0, 1).execute(machineState, journal); + await new SLoad(/*indirect=*/ 0, /*slotOffset=*/ 0, /*dstOffset=*/ 1).execute(machineState, journal); - expect(journal.readStorage).toBeCalledWith(address, new Fr(a.toBigInt())); + expect(journal.readStorage).toHaveBeenCalledWith(address, new Fr(a.toBigInt())); - const actual = machineState.memory.get(1); - expect(actual).toEqual(new Field(expectedResult)); + const actual = machineState.memory.get(1); + expect(actual).toEqual(new Field(expectedResult)); + }); }); }); diff --git a/yarn-project/acir-simulator/src/avm/opcodes/storage.ts b/yarn-project/acir-simulator/src/avm/opcodes/storage.ts index c226522b3db..5cd9bee9ceb 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/storage.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/storage.ts @@ -2,25 +2,39 @@ import { Fr } from '@aztec/foundation/fields'; import { AvmMachineState } from '../avm_machine_state.js'; import { Field } from '../avm_memory_types.js'; -import { AvmInterpreterError } from '../interpreter/interpreter.js'; import { AvmJournal } from '../journal/journal.js'; -import { Instruction } from './instruction.js'; +import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; +import { Instruction, InstructionExecutionError } from './instruction.js'; -export class SStore extends Instruction { - static type: string = 'SSTORE'; - static numberOfOperands = 2; +abstract class BaseStorageInstruction extends Instruction { + // Informs (de)serialization. See Instruction.deserialize. + public static readonly wireFormat: OperandType[] = [ + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT32, + OperandType.UINT32, + ]; - constructor(private slotOffset: number, private dataOffset: number) { + constructor(protected indirect: number, protected aOffset: number, protected bOffset: number) { super(); } +} + +export class SStore extends BaseStorageInstruction { + static readonly type: string = 'SSTORE'; + static readonly opcode = Opcode.SSTORE; + + constructor(indirect: number, srcOffset: number, slotOffset: number) { + super(indirect, srcOffset, slotOffset); + } async execute(machineState: AvmMachineState, journal: AvmJournal): Promise { if (machineState.executionEnvironment.isStaticCall) { throw new StaticCallStorageAlterError(); } - const slot = machineState.memory.get(this.slotOffset); - const data = machineState.memory.get(this.dataOffset); + const slot = machineState.memory.get(this.aOffset); + const data = machineState.memory.get(this.bOffset); journal.writeStorage( machineState.executionEnvironment.storageAddress, @@ -32,23 +46,23 @@ export class SStore extends Instruction { } } -export class SLoad extends Instruction { - static type: string = 'SLOAD'; - static numberOfOperands = 2; +export class SLoad extends BaseStorageInstruction { + static readonly type: string = 'SLOAD'; + static readonly opcode = Opcode.SLOAD; - constructor(private slotOffset: number, private dstOffset: number) { - super(); + constructor(indirect: number, slotOffset: number, dstOffset: number) { + super(indirect, slotOffset, dstOffset); } async execute(machineState: AvmMachineState, journal: AvmJournal): Promise { - const slot = machineState.memory.get(this.slotOffset); + const slot = machineState.memory.get(this.aOffset); const data: Fr = await journal.readStorage( machineState.executionEnvironment.storageAddress, new Fr(slot.toBigInt()), ); - machineState.memory.set(this.dstOffset, new Field(data)); + machineState.memory.set(this.bOffset, new Field(data)); this.incrementPc(machineState); } @@ -57,7 +71,7 @@ export class SLoad extends Instruction { /** * Error is thrown when a static call attempts to alter storage */ -export class StaticCallStorageAlterError extends AvmInterpreterError { +export class StaticCallStorageAlterError extends InstructionExecutionError { constructor() { super('Static calls cannot alter storage'); this.name = 'StaticCallStorageAlterError'; diff --git a/yarn-project/acir-simulator/src/avm/serialization/buffer_cursor.ts b/yarn-project/acir-simulator/src/avm/serialization/buffer_cursor.ts new file mode 100644 index 00000000000..237d9ae24f1 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/serialization/buffer_cursor.ts @@ -0,0 +1,109 @@ +import { strict as assert } from 'assert'; + +/* + * A Buffer-like class that automatically advances the position. + */ +export class BufferCursor { + constructor(private _buffer: Buffer, private _position: number = 0) {} + + public position(): number { + return this._position; + } + + public eof(): boolean { + return this._position === this._buffer.length; + } + + public bufferAtPosition(): Buffer { + return this._buffer.subarray(this._position); + } + + public advance(n: number): void { + this._position += n; + assert(n < this._buffer.length); + } + + public readUint8(): number { + const ret = this._buffer.readUint8(this._position); + this._position += 1; + return ret; + } + + public readUint16LE(): number { + const ret = this._buffer.readUint16LE(this._position); + this._position += 2; + return ret; + } + + public readUint16BE(): number { + const ret = this._buffer.readUint16BE(this._position); + this._position += 2; + return ret; + } + + public readUint32LE(): number { + const ret = this._buffer.readUint32LE(this._position); + this._position += 4; + return ret; + } + + public readUint32BE(): number { + const ret = this._buffer.readUint32BE(this._position); + this._position += 4; + return ret; + } + + public readBigInt64LE(): bigint { + const ret = this._buffer.readBigInt64LE(this._position); + this._position += 8; + return ret; + } + + public readBigInt64BE(): bigint { + const ret = this._buffer.readBigInt64BE(this._position); + this._position += 8; + return ret; + } + + public writeUint8(v: number) { + const ret = this._buffer.writeUint8(v, this._position); + this._position += 1; + return ret; + } + + public writeUint16LE(v: number) { + const ret = this._buffer.writeUint16LE(v, this._position); + this._position += 2; + return ret; + } + + public writeUint16BE(v: number) { + const ret = this._buffer.writeUint16BE(v, this._position); + this._position += 2; + return ret; + } + + public writeUint32LE(v: number) { + const ret = this._buffer.writeUint32LE(v, this._position); + this._position += 4; + return ret; + } + + public writeUint32BE(v: number) { + const ret = this._buffer.writeUint32BE(v, this._position); + this._position += 4; + return ret; + } + + public writeBigInt64LE(v: bigint) { + const ret = this._buffer.writeBigInt64LE(v, this._position); + this._position += 8; + return ret; + } + + public writeBigInt64BE(v: bigint) { + const ret = this._buffer.writeBigInt64BE(v, this._position); + this._position += 8; + return ret; + } +} diff --git a/yarn-project/acir-simulator/src/avm/serialization/bytecode_serialization.test.ts b/yarn-project/acir-simulator/src/avm/serialization/bytecode_serialization.test.ts new file mode 100644 index 00000000000..adca61ef609 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/serialization/bytecode_serialization.test.ts @@ -0,0 +1,98 @@ +import { strict as assert } from 'assert'; + +import { Add, Address, Sub } from '../opcodes/index.js'; +import { BufferCursor } from './buffer_cursor.js'; +import { InstructionSet, decodeFromBytecode, encodeToBytecode } from './bytecode_serialization.js'; +import { Opcode } from './instruction_serialization.js'; + +class InstA { + constructor(private n: number) {} + static readonly opcode: number = 1; + + // Expects opcode. + public static deserialize(buf: BufferCursor): InstA { + const opcode: number = buf.readUint8(); + assert(opcode == InstA.opcode); + return new InstA(buf.readUint16BE()); + } + + // Includes opcode. + public serialize(): Buffer { + const buf = Buffer.alloc(1 + 2); + buf.writeUint8(InstA.opcode); + buf.writeUint16BE(this.n, 1); + return buf; + } +} + +class InstB { + constructor(private n: bigint) {} + static readonly opcode: number = 2; + + // Expects opcode. + public static deserialize(buf: BufferCursor): InstB { + const opcode: number = buf.readUint8(); + assert(opcode == InstB.opcode); + return new InstB(buf.readBigInt64BE()); + } + + // Includes opcode. + public serialize(): Buffer { + const buf = Buffer.alloc(1 + 8); + buf.writeUint8(InstB.opcode); + buf.writeBigInt64BE(this.n, 1); + return buf; + } +} + +describe('Bytecode Serialization', () => { + it('Should deserialize using instruction set', () => { + const instructionSet: InstructionSet = new Map([ + [InstA.opcode, InstA], + [InstB.opcode, InstB], + ]); + const a = new InstA(0x1234); + const b = new InstB(0x5678n); + const bytecode = Buffer.concat([a.serialize(), b.serialize()]); + + const actual = decodeFromBytecode(bytecode, instructionSet); + + expect(actual).toEqual([a, b]); + }); + + it('Should serialize using instruction.serialize()', () => { + const a = new InstA(1234); + const b = new InstB(5678n); + + const actual = encodeToBytecode([a, b]); + + const expected = Buffer.concat([a.serialize(), b.serialize()]); + expect(actual).toEqual(expected); + }); + + it('Should deserialize real instructions', () => { + const instructions = [ + new Add(/*indirect=*/ 0, /*inTag=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2), + new Sub(/*indirect=*/ 0, /*inTag=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2), + new Address(/*indirect=*/ 0, /*dstOffset=*/ 1), + ]; + const bytecode = Buffer.concat(instructions.map(i => i.serialize())); + + const actual = decodeFromBytecode(bytecode); + + expect(actual).toEqual(instructions); + }); + + it('Should serialize real instructions', () => { + const instructions = [ + new Add(/*indirect=*/ 0, /*inTag=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2), + new Sub(/*indirect=*/ 0, /*inTag=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2), + new Address(/*indirect=*/ 0, /*dstOffset=*/ 1), + ]; + + const actual = encodeToBytecode(instructions); + + const expected = Buffer.concat(instructions.map(i => i.serialize())); + expect(actual).toEqual(expected); + }); +}); diff --git a/yarn-project/acir-simulator/src/avm/serialization/bytecode_serialization.ts b/yarn-project/acir-simulator/src/avm/serialization/bytecode_serialization.ts new file mode 100644 index 00000000000..b0285018980 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/serialization/bytecode_serialization.ts @@ -0,0 +1,165 @@ +import { + Add, + Address, + And, + BlockNumber, + CMov, // Call, + CalldataCopy, + Cast, + ChainId, + Div, + EmitNoteHash, + EmitNullifier, + EmitUnencryptedLog, + Eq, + FeePerDAGas, + FeePerL1Gas, + FeePerL2Gas, + InternalCall, + InternalReturn, + Jump, + JumpI, + Lt, + Lte, + Mov, + Mul, + Not, + Or, + Origin, + Portal, + Return, + Revert, + SLoad, + SStore, + SendL2ToL1Message, + Sender, + Set, + Shl, + Shr, // StaticCall, + StorageAddress, + Sub, + Timestamp, + Version, + Xor, +} from '../opcodes/index.js'; +import { Instruction } from '../opcodes/instruction.js'; +import { BufferCursor } from './buffer_cursor.js'; +import { Opcode } from './instruction_serialization.js'; + +interface DeserializableInstruction { + deserialize(buf: BufferCursor | Buffer): Instruction; + opcode: Opcode; +} + +export type InstructionSet = Map; +const INSTRUCTION_SET: InstructionSet = new Map( + [ + [Add.opcode, Add], + [Sub.opcode, Sub], + [Sub.opcode, Sub], + [Mul.opcode, Mul], + [Div.opcode, Div], + [Eq.opcode, Eq], + [Lt.opcode, Lt], + [Lte.opcode, Lte], + [And.opcode, And], + [Or.opcode, Or], + [Xor.opcode, Xor], + [Not.opcode, Not], + [Shl.opcode, Shl], + [Shr.opcode, Shr], + [Cast.opcode, Cast], + [Address.opcode, Address], + [StorageAddress.opcode, StorageAddress], + [Origin.opcode, Origin], + [Sender.opcode, Sender], + [Portal.opcode, Portal], + [FeePerL1Gas.opcode, FeePerL1Gas], + [FeePerL2Gas.opcode, FeePerL2Gas], + [FeePerDAGas.opcode, FeePerDAGas], + // [Contractcalldepth.opcode, Contractcalldepth], + // Execution Environment - Globals + [ChainId.opcode, ChainId], + [Version.opcode, Version], + [BlockNumber.opcode, BlockNumber], + [Timestamp.opcode, Timestamp], + // [Coinbase.opcode, Coinbase], + // [Blockl1gaslimit.opcode, Blockl1gaslimit], + // [Blockl2gaslimit.opcode, Blockl2gaslimit], + // [Blockdagaslimit.opcode, Blockdagaslimit], + // // Execution Environment - Calldata + [CalldataCopy.opcode, CalldataCopy], + + // //// Machine State + // // Machine State - Gas + // //[L1gasleft.opcode, L1gasleft], + // //[L2gasleft.opcode, L2gasleft], + // //[Dagasleft.opcode, Dagasleft], + // //// Machine State - Internal Control Flow + [Jump.opcode, Jump], + [JumpI.opcode, JumpI], + [InternalCall.opcode, InternalCall], + [InternalReturn.opcode, InternalReturn], + [Set.opcode, Set], + [Mov.opcode, Mov], + [CMov.opcode, CMov], + + // //// World State + // //[Blockheaderbynumber.opcode, Blockheaderbynumber], + [SLoad.opcode, SLoad], // Public Storage + [SStore.opcode, SStore], // Public Storage + // //[Readl1tol2msg.opcode, Readl1tol2msg], // Messages + [SendL2ToL1Message.opcode, SendL2ToL1Message], // Messages + [EmitNoteHash.opcode, EmitNoteHash], // Notes & Nullifiers + [EmitNullifier.opcode, EmitNullifier], // Notes & Nullifiers + + // //// Accrued Substate + [EmitUnencryptedLog.opcode, EmitUnencryptedLog], + + // //// Control Flow - Contract Calls + // [Call.opcode, Call], + // [StaticCall.opcode, StaticCall], + [Return.opcode, Return], + [Revert.opcode, Revert], + + // //// Gadgets + // //[Keccak.opcode, Keccak], + // //[Poseidon.opcode, Poseidon], + ], //), +); + +interface Serializable { + serialize(): Buffer; +} + +/** + * Serializes an array of instructions to bytecode. + */ +export function encodeToBytecode(instructions: Serializable[]): Buffer { + return Buffer.concat(instructions.map(i => i.serialize())); +} + +/** + * Convert a buffer of bytecode into an array of instructions. + * @param bytecode Buffer of bytecode. + * @param instructionSet Optional {@code InstructionSet} to be used for deserialization. + * @returns Bytecode decoded into an ordered array of Instructions + */ +export function decodeFromBytecode(bytecode: Buffer, instructionSet: InstructionSet = INSTRUCTION_SET): Instruction[] { + const instructions: Instruction[] = []; + const cursor = new BufferCursor(bytecode); + + while (!cursor.eof()) { + const opcode: Opcode = cursor.bufferAtPosition().readUint8(); // peek. + const instructionDeserializerOrUndef = instructionSet.get(opcode); + if (instructionDeserializerOrUndef === undefined) { + throw new Error(`Opcode 0x${opcode.toString(16)} not implemented`); + } + + const instructionDeserializer: DeserializableInstruction = instructionDeserializerOrUndef; + const i: Instruction = instructionDeserializer.deserialize(cursor); + instructions.push(i); + } + + return instructions; +} diff --git a/yarn-project/acir-simulator/src/avm/serialization/instruction_serialization.test.ts b/yarn-project/acir-simulator/src/avm/serialization/instruction_serialization.test.ts new file mode 100644 index 00000000000..3f7d8905c98 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/serialization/instruction_serialization.test.ts @@ -0,0 +1,70 @@ +import { BufferCursor } from './buffer_cursor.js'; +import { OperandType, deserialize, serialize } from './instruction_serialization.js'; + +class InstA { + constructor(private a: number, private b: number, private c: number, private d: bigint, private e: bigint) {} + + static readonly opcode: number = 1; + static readonly wireFormat: OperandType[] = [ + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT16, + OperandType.UINT32, + OperandType.UINT64, + OperandType.UINT128, + ]; +} + +describe('Instruction Serialization', () => { + it('Should serialize all types from OperandType[]', () => { + const instance = new InstA(0x12, 0x1234, 0x12345678, 0x1234567887654321n, 0x1234567887654321abcdef0000fedcban); + const actual: Buffer = serialize(InstA.wireFormat, instance); + + expect(actual).toEqual( + Buffer.from( + [ + // opcode + '01', + // a + '12', + // b + '1234', + // c + '12345678', + // d + '1234567887654321', + // e + '1234567887654321ABCDEF0000FEDCBA', + ].join(''), + 'hex', + ), + ); + }); + + it('Should deserialize all types from OperandType[]', () => { + const buffer = Buffer.from( + [ + // opcode + '01', + // a + '12', + // b + '1234', + // c + '12345678', + // d + '1234567887654321', + // e + '1234567887654321ABCDEF0000FEDCBA', + ].join(''), + 'hex', + ); + + const deserializedParams = deserialize(new BufferCursor(buffer), InstA.wireFormat); + const params = deserializedParams.slice(1) as ConstructorParameters; // Drop opcode. + + const actual = new InstA(...params); + const expected = new InstA(0x12, 0x1234, 0x12345678, 0x1234567887654321n, 0x1234567887654321abcdef0000fedcban); + expect(actual).toEqual(expected); + }); +}); diff --git a/yarn-project/acir-simulator/src/avm/serialization/instruction_serialization.ts b/yarn-project/acir-simulator/src/avm/serialization/instruction_serialization.ts new file mode 100644 index 00000000000..0cba3caef37 --- /dev/null +++ b/yarn-project/acir-simulator/src/avm/serialization/instruction_serialization.ts @@ -0,0 +1,162 @@ +import { strict as assert } from 'assert'; + +import { BufferCursor } from './buffer_cursor.js'; + +/** + * All AVM opcodes. (Keep in sync with cpp counterpart code AvmMini_opcode.hpp). + * Source: https://yp-aztec.netlify.app/docs/public-vm/instruction-set + */ +export enum Opcode { + ADD, + SUB, + MUL, + DIV, + EQ, + LT, + LTE, + AND, + OR, + XOR, + NOT, + SHL, + SHR, + CAST, + ADDRESS, + STORAGEADDRESS, + ORIGIN, + SENDER, + PORTAL, + FEEPERL1GAS, + FEEPERL2GAS, + FEEPERDAGAS, + CONTRACTCALLDEPTH, + CHAINID, + VERSION, + BLOCKNUMBER, + TIMESTAMP, + COINBASE, + BLOCKL1GASLIMIT, + BLOCKL2GASLIMIT, + BLOCKDAGASLIMIT, + CALLDATACOPY, + L1GASLEFT, + L2GASLEFT, + DAGASLEFT, + JUMP, + JUMPI, + INTERNALCALL, + INTERNALRETURN, + SET, + MOV, + CMOV, + BLOCKHEADERBYNUMBER, + SLOAD, // Public Storage + SSTORE, // Public Storage + READL1TOL2MSG, // Messages + SENDL2TOL1MSG, // Messages + EMITNOTEHASH, // Notes & Nullifiers + EMITNULLIFIER, // Notes & Nullifiers + EMITUNENCRYPTEDLOG, + CALL, + STATICCALL, + RETURN, + REVERT, + KECCAK, + POSEIDON, + // Add new opcodes before this + TOTAL_OPCODES_NUMBER, +} + +// Possible types for an instruction's operand in its wire format. +export enum OperandType { + UINT8, + UINT16, + UINT32, + UINT64, + UINT128, +} + +type OperandNativeType = number | bigint; +type OperandWriter = (value: any) => void; + +// Specifies how to read and write each operand type. +const OPERAND_SPEC = new Map OperandNativeType, OperandWriter]>([ + [OperandType.UINT8, [1, Buffer.prototype.readUint8, Buffer.prototype.writeUint8]], + [OperandType.UINT16, [2, Buffer.prototype.readUint16BE, Buffer.prototype.writeUint16BE]], + [OperandType.UINT32, [4, Buffer.prototype.readUint32BE, Buffer.prototype.writeUint32BE]], + [OperandType.UINT64, [8, Buffer.prototype.readBigInt64BE, Buffer.prototype.writeBigInt64BE]], + [OperandType.UINT128, [16, readBigInt128BE, writeBigInt128BE]], +]); + +function readBigInt128BE(this: Buffer): bigint { + const totalBytes = 16; + let ret: bigint = 0n; + for (let i = 0; i < totalBytes; ++i) { + ret <<= 8n; + ret |= BigInt(this.readUint8(i)); + } + return ret; +} + +function writeBigInt128BE(this: Buffer, value: bigint): void { + const totalBytes = 16; + for (let offset = totalBytes - 1; offset >= 0; --offset) { + this.writeUint8(Number(value & 0xffn), offset); + value >>= 8n; + } +} + +/** + * Reads an array of operands from a buffer. + * @param cursor Buffer to read from. Might be longer than needed. + * @param operands Specification of the operand types. + * @returns An array as big as {@code operands}, with the converted TS values. + */ +export function deserialize(cursor: BufferCursor | Buffer, operands: OperandType[]): (number | bigint)[] { + const argValues = []; + if (cursor instanceof Buffer) { + cursor = new BufferCursor(cursor); + } + + for (const op of operands) { + const opType = op; + const [sizeBytes, reader, _writer] = OPERAND_SPEC.get(opType)!; + argValues.push(reader.call(cursor.bufferAtPosition())); + cursor.advance(sizeBytes); + } + + return argValues; +} + +/** + * Serializes a class using the specified operand types. + * More specifically, this serializes {@code [cls.constructor.opcode, ...Object.values(cls)]}. + * Observe in particular that: + * (1) the first operand type specified must correspond to the opcode; + * (2) the rest of the operand types must be specified in the order returned by {@code Object.values()}. + * @param operands Type specification for the values to be serialized. + * @param cls The class to be serialized. + * @returns + */ +export function serialize(operands: OperandType[], cls: any): Buffer { + const chunks: Buffer[] = []; + + // TODO: infer opcode not in this loop + assert(cls.constructor.opcode !== undefined && cls.constructor.opcode !== null); + const rawClassValues: any[] = [cls.constructor.opcode, ...Object.values(cls)]; + assert( + rawClassValues.length === operands.length, + `Got ${rawClassValues.length} values but only ${operands.length} serialization operands are specified!`, + ); + const classValues = rawClassValues as OperandNativeType[]; + + for (let i = 0; i < operands.length; i++) { + const opType = operands[i]; + const [sizeBytes, _reader, writer] = OPERAND_SPEC.get(opType)!; + const buf = Buffer.alloc(sizeBytes); + writer.call(buf, classValues[i]); + chunks.push(buf); + } + + return Buffer.concat(chunks); +} diff --git a/yarn-project/acir-simulator/src/client/client_execution_context.ts b/yarn-project/acir-simulator/src/client/client_execution_context.ts index f3d684f4ef0..27afde14d26 100644 --- a/yarn-project/acir-simulator/src/client/client_execution_context.ts +++ b/yarn-project/acir-simulator/src/client/client_execution_context.ts @@ -17,13 +17,7 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr, Point } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; -import { - NoteData, - toACVMCallContext, - toACVMContractDeploymentData, - toACVMHeader, - toACVMWitness, -} from '../acvm/index.js'; +import { NoteData, toACVMWitness } from '../acvm/index.js'; import { PackedArgsCache } from '../common/packed_args_cache.js'; import { DBOracle } from './db_oracle.js'; import { ExecutionNoteCache } from './execution_note_cache.js'; @@ -96,9 +90,9 @@ export class ClientExecutionContext extends ViewDataOracle { } const fields = [ - ...toACVMCallContext(this.callContext), - ...toACVMHeader(this.historicalHeader), - ...toACVMContractDeploymentData(contractDeploymentData), + ...this.callContext.toFields(), + ...this.historicalHeader.toFields(), + ...contractDeploymentData.toFields(), this.txContext.chainId, this.txContext.version, diff --git a/yarn-project/acir-simulator/src/client/private_execution.test.ts b/yarn-project/acir-simulator/src/client/private_execution.test.ts index 0587f3ec4dd..c199faa8cb5 100644 --- a/yarn-project/acir-simulator/src/client/private_execution.test.ts +++ b/yarn-project/acir-simulator/src/client/private_execution.test.ts @@ -56,7 +56,7 @@ import { jest } from '@jest/globals'; import { MockProxy, mock } from 'jest-mock-extended'; import { getFunctionSelector } from 'viem'; -import { KeyPair } from '../acvm/index.js'; +import { KeyPair, MessageLoadOracleInputs } from '../acvm/index.js'; import { buildL1ToL2Message } from '../test/utils.js'; import { computeSlotForMapping } from '../utils.js'; import { DBOracle } from './db_oracle.js'; @@ -277,11 +277,18 @@ describe('Private Execution test suite', () => { oracle.getFunctionArtifactByName.mockImplementation((_, functionName: string) => Promise.resolve(getFunctionArtifact(StatefulTestContractArtifact, functionName)), ); + + oracle.getFunctionArtifact.mockImplementation((_, selector: FunctionSelector) => + Promise.resolve(getFunctionArtifact(StatefulTestContractArtifact, selector)), + ); + + oracle.getPortalContractAddress.mockResolvedValue(EthAddress.ZERO); }); it('should have a constructor with arguments that inserts notes', async () => { const artifact = getFunctionArtifact(StatefulTestContractArtifact, 'constructor'); - const result = await runSimulator({ args: [owner, 140], artifact }); + const topLevelResult = await runSimulator({ args: [owner, 140], artifact }); + const result = topLevelResult.nestedExecutions[0]; expect(result.newNotes).toHaveLength(1); const newNote = result.newNotes[0]; @@ -544,11 +551,7 @@ describe('Private Execution test suite', () => { const mockOracles = async () => { const tree = await insertLeaves([messageKey ?? preimage.hash()], 'l1ToL2Messages'); oracle.getL1ToL2Message.mockImplementation(async () => { - return Promise.resolve({ - message: preimage.toFieldArray(), - index: 0n, - siblingPath: (await tree.getSiblingPath(0n, false)).toFieldArray(), - }); + return Promise.resolve(new MessageLoadOracleInputs(preimage, 0n, await tree.getSiblingPath(0n, false))); }); }; diff --git a/yarn-project/acir-simulator/src/client/private_execution.ts b/yarn-project/acir-simulator/src/client/private_execution.ts index c16fcd83b34..f02ca372be9 100644 --- a/yarn-project/acir-simulator/src/client/private_execution.ts +++ b/yarn-project/acir-simulator/src/client/private_execution.ts @@ -1,11 +1,11 @@ -import { FunctionData, PrivateCallStackItem } from '@aztec/circuits.js'; +import { FunctionData, PrivateCallStackItem, PrivateCircuitPublicInputs } from '@aztec/circuits.js'; import { FunctionArtifactWithDebugMetadata, decodeReturnValues } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; import { to2Fields } from '@aztec/foundation/serialize'; -import { extractPrivateCircuitPublicInputs } from '../acvm/deserialize.js'; +import { extractReturnWitness } from '../acvm/deserialize.js'; import { Oracle, acvm, extractCallStack } from '../acvm/index.js'; import { ExecutionError } from '../common/errors.js'; import { ClientExecutionContext } from './client_execution_context.js'; @@ -42,7 +42,8 @@ export async function executePrivateFunction( }, ); - const publicInputs = extractPrivateCircuitPublicInputs(partialWitness, acir); + const returnWitness = extractReturnWitness(acir, partialWitness); + const publicInputs = PrivateCircuitPublicInputs.fromFields(returnWitness); const encryptedLogs = context.getEncryptedLogs(); const unencryptedLogs = context.getUnencryptedLogs(); diff --git a/yarn-project/acir-simulator/src/public/db.ts b/yarn-project/acir-simulator/src/public/db.ts index a001a4d4ab8..26923cc366e 100644 --- a/yarn-project/acir-simulator/src/public/db.ts +++ b/yarn-project/acir-simulator/src/public/db.ts @@ -1,4 +1,4 @@ -import { EthAddress, FunctionSelector } from '@aztec/circuits.js'; +import { EthAddress, FunctionSelector, L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/circuits.js'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; @@ -74,7 +74,7 @@ export interface CommitmentsDB { * @param msgKey - The message Key. * @returns - The l1 to l2 message object */ - getL1ToL2Message(msgKey: Fr): Promise; + getL1ToL2Message(msgKey: Fr): Promise>; /** * Gets the index of a commitment in the note hash tree. diff --git a/yarn-project/acir-simulator/src/public/executor.ts b/yarn-project/acir-simulator/src/public/executor.ts index 67af8c89a57..e5d3b935403 100644 --- a/yarn-project/acir-simulator/src/public/executor.ts +++ b/yarn-project/acir-simulator/src/public/executor.ts @@ -1,7 +1,7 @@ -import { GlobalVariables, Header } from '@aztec/circuits.js'; +import { GlobalVariables, Header, PublicCircuitPublicInputs } from '@aztec/circuits.js'; import { createDebugLogger } from '@aztec/foundation/log'; -import { Oracle, acvm, extractCallStack, extractPublicCircuitPublicInputs } from '../acvm/index.js'; +import { Oracle, acvm, extractCallStack, extractReturnWitness } from '../acvm/index.js'; import { ExecutionError, createSimulationError } from '../common/errors.js'; import { SideEffectCounter } from '../common/index.js'; import { PackedArgsCache } from '../common/packed_args_cache.js'; @@ -39,12 +39,13 @@ export async function executePublicFunction( }, ); + const returnWitness = extractReturnWitness(acir, partialWitness); const { returnValues, newL2ToL1Msgs, newCommitments: newCommitmentsPadded, newNullifiers: newNullifiersPadded, - } = extractPublicCircuitPublicInputs(partialWitness, acir); + } = PublicCircuitPublicInputs.fromFields(returnWitness); const newL2ToL1Messages = newL2ToL1Msgs.filter(v => !v.isZero()); const newCommitments = newCommitmentsPadded.filter(v => !v.isEmpty()); diff --git a/yarn-project/acir-simulator/src/public/index.test.ts b/yarn-project/acir-simulator/src/public/index.test.ts index dde71adb1ea..e96cd2ecaa9 100644 --- a/yarn-project/acir-simulator/src/public/index.test.ts +++ b/yarn-project/acir-simulator/src/public/index.test.ts @@ -1,4 +1,4 @@ -import { L1ToL2Message } from '@aztec/circuit-types'; +import { L1ToL2Message, SiblingPath } from '@aztec/circuit-types'; import { AppendOnlyTreeSnapshot, CallContext, @@ -21,6 +21,7 @@ import { MockProxy, mock } from 'jest-mock-extended'; import { type MemDown, default as memdown } from 'memdown'; import { getFunctionSelector } from 'viem'; +import { MessageLoadOracleInputs } from '../index.js'; import { buildL1ToL2Message } from '../test/utils.js'; import { computeSlotForMapping } from '../utils.js'; import { CommitmentsDB, PublicContractsDB, PublicStateDB } from './db.js'; @@ -454,17 +455,17 @@ describe('ACIR public execution simulator', () => { publicContracts.getBytecode.mockResolvedValue(Buffer.from(mintPublicArtifact.bytecode, 'base64')); publicState.storageRead.mockResolvedValue(Fr.ZERO); - const siblingPath = Array(L1_TO_L2_MSG_TREE_HEIGHT).fill(Fr.random()); + const siblingPathBuffers = Array(L1_TO_L2_MSG_TREE_HEIGHT) + .fill(Fr.random()) + .map(f => f.toBuffer()); + const siblingPath = new SiblingPath(L1_TO_L2_MSG_TREE_HEIGHT, siblingPathBuffers); + let root = messageKey ?? preimage.hash(); - for (const sibling of siblingPath) { - root = Fr.fromBuffer(pedersenHash([root.toBuffer(), sibling.toBuffer()])); + for (const sibling of siblingPathBuffers) { + root = Fr.fromBuffer(pedersenHash([root.toBuffer(), sibling])); } - commitmentsDb.getL1ToL2Message.mockImplementation(async () => { - return await Promise.resolve({ - message: preimage.toFieldArray(), - index: 0n, - siblingPath, - }); + commitmentsDb.getL1ToL2Message.mockImplementation(() => { + return Promise.resolve(new MessageLoadOracleInputs(preimage, 0n, siblingPath)); }); return new AppendOnlyTreeSnapshot( diff --git a/yarn-project/acir-simulator/src/public/public_execution_context.ts b/yarn-project/acir-simulator/src/public/public_execution_context.ts index 5ed036b1413..1c275fae334 100644 --- a/yarn-project/acir-simulator/src/public/public_execution_context.ts +++ b/yarn-project/acir-simulator/src/public/public_execution_context.ts @@ -5,7 +5,7 @@ import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; -import { TypedOracle, toACVMCallContext, toACVMGlobalVariables, toACVMHeader, toACVMWitness } from '../acvm/index.js'; +import { TypedOracle, toACVMWitness } from '../acvm/index.js'; import { PackedArgsCache, SideEffectCounter } from '../common/index.js'; import { CommitmentsDB, PublicContractsDB, PublicStateDB } from './db.js'; import { PublicExecution, PublicExecutionResult } from './execution.js'; @@ -49,13 +49,7 @@ export class PublicExecutionContext extends TypedOracle { */ public getInitialWitness(witnessStartIndex = 0) { const { callContext, args } = this.execution; - const fields = [ - ...toACVMCallContext(callContext), - ...toACVMHeader(this.header), - ...toACVMGlobalVariables(this.globalVariables), - - ...args, - ]; + const fields = [...callContext.toFields(), ...this.header.toFields(), ...this.globalVariables.toFields(), ...args]; return toACVMWitness(witnessStartIndex, fields); } diff --git a/yarn-project/aztec-node/src/aztec-node/http_rpc_server.ts b/yarn-project/aztec-node/src/aztec-node/http_rpc_server.ts index 0b4d6f7236b..fbbbed51794 100644 --- a/yarn-project/aztec-node/src/aztec-node/http_rpc_server.ts +++ b/yarn-project/aztec-node/src/aztec-node/http_rpc_server.ts @@ -8,6 +8,7 @@ import { L2BlockL2Logs, L2Tx, LogId, + SiblingPath, Tx, TxHash, } from '@aztec/circuit-types'; @@ -16,7 +17,6 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; import { JsonRpcServer } from '@aztec/foundation/json-rpc/server'; -import { SiblingPath } from '@aztec/types/membership'; /** * Wrap an AztecNode instance with a JSON RPC HTTP server. diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 2ccd40baa00..5f55ade4850 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -19,6 +19,7 @@ import { NullifierMembershipWitness, PublicDataWitness, SequencerConfig, + SiblingPath, Tx, TxHash, } from '@aztec/circuit-types'; @@ -46,7 +47,6 @@ import { SequencerClient, getGlobalVariableBuilder, } from '@aztec/sequencer-client'; -import { SiblingPath } from '@aztec/types/membership'; import { MerkleTrees, ServerWorldStateSynchronizer, diff --git a/yarn-project/aztec.js/src/contract/deploy_method.ts b/yarn-project/aztec.js/src/contract/deploy_method.ts index 7cd77c1a017..32cbd28c7c6 100644 --- a/yarn-project/aztec.js/src/contract/deploy_method.ts +++ b/yarn-project/aztec.js/src/contract/deploy_method.ts @@ -4,7 +4,6 @@ import { ContractDeploymentData, FunctionData, TxContext, - computeContractAddressFromInstance, computePartialAddress, getContractInstanceFromDeployParams, } from '@aztec/circuits.js'; @@ -77,7 +76,7 @@ export class DeployMethod extends Bas const deployParams = [this.artifact, this.args, contractAddressSalt, this.publicKey, portalContract] as const; const instance = getContractInstanceFromDeployParams(...deployParams); - const address = computeContractAddressFromInstance(instance); + const address = instance.address; const contractDeploymentData = new ContractDeploymentData( this.publicKey, diff --git a/yarn-project/circuit-types/src/aztec_node/rpc/aztec_node_client.ts b/yarn-project/circuit-types/src/aztec_node/rpc/aztec_node_client.ts index c78a6cdeb08..2ac789ffe38 100644 --- a/yarn-project/circuit-types/src/aztec_node/rpc/aztec_node_client.ts +++ b/yarn-project/circuit-types/src/aztec_node/rpc/aztec_node_client.ts @@ -4,7 +4,6 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; import { createJsonRpcClient, defaultFetch } from '@aztec/foundation/json-rpc/client'; -import { SiblingPath } from '@aztec/types/membership'; import { ContractData, ExtendedContractData } from '../../contract_data.js'; import { AztecNode } from '../../interfaces/index.js'; @@ -12,6 +11,7 @@ import { L1ToL2MessageAndIndex } from '../../l1_to_l2_message.js'; import { L2Block } from '../../l2_block.js'; import { L2Tx } from '../../l2_tx.js'; import { ExtendedUnencryptedL2Log, L2BlockL2Logs, LogId } from '../../logs/index.js'; +import { SiblingPath } from '../../sibling_path/index.js'; import { Tx, TxHash } from '../../tx/index.js'; /** diff --git a/yarn-project/circuit-types/src/index.ts b/yarn-project/circuit-types/src/index.ts index 85ea8cb627f..a61e91fc27d 100644 --- a/yarn-project/circuit-types/src/index.ts +++ b/yarn-project/circuit-types/src/index.ts @@ -16,6 +16,7 @@ export * from './merkle_tree_id.js'; export * from './mocks.js'; export * from './public_data_write.js'; export * from './simulation_error.js'; +export * from './sibling_path/index.js'; export * from './tx/index.js'; export * from './tx_execution_request.js'; export * from './packed_arguments.js'; diff --git a/yarn-project/circuit-types/src/interfaces/nullifier_tree.ts b/yarn-project/circuit-types/src/interfaces/nullifier_tree.ts index 4a06400a8c7..81b49d7dd46 100644 --- a/yarn-project/circuit-types/src/interfaces/nullifier_tree.ts +++ b/yarn-project/circuit-types/src/interfaces/nullifier_tree.ts @@ -1,5 +1,6 @@ import { Fr, NULLIFIER_TREE_HEIGHT, NullifierLeafPreimage } from '@aztec/circuits.js'; -import { SiblingPath } from '@aztec/types/membership'; + +import { SiblingPath } from '../sibling_path/index.js'; /** * Nullifier membership witness. diff --git a/yarn-project/circuit-types/src/interfaces/public_data_tree.ts b/yarn-project/circuit-types/src/interfaces/public_data_tree.ts index 5c5fc9c3054..5ea517fc84a 100644 --- a/yarn-project/circuit-types/src/interfaces/public_data_tree.ts +++ b/yarn-project/circuit-types/src/interfaces/public_data_tree.ts @@ -1,5 +1,6 @@ import { Fr, PUBLIC_DATA_TREE_HEIGHT, PublicDataTreeLeafPreimage } from '@aztec/circuits.js'; -import { SiblingPath } from '@aztec/types/membership'; + +import { SiblingPath } from '../sibling_path/index.js'; /** * Public data witness. diff --git a/yarn-project/circuit-types/src/interfaces/state_info_provider.ts b/yarn-project/circuit-types/src/interfaces/state_info_provider.ts index 3e7d83ece94..d653a2284c8 100644 --- a/yarn-project/circuit-types/src/interfaces/state_info_provider.ts +++ b/yarn-project/circuit-types/src/interfaces/state_info_provider.ts @@ -7,11 +7,11 @@ import { NULLIFIER_TREE_HEIGHT, PUBLIC_DATA_TREE_HEIGHT, } from '@aztec/circuits.js'; -import { SiblingPath } from '@aztec/types/membership'; import { L1ToL2MessageAndIndex } from '../l1_to_l2_message.js'; import { L2Block } from '../l2_block.js'; import { MerkleTreeId } from '../merkle_tree_id.js'; +import { SiblingPath } from '../sibling_path/index.js'; import { NullifierMembershipWitness } from './nullifier_tree.js'; import { PublicDataWitness } from './public_data_tree.js'; diff --git a/yarn-project/types/src/sibling-path/index.ts b/yarn-project/circuit-types/src/sibling_path/index.ts similarity index 100% rename from yarn-project/types/src/sibling-path/index.ts rename to yarn-project/circuit-types/src/sibling_path/index.ts diff --git a/yarn-project/types/src/sibling-path/sibling-path.ts b/yarn-project/circuit-types/src/sibling_path/sibling-path.ts similarity index 99% rename from yarn-project/types/src/sibling-path/sibling-path.ts rename to yarn-project/circuit-types/src/sibling_path/sibling-path.ts index ae2db6fe133..f0a8ee22a43 100644 --- a/yarn-project/types/src/sibling-path/sibling-path.ts +++ b/yarn-project/circuit-types/src/sibling_path/sibling-path.ts @@ -6,8 +6,7 @@ import { deserializeArrayFromVector, serializeBufferArrayToVector, } from '@aztec/foundation/serialize'; - -import { Hasher } from '../interfaces/index.js'; +import { Hasher } from '@aztec/types/interfaces'; /** * Contains functionality to compute and serialize/deserialize a sibling path. diff --git a/yarn-project/circuits.js/src/abis/abis.ts b/yarn-project/circuits.js/src/abis/abis.ts index 8f346fbd79e..003829def1b 100644 --- a/yarn-project/circuits.js/src/abis/abis.ts +++ b/yarn-project/circuits.js/src/abis/abis.ts @@ -397,7 +397,7 @@ function computePrivateInputsHash(input: PrivateCircuitPublicInputs) { ...input.unencryptedLogsHash.map(fr => fr.toBuffer()), input.encryptedLogPreimagesLength.toBuffer(), input.unencryptedLogPreimagesLength.toBuffer(), - ...(input.historicalHeader.toFieldArray().map(fr => fr.toBuffer()) as Buffer[]), + ...(input.historicalHeader.toFields().map(fr => fr.toBuffer()) as Buffer[]), computeContractDeploymentDataHash(input.contractDeploymentData).toBuffer(), input.chainId.toBuffer(), input.version.toBuffer(), @@ -463,7 +463,7 @@ export function computePublicInputsHash(input: PublicCircuitPublicInputs) { ...input.newL2ToL1Msgs.map(fr => fr.toBuffer()), ...input.unencryptedLogsHash.map(fr => fr.toBuffer()), input.unencryptedLogPreimagesLength.toBuffer(), - ...input.historicalHeader.toFieldArray().map(fr => fr.toBuffer()), + ...input.historicalHeader.toFields().map(fr => fr.toBuffer()), input.proverAddress.toBuffer(), ]; if (toHash.length != PUBLIC_CIRCUIT_PUBLIC_INPUTS_HASH_INPUT_LENGTH) { diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index 6d2571f17ec..6fcfc964d85 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -59,10 +59,10 @@ export const CALL_CONTEXT_LENGTH = 8; export const HEADER_LENGTH = 18; export const FUNCTION_DATA_LENGTH = 4; export const CONTRACT_DEPLOYMENT_DATA_LENGTH = 6; -export const PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH = 200; +export const PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH = 204; export const CONTRACT_STORAGE_UPDATE_REQUEST_LENGTH = 3; export const CONTRACT_STORAGE_READ_LENGTH = 2; -export const PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 190; +export const PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 201; export const GET_NOTES_ORACLE_RETURN_LENGTH = 674; export const CALL_PRIVATE_FUNCTION_RETURN_SIZE = 210; export const PUBLIC_CIRCUIT_PUBLIC_INPUTS_HASH_INPUT_LENGTH = 98; diff --git a/yarn-project/circuits.js/src/contract/contract_instance.ts b/yarn-project/circuits.js/src/contract/contract_instance.ts index 01c11167844..6c57db21e37 100644 --- a/yarn-project/circuits.js/src/contract/contract_instance.ts +++ b/yarn-project/circuits.js/src/contract/contract_instance.ts @@ -1,7 +1,7 @@ import { ContractArtifact } from '@aztec/foundation/abi'; import { ContractInstance, ContractInstanceWithAddress } from '@aztec/types/contracts'; -import { EthAddress, Fr, PublicKey, computeContractClassId, getContractClassFromArtifact } from '../index.js'; +import { EthAddress, Fr, Point, PublicKey, computeContractClassId, getContractClassFromArtifact } from '../index.js'; import { computeContractAddressFromInstance, computeInitializationHash, @@ -20,10 +20,10 @@ import { isConstructor } from './contract_tree/contract_tree.js'; */ export function getContractInstanceFromDeployParams( artifact: ContractArtifact, - args: any[], - contractAddressSalt: Fr, - publicKey: PublicKey, - portalContractAddress: EthAddress, + args: any[] = [], + contractAddressSalt: Fr = Fr.random(), + publicKey: PublicKey = Point.ZERO, + portalContractAddress: EthAddress = EthAddress.ZERO, ): ContractInstanceWithAddress { const constructorArtifact = artifact.functions.find(isConstructor); if (!constructorArtifact) { diff --git a/yarn-project/circuits.js/src/structs/__snapshots__/call_stack_item.test.ts.snap b/yarn-project/circuits.js/src/structs/__snapshots__/call_stack_item.test.ts.snap new file mode 100644 index 00000000000..d54ec645a35 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/__snapshots__/call_stack_item.test.ts.snap @@ -0,0 +1,44 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PrivateCallStackItem computes hash 1`] = ` +Fr { + "asBigInt": 20211835094457188239301134546342553663959029760227448898294272588084026667910n, + "asBuffer": { + "data": [ + 44, + 175, + 126, + 70, + 125, + 139, + 43, + 118, + 26, + 81, + 126, + 221, + 198, + 219, + 65, + 196, + 232, + 191, + 214, + 222, + 18, + 149, + 88, + 100, + 79, + 28, + 235, + 175, + 176, + 129, + 243, + 134, + ], + "type": "Buffer", + }, +} +`; diff --git a/yarn-project/circuits.js/src/structs/call_context.test.ts b/yarn-project/circuits.js/src/structs/call_context.test.ts new file mode 100644 index 00000000000..0cc83f9aaa4 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/call_context.test.ts @@ -0,0 +1,24 @@ +import { CALL_CONTEXT_LENGTH } from '../constants.gen.js'; +import { makeCallContext } from '../tests/factories.js'; +import { CallContext } from './call_context.js'; + +describe('CallContext', () => { + let callContext: CallContext; + + beforeAll(() => { + const randomInt = Math.floor(Math.random() * 1000); + callContext = makeCallContext(randomInt); + }); + + it(`serializes to buffer and deserializes it back`, () => { + const buffer = callContext.toBuffer(); + const res = CallContext.fromBuffer(buffer); + expect(res).toEqual(callContext); + expect(res.isEmpty()).toBe(false); + }); + + it('number of fields matches constant', () => { + const fields = callContext.toFields(); + expect(fields.length).toBe(CALL_CONTEXT_LENGTH); + }); +}); diff --git a/yarn-project/circuits.js/src/structs/call_context.ts b/yarn-project/circuits.js/src/structs/call_context.ts index 5f35bced17d..3640040718d 100644 --- a/yarn-project/circuits.js/src/structs/call_context.ts +++ b/yarn-project/circuits.js/src/structs/call_context.ts @@ -1,6 +1,6 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; -import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { BufferReader, FieldReader, serializeToBuffer, serializeToFieldArray } from '@aztec/foundation/serialize'; import { FieldsOf } from '@aztec/foundation/types'; import { Fr, FunctionSelector } from './index.js'; @@ -107,6 +107,10 @@ export class CallContext { return serializeToBuffer(...CallContext.getFields(this)); } + toFields(): Fr[] { + return serializeToFieldArray(...CallContext.getFields(this)); + } + /** * Deserialize this from a buffer. * @param buffer - The bufferable type from which to deserialize. diff --git a/yarn-project/circuits.js/src/structs/call_stack_item.test.ts b/yarn-project/circuits.js/src/structs/call_stack_item.test.ts new file mode 100644 index 00000000000..0a483a35699 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/call_stack_item.test.ts @@ -0,0 +1,28 @@ +import { makePrivateCallStackItem } from '../tests/factories.js'; +import { PrivateCallStackItem } from './call_stack_item.js'; + +describe('PrivateCallStackItem', () => { + it('serializes to buffer and deserializes it back', () => { + const randomInt = Math.floor(Math.random() * 1000); + const expected = makePrivateCallStackItem(randomInt); + const buffer = expected.toBuffer(); + const res = PrivateCallStackItem.fromBuffer(buffer); + expect(res).toEqual(expected); + }); + + it('serializes to field array and deserializes it back', () => { + const randomInt = Math.floor(Math.random() * 1000); + const expected = makePrivateCallStackItem(randomInt); + + const fieldArray = expected.toFields(); + const res = PrivateCallStackItem.fromFields(fieldArray); + expect(res).toEqual(expected); + }); + + it('computes hash', () => { + const seed = 9870243; + const PrivateCallStackItem = makePrivateCallStackItem(seed); + const hash = PrivateCallStackItem.hash(); + expect(hash).toMatchSnapshot(); + }); +}); diff --git a/yarn-project/circuits.js/src/structs/call_stack_item.ts b/yarn-project/circuits.js/src/structs/call_stack_item.ts index 4752238d7bc..397b1654289 100644 --- a/yarn-project/circuits.js/src/structs/call_stack_item.ts +++ b/yarn-project/circuits.js/src/structs/call_stack_item.ts @@ -1,6 +1,6 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { computePrivateCallStackItemHash, computePublicCallStackItemHash } from '../abis/abis.js'; import { CallRequest, CallerContext } from './call_request.js'; @@ -10,7 +10,6 @@ import { PublicCircuitPublicInputs } from './public_circuit_public_inputs.js'; /** * Call stack item on a private call. - * @see cpp/src/aztec3/circuits/abis/call_stack_item.hpp. */ export class PrivateCallStackItem { constructor( @@ -40,6 +39,15 @@ export class PrivateCallStackItem { return serializeToBuffer(this.contractAddress, this.functionData, this.publicInputs, this.isExecutionRequest); } + toFields(): Fr[] { + return [ + this.contractAddress.toField(), + ...this.functionData.toFields(), + ...this.publicInputs.toFields(), + new Fr(this.isExecutionRequest), + ]; + } + /** * Deserializes from a buffer or reader. * @param buffer - Buffer or reader to read from. @@ -55,6 +63,17 @@ export class PrivateCallStackItem { ); } + static fromFields(fields: Fr[] | FieldReader): PrivateCallStackItem { + const reader = FieldReader.asReader(fields); + + const contractAddress = AztecAddress.fromFields(reader); + const functionData = FunctionData.fromFields(reader); + const publicInputs = PrivateCircuitPublicInputs.fromFields(reader); + const isExecutionRequest = reader.readBoolean(); + + return new PrivateCallStackItem(contractAddress, functionData, publicInputs, isExecutionRequest); + } + /** * Returns a new instance of PrivateCallStackItem with zero contract address, function data and public inputs. * @returns A new instance of PrivateCallStackItem with zero contract address, function data and public inputs. diff --git a/yarn-project/circuits.js/src/structs/complete_address.ts b/yarn-project/circuits.js/src/structs/complete_address.ts index cf34dfa2426..226a3d5a76f 100644 --- a/yarn-project/circuits.js/src/structs/complete_address.ts +++ b/yarn-project/circuits.js/src/structs/complete_address.ts @@ -1,5 +1,5 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { Fr, Point } from '@aztec/foundation/fields'; +import { Fr, GrumpkinScalar, Point } from '@aztec/foundation/fields'; import { BufferReader } from '@aztec/foundation/serialize'; import { Grumpkin } from '../barretenberg/index.js'; @@ -48,6 +48,12 @@ export class CompleteAddress { return new CompleteAddress(address, publicKey, partialAddress); } + static fromRandomPrivateKey() { + const privateKey = GrumpkinScalar.random(); + const partialAddress = Fr.random(); + return { privateKey, completeAddress: CompleteAddress.fromPrivateKeyAndPartialAddress(privateKey, partialAddress) }; + } + static fromPrivateKeyAndPartialAddress(privateKey: GrumpkinPrivateKey, partialAddress: Fr): CompleteAddress { const grumpkin = new Grumpkin(); const publicKey = grumpkin.mul(Grumpkin.generator, privateKey); diff --git a/yarn-project/circuits.js/src/structs/contract_deployment_data.test.ts b/yarn-project/circuits.js/src/structs/contract_deployment_data.test.ts new file mode 100644 index 00000000000..d1988a62553 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/contract_deployment_data.test.ts @@ -0,0 +1,24 @@ +import { CONTRACT_DEPLOYMENT_DATA_LENGTH } from '../constants.gen.js'; +import { makeContractDeploymentData } from '../tests/factories.js'; +import { ContractDeploymentData } from './contract_deployment_data.js'; + +describe('ContractDeploymentData', () => { + it(`serializes to buffer and deserializes it back`, () => { + const expected = makeContractDeploymentData(1); + const buffer = expected.toBuffer(); + const res = ContractDeploymentData.fromBuffer(buffer); + expect(res).toEqual(expected); + expect(res.isEmpty()).toBe(false); + }); + + it(`initializes an empty ContractDeploymentData`, () => { + const target = ContractDeploymentData.empty(); + expect(target.isEmpty()).toBe(true); + }); + + it('number of fields matches constant', () => { + const target = makeContractDeploymentData(327); + const fields = target.toFields(); + expect(fields.length).toBe(CONTRACT_DEPLOYMENT_DATA_LENGTH); + }); +}); diff --git a/yarn-project/circuits.js/src/structs/contract_deployment_data.ts b/yarn-project/circuits.js/src/structs/contract_deployment_data.ts new file mode 100644 index 00000000000..d99d15044a0 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/contract_deployment_data.ts @@ -0,0 +1,94 @@ +import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; + +import { EthAddress, Fr, Point, PublicKey } from '../index.js'; + +/** + * Contract deployment data in a TxContext + * Not to be confused with NewContractData. + */ +export class ContractDeploymentData { + constructor( + /** Public key of the contract. */ + public publicKey: PublicKey, + /** Hash of the initialization payload. */ + public initializationHash: Fr, + /** Contract class identifier. */ + public contractClassId: Fr, + /** Contract address salt (used when deriving a contract address). */ + public contractAddressSalt: Fr, + /** Ethereum address of the portal contract on L1. */ + public portalContractAddress: EthAddress, + ) {} + + toBuffer() { + return serializeToBuffer( + this.publicKey, + this.initializationHash, + this.contractClassId, + this.contractAddressSalt, + this.portalContractAddress, + ); + } + + toFields(): Fr[] { + return [ + ...this.publicKey.toFields(), + this.initializationHash, + this.contractClassId, + this.contractAddressSalt, + this.portalContractAddress.toField(), + ]; + } + + /** + * Returns an empty ContractDeploymentData. + * @returns The empty ContractDeploymentData. + */ + public static empty(): ContractDeploymentData { + return new ContractDeploymentData(Point.ZERO, Fr.ZERO, Fr.ZERO, Fr.ZERO, EthAddress.ZERO); + } + + isEmpty() { + return ( + this.publicKey.isZero() && + this.initializationHash.isZero() && + this.contractClassId.isZero() && + this.contractAddressSalt.isZero() && + this.portalContractAddress.isZero() + ); + } + + /** + * Deserializes contract deployment data rom a buffer or reader. + * @param buffer - Buffer to read from. + * @returns The deserialized ContractDeploymentData. + */ + static fromBuffer(buffer: Buffer | BufferReader): ContractDeploymentData { + const reader = BufferReader.asReader(buffer); + return new ContractDeploymentData( + reader.readObject(Point), + Fr.fromBuffer(reader), + Fr.fromBuffer(reader), + Fr.fromBuffer(reader), + new EthAddress(reader.readBytes(32)), + ); + } + + static fromFields(fields: Fr[] | FieldReader): ContractDeploymentData { + const reader = FieldReader.asReader(fields); + + const publicKey = Point.fromFields(reader); + const initializationHash = reader.readField(); + const contractClassId = reader.readField(); + const contractAddressSalt = reader.readField(); + const portalContractAddress = new EthAddress(reader.readField().toBuffer()); + + return new ContractDeploymentData( + publicKey, + initializationHash, + contractClassId, + contractAddressSalt, + portalContractAddress, + ); + } +} diff --git a/yarn-project/circuits.js/src/structs/contract_storage_read.test.ts b/yarn-project/circuits.js/src/structs/contract_storage_read.test.ts new file mode 100644 index 00000000000..9bedecfd3e9 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/contract_storage_read.test.ts @@ -0,0 +1,29 @@ +import { CONTRACT_STORAGE_READ_LENGTH } from '../constants.gen.js'; +import { makeContractStorageRead } from '../tests/factories.js'; +import { ContractStorageRead } from './contract_storage_read.js'; + +describe('ContractStorageRead', () => { + let read: ContractStorageRead; + + beforeAll(() => { + const randomInt = Math.floor(Math.random() * 1000); + read = makeContractStorageRead(randomInt); + }); + + it('serializes to buffer and deserializes it back', () => { + const buffer = read.toBuffer(); + const res = ContractStorageRead.fromBuffer(buffer); + expect(res).toEqual(read); + }); + + it('serializes to field array and deserializes it back', () => { + const fieldArray = read.toFields(); + const res = ContractStorageRead.fromFields(fieldArray); + expect(res).toEqual(read); + }); + + it('number of fields matches constant', () => { + const fields = read.toFields(); + expect(fields.length).toBe(CONTRACT_STORAGE_READ_LENGTH); + }); +}); diff --git a/yarn-project/circuits.js/src/structs/contract_storage_read.ts b/yarn-project/circuits.js/src/structs/contract_storage_read.ts new file mode 100644 index 00000000000..907470b132d --- /dev/null +++ b/yarn-project/circuits.js/src/structs/contract_storage_read.ts @@ -0,0 +1,77 @@ +import { Fr } from '@aztec/foundation/fields'; +import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; + +/** + * Contract storage read operation on a specific contract. + * + * Note: Similar to `PublicDataRead` but it's from the POV of contract storage so we are not working with public data + * tree leaf index but storage slot index. + */ +export class ContractStorageRead { + constructor( + /** + * Storage slot we are reading from. + */ + public readonly storageSlot: Fr, + /** + * Value read from the storage slot. + */ + public readonly currentValue: Fr, + /** + * Optional side effect counter tracking position of this event in tx execution. + * Note: Not serialized + */ + public readonly sideEffectCounter?: number, + ) {} + + static from(args: { + /** + * Storage slot we are reading from. + */ + storageSlot: Fr; + /** + * Value read from the storage slot. + */ + currentValue: Fr; + /** + * Optional side effect counter tracking position of this event in tx execution. + */ + sideEffectCounter?: number; + }) { + return new ContractStorageRead(args.storageSlot, args.currentValue, args.sideEffectCounter); + } + + toBuffer() { + return serializeToBuffer(this.storageSlot, this.currentValue); + } + + static fromBuffer(buffer: Buffer | BufferReader) { + const reader = BufferReader.asReader(buffer); + return new ContractStorageRead(Fr.fromBuffer(reader), Fr.fromBuffer(reader)); + } + + static empty() { + return new ContractStorageRead(Fr.ZERO, Fr.ZERO); + } + + isEmpty() { + return this.storageSlot.isZero() && this.currentValue.isZero(); + } + + toFriendlyJSON() { + return `Slot=${this.storageSlot.toFriendlyJSON()}: ${this.currentValue.toFriendlyJSON()}`; + } + + toFields(): Fr[] { + return [this.storageSlot, this.currentValue]; + } + + static fromFields(fields: Fr[] | FieldReader): ContractStorageRead { + const reader = FieldReader.asReader(fields); + + const storageSlot = reader.readField(); + const currentValue = reader.readField(); + + return new ContractStorageRead(storageSlot, currentValue); + } +} diff --git a/yarn-project/circuits.js/src/structs/contract_storage_update_request.test.ts b/yarn-project/circuits.js/src/structs/contract_storage_update_request.test.ts new file mode 100644 index 00000000000..6e3ca79cad1 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/contract_storage_update_request.test.ts @@ -0,0 +1,29 @@ +import { CONTRACT_STORAGE_UPDATE_REQUEST_LENGTH } from '../constants.gen.js'; +import { makeContractStorageUpdateRequest } from '../tests/factories.js'; +import { ContractStorageUpdateRequest } from './contract_storage_update_request.js'; + +describe('ContractStorageUpdateRequest', () => { + let request: ContractStorageUpdateRequest; + + beforeAll(() => { + const randomInt = Math.floor(Math.random() * 1000); + request = makeContractStorageUpdateRequest(randomInt); + }); + + it('serializes to buffer and deserializes it back', () => { + const buffer = request.toBuffer(); + const res = ContractStorageUpdateRequest.fromBuffer(buffer); + expect(res).toEqual(request); + }); + + it('serializes to field array and deserializes it back', () => { + const fieldArray = request.toFields(); + const res = ContractStorageUpdateRequest.fromFields(fieldArray); + expect(res).toEqual(request); + }); + + it('number of fields matches constant', () => { + const fields = request.toFields(); + expect(fields.length).toBe(CONTRACT_STORAGE_UPDATE_REQUEST_LENGTH); + }); +}); diff --git a/yarn-project/circuits.js/src/structs/contract_storage_update_request.ts b/yarn-project/circuits.js/src/structs/contract_storage_update_request.ts new file mode 100644 index 00000000000..351ea091825 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/contract_storage_update_request.ts @@ -0,0 +1,83 @@ +import { Fr } from '@aztec/foundation/fields'; +import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { FieldsOf } from '@aztec/foundation/types'; + +/** + * Contract storage update request for a slot on a specific contract. + * + * Note: Similar to `PublicDataUpdateRequest` but it's from the POV of contract storage so we are not working with + * public data tree leaf index but storage slot index. + */ +export class ContractStorageUpdateRequest { + constructor( + /** + * Storage slot we are updating. + */ + public readonly storageSlot: Fr, + /** + * Old value of the storage slot. + */ + public readonly oldValue: Fr, + /** + * New value of the storage slot. + */ + public readonly newValue: Fr, + /** + * Optional side effect counter tracking position of this event in tx execution. + */ + public readonly sideEffectCounter?: number, + ) {} + + toBuffer() { + return serializeToBuffer(this.storageSlot, this.oldValue, this.newValue); + } + + static fromBuffer(buffer: Buffer | BufferReader) { + const reader = BufferReader.asReader(buffer); + return new ContractStorageUpdateRequest(Fr.fromBuffer(reader), Fr.fromBuffer(reader), Fr.fromBuffer(reader)); + } + + /** + * Create PublicCallRequest from a fields dictionary. + * @param fields - The dictionary. + * @returns A PublicCallRequest object. + */ + static from(fields: FieldsOf): ContractStorageUpdateRequest { + return new ContractStorageUpdateRequest(...ContractStorageUpdateRequest.getFields(fields)); + } + + /** + * Serialize into a field array. Low-level utility. + * @param fields - Object with fields. + * @returns The array. + */ + static getFields(fields: FieldsOf) { + return [fields.storageSlot, fields.oldValue, fields.newValue, fields.sideEffectCounter] as const; + } + + static empty() { + return new ContractStorageUpdateRequest(Fr.ZERO, Fr.ZERO, Fr.ZERO); + } + + isEmpty() { + return this.storageSlot.isZero() && this.oldValue.isZero() && this.newValue.isZero(); + } + + toFriendlyJSON() { + return `Slot=${this.storageSlot.toFriendlyJSON()}: ${this.oldValue.toFriendlyJSON()} => ${this.newValue.toFriendlyJSON()}`; + } + + toFields(): Fr[] { + return [this.storageSlot, this.oldValue, this.newValue]; + } + + static fromFields(fields: Fr[] | FieldReader): ContractStorageUpdateRequest { + const reader = FieldReader.asReader(fields); + + const storageSlot = reader.readField(); + const oldValue = reader.readField(); + const newValue = reader.readField(); + + return new ContractStorageUpdateRequest(storageSlot, oldValue, newValue); + } +} diff --git a/yarn-project/circuits.js/src/structs/function_data.test.ts b/yarn-project/circuits.js/src/structs/function_data.test.ts index 269198d6ef7..4a953ccdb0c 100644 --- a/yarn-project/circuits.js/src/structs/function_data.test.ts +++ b/yarn-project/circuits.js/src/structs/function_data.test.ts @@ -1,13 +1,24 @@ import { FunctionSelector } from '@aztec/foundation/abi'; +import { FUNCTION_DATA_LENGTH } from '../constants.gen.js'; import { FunctionData } from './function_data.js'; describe('FunctionData', () => { + let functionData: FunctionData; + + beforeAll(() => { + functionData = new FunctionData(new FunctionSelector(123), false, true, true); + }); + it(`serializes to buffer and deserializes it back`, () => { - const expected = new FunctionData(new FunctionSelector(123), false, true, true); - const buffer = expected.toBuffer(); + const buffer = functionData.toBuffer(); const res = FunctionData.fromBuffer(buffer); - expect(res).toEqual(expected); + expect(res).toEqual(functionData); expect(res.isEmpty()).toBe(false); }); + + it('number of fields matches constant', () => { + const fields = functionData.toFields(); + expect(fields.length).toBe(FUNCTION_DATA_LENGTH); + }); }); diff --git a/yarn-project/circuits.js/src/structs/function_data.ts b/yarn-project/circuits.js/src/structs/function_data.ts index 7bafa4c126a..d35eb8dfffa 100644 --- a/yarn-project/circuits.js/src/structs/function_data.ts +++ b/yarn-project/circuits.js/src/structs/function_data.ts @@ -1,7 +1,7 @@ import { FunctionAbi, FunctionSelector, FunctionType } from '@aztec/foundation/abi'; -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; -import { ContractFunctionDao } from '../index.js'; +import { ContractFunctionDao, Fr } from '../index.js'; /** * Function description for circuit. @@ -44,6 +44,10 @@ export class FunctionData { return serializeToBuffer(this.selector, this.isInternal, this.isPrivate, this.isConstructor); } + toFields(): Fr[] { + return [this.selector.toField(), new Fr(this.isInternal), new Fr(this.isPrivate), new Fr(this.isConstructor)]; + } + /** * Returns whether this instance is empty. * @returns True if the function selector is zero. @@ -93,4 +97,15 @@ export class FunctionData { reader.readBoolean(), ); } + + static fromFields(fields: Fr[] | FieldReader): FunctionData { + const reader = FieldReader.asReader(fields); + + const selector = FunctionSelector.fromFields(reader); + const isInternal = reader.readBoolean(); + const isPrivate = reader.readBoolean(); + const isConstructor = reader.readBoolean(); + + return new FunctionData(selector, isInternal, isPrivate, isConstructor); + } } diff --git a/yarn-project/circuits.js/src/structs/global_variables.ts b/yarn-project/circuits.js/src/structs/global_variables.ts index fd353693a10..04491e46aba 100644 --- a/yarn-project/circuits.js/src/structs/global_variables.ts +++ b/yarn-project/circuits.js/src/structs/global_variables.ts @@ -1,5 +1,5 @@ import { Fr } from '@aztec/foundation/fields'; -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { FieldsOf } from '@aztec/foundation/types'; /** @@ -52,6 +52,12 @@ export class GlobalVariables { ); } + static fromFields(fields: Fr[] | FieldReader): GlobalVariables { + const reader = FieldReader.asReader(fields); + + return new GlobalVariables(reader.readField(), reader.readField(), reader.readField(), reader.readField()); + } + static getFields(fields: FieldsOf) { // Note: The order here must match the order in the HeaderLib solidity library. return [fields.chainId, fields.version, fields.blockNumber, fields.timestamp] as const; @@ -61,7 +67,7 @@ export class GlobalVariables { return serializeToBuffer(...GlobalVariables.getFields(this)); } - toFieldArray() { + toFields() { return GlobalVariables.getFields(this); } diff --git a/yarn-project/circuits.js/src/structs/header.test.ts b/yarn-project/circuits.js/src/structs/header.test.ts index 72d49ae9bf2..716c0a660b5 100644 --- a/yarn-project/circuits.js/src/structs/header.test.ts +++ b/yarn-project/circuits.js/src/structs/header.test.ts @@ -1,22 +1,25 @@ +import { HEADER_LENGTH } from '../constants.gen.js'; import { makeHeader } from '../tests/factories.js'; import { Header } from './header.js'; describe('Header', () => { - it('serializes to buffer and deserializes it back', () => { + let header: Header; + + beforeAll(() => { const randomInt = Math.floor(Math.random() * 1000); - const expected = makeHeader(randomInt, undefined); - const buffer = expected.toBuffer(); + header = makeHeader(randomInt, undefined); + }); + + it('serializes to buffer and deserializes it back', () => { + const buffer = header.toBuffer(); const res = Header.fromBuffer(buffer); - expect(res).toEqual(expected); + expect(res).toEqual(header); }); it('serializes to field array and deserializes it back', () => { - const randomInt = Math.floor(Math.random() * 1000); - const expected = makeHeader(randomInt, undefined); - - const fieldArray = expected.toFieldArray(); - const res = Header.fromFieldArray(fieldArray); - expect(res).toEqual(expected); + const fieldArray = header.toFields(); + const res = Header.fromFields(fieldArray); + expect(res).toEqual(header); }); it('computes hash', () => { @@ -25,4 +28,9 @@ describe('Header', () => { const hash = header.hash(); expect(hash).toMatchSnapshot(); }); + + it('number of fields matches constant', () => { + const fields = header.toFields(); + expect(fields.length).toBe(HEADER_LENGTH); + }); }); diff --git a/yarn-project/circuits.js/src/structs/header.ts b/yarn-project/circuits.js/src/structs/header.ts index 016f9e9f98e..5923e869541 100644 --- a/yarn-project/circuits.js/src/structs/header.ts +++ b/yarn-project/circuits.js/src/structs/header.ts @@ -1,10 +1,9 @@ import { pedersenHash } from '@aztec/foundation/crypto'; import { Fr } from '@aztec/foundation/fields'; -import { BufferReader, from2Fields, serializeToBuffer, to2Fields } from '@aztec/foundation/serialize'; +import { BufferReader, FieldReader, from2Fields, serializeToBuffer, to2Fields } from '@aztec/foundation/serialize'; import { GeneratorIndex, HEADER_LENGTH } from '../constants.gen.js'; import { GlobalVariables } from './global_variables.js'; -import { PartialStateReference } from './partial_state_reference.js'; import { AppendOnlyTreeSnapshot } from './rollup/append_only_tree_snapshot.js'; import { StateReference } from './state_reference.js'; @@ -32,13 +31,13 @@ export class Header { return serializeToBuffer(this.lastArchive, this.bodyHash, this.state, this.globalVariables); } - toFieldArray(): Fr[] { + toFields(): Fr[] { // Note: The order here must match the order in header.nr const serialized = [ - ...this.lastArchive.toFieldArray(), + ...this.lastArchive.toFields(), ...to2Fields(this.bodyHash), ...this.state.toFieldArray(), - ...this.globalVariables.toFieldArray(), + ...this.globalVariables.toFields(), ]; if (serialized.length !== HEADER_LENGTH) { throw new Error(`Expected header to have ${HEADER_LENGTH} fields, but it has ${serialized.length} fields`); @@ -57,23 +56,13 @@ export class Header { ); } - static fromFieldArray(fields: Fr[]): Header { - if (fields.length !== HEADER_LENGTH) { - throw new Error(`Expected header to have ${HEADER_LENGTH} fields, but it has ${fields.length} fields`); - } - // Note: The order here must match the order in header.nr - const lastArchive = new AppendOnlyTreeSnapshot(fields[0], Number(fields[1].toBigInt())); - const bodyHash = from2Fields(fields[2], fields[3]); - const state = new StateReference( - new AppendOnlyTreeSnapshot(fields[4], Number(fields[5].toBigInt())), - new PartialStateReference( - new AppendOnlyTreeSnapshot(fields[6], Number(fields[7].toBigInt())), - new AppendOnlyTreeSnapshot(fields[8], Number(fields[9].toBigInt())), - new AppendOnlyTreeSnapshot(fields[10], Number(fields[11].toBigInt())), - new AppendOnlyTreeSnapshot(fields[12], Number(fields[13].toBigInt())), - ), - ); - const globalVariables = new GlobalVariables(fields[14], fields[15], fields[16], fields[17]); + static fromFields(fields: Fr[] | FieldReader): Header { + const reader = FieldReader.asReader(fields); + + const lastArchive = new AppendOnlyTreeSnapshot(reader.readField(), Number(reader.readField().toBigInt())); + const bodyHash = from2Fields(reader.readField(), reader.readField()); + const state = StateReference.fromFields(reader); + const globalVariables = GlobalVariables.fromFields(reader); return new Header(lastArchive, bodyHash, state, globalVariables); } @@ -112,7 +101,7 @@ export class Header { hash(): Fr { return Fr.fromBuffer( pedersenHash( - this.toFieldArray().map(f => f.toBuffer()), + this.toFields().map(f => f.toBuffer()), GeneratorIndex.BLOCK_HASH, ), ); diff --git a/yarn-project/circuits.js/src/structs/index.ts b/yarn-project/circuits.js/src/structs/index.ts index ca2cb39c1c6..cb06d1b1412 100644 --- a/yarn-project/circuits.js/src/structs/index.ts +++ b/yarn-project/circuits.js/src/structs/index.ts @@ -4,6 +4,9 @@ export * from './call_context.js'; export * from './call_request.js'; export * from './call_stack_item.js'; export * from './complete_address.js'; +export * from './contract_deployment_data.js'; +export * from './contract_storage_read.js'; +export * from './contract_storage_update_request.js'; export * from './function_data.js'; export * from './function_leaf_preimage.js'; export * from './global_variables.js'; diff --git a/yarn-project/circuits.js/src/structs/partial_state_reference.ts b/yarn-project/circuits.js/src/structs/partial_state_reference.ts index 7e375eeb12a..369b0732c41 100644 --- a/yarn-project/circuits.js/src/structs/partial_state_reference.ts +++ b/yarn-project/circuits.js/src/structs/partial_state_reference.ts @@ -1,4 +1,5 @@ -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { Fr } from '@aztec/foundation/fields'; +import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { AppendOnlyTreeSnapshot } from './rollup/append_only_tree_snapshot.js'; @@ -27,6 +28,17 @@ export class PartialStateReference { ); } + static fromFields(fields: Fr[] | FieldReader): PartialStateReference { + const reader = FieldReader.asReader(fields); + + const noteHashTree = AppendOnlyTreeSnapshot.fromFields(reader); + const nullifierTree = AppendOnlyTreeSnapshot.fromFields(reader); + const contractTree = AppendOnlyTreeSnapshot.fromFields(reader); + const publicDataTree = AppendOnlyTreeSnapshot.fromFields(reader); + + return new PartialStateReference(noteHashTree, nullifierTree, contractTree, publicDataTree); + } + static empty(): PartialStateReference { return new PartialStateReference( AppendOnlyTreeSnapshot.zero(), @@ -40,12 +52,12 @@ export class PartialStateReference { return serializeToBuffer(this.noteHashTree, this.nullifierTree, this.contractTree, this.publicDataTree); } - toFieldArray() { + toFields() { return [ - ...this.noteHashTree.toFieldArray(), - ...this.nullifierTree.toFieldArray(), - ...this.contractTree.toFieldArray(), - ...this.publicDataTree.toFieldArray(), + ...this.noteHashTree.toFields(), + ...this.nullifierTree.toFields(), + ...this.contractTree.toFields(), + ...this.publicDataTree.toFields(), ]; } diff --git a/yarn-project/circuits.js/src/structs/private_circuit_public_inputs.test.ts b/yarn-project/circuits.js/src/structs/private_circuit_public_inputs.test.ts index a5f54760c11..8b8753e51cc 100644 --- a/yarn-project/circuits.js/src/structs/private_circuit_public_inputs.test.ts +++ b/yarn-project/circuits.js/src/structs/private_circuit_public_inputs.test.ts @@ -1,8 +1,34 @@ +import { PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH } from '../constants.gen.js'; +import { makePrivateCircuitPublicInputs } from '../tests/factories.js'; import { PrivateCircuitPublicInputs } from './private_circuit_public_inputs.js'; describe('PrivateCircuitPublicInputs', () => { + let inputs: PrivateCircuitPublicInputs; + + beforeAll(() => { + const randomInt = Math.floor(Math.random() * 1000); + inputs = makePrivateCircuitPublicInputs(randomInt); + }); + + it('serializes to buffer and back', () => { + const buffer = inputs.toBuffer(); + const result = PrivateCircuitPublicInputs.fromBuffer(buffer); + expect(result).toEqual(inputs); + }); + + it('serializes to fields and back', () => { + const fields = inputs.toFields(); + const result = PrivateCircuitPublicInputs.fromFields(fields); + expect(result).toEqual(inputs); + }); + it(`initializes an empty PrivateCircuitPublicInputs`, () => { const target = PrivateCircuitPublicInputs.empty(); expect(target.isEmpty()).toBe(true); }); + + it('number of fields matches constant', () => { + const fields = inputs.toFields(); + expect(fields.length).toBe(PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH); + }); }); diff --git a/yarn-project/circuits.js/src/structs/private_circuit_public_inputs.ts b/yarn-project/circuits.js/src/structs/private_circuit_public_inputs.ts index 9ddd2fbcdde..c5f7332d6c6 100644 --- a/yarn-project/circuits.js/src/structs/private_circuit_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/private_circuit_public_inputs.ts @@ -1,7 +1,13 @@ import { makeTuple } from '@aztec/foundation/array'; import { isArrayEmpty } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/fields'; -import { BufferReader, Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; +import { + BufferReader, + FieldReader, + Tuple, + serializeToBuffer, + serializeToFieldArray, +} from '@aztec/foundation/serialize'; import { FieldsOf } from '@aztec/foundation/types'; import { @@ -16,9 +22,8 @@ import { RETURN_VALUES_LENGTH, } from '../constants.gen.js'; import { CallContext } from './call_context.js'; -import { Header, SideEffect, SideEffectLinkedToNoteHash } from './index.js'; +import { ContractDeploymentData, Header, SideEffect, SideEffectLinkedToNoteHash } from './index.js'; import { NullifierKeyValidationRequest } from './nullifier_key_validation_request.js'; -import { ContractDeploymentData } from './tx_context.js'; /** * Public inputs to a private circuit. @@ -154,6 +159,31 @@ export class PrivateCircuitPublicInputs { ); } + static fromFields(fields: Fr[] | FieldReader): PrivateCircuitPublicInputs { + const reader = FieldReader.asReader(fields); + return new PrivateCircuitPublicInputs( + reader.readObject(CallContext), + reader.readField(), + reader.readFieldArray(RETURN_VALUES_LENGTH), + reader.readArray(MAX_READ_REQUESTS_PER_CALL, SideEffect), + reader.readArray(MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL, NullifierKeyValidationRequest), + reader.readArray(MAX_NEW_COMMITMENTS_PER_CALL, SideEffect), + reader.readArray(MAX_NEW_NULLIFIERS_PER_CALL, SideEffectLinkedToNoteHash), + reader.readFieldArray(MAX_PRIVATE_CALL_STACK_LENGTH_PER_CALL), + reader.readFieldArray(MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL), + reader.readFieldArray(MAX_NEW_L2_TO_L1_MSGS_PER_CALL), + reader.readField(), + reader.readFieldArray(NUM_FIELDS_PER_SHA256), + reader.readFieldArray(NUM_FIELDS_PER_SHA256), + reader.readField(), + reader.readField(), + reader.readObject(Header), + reader.readObject(ContractDeploymentData), + reader.readField(), + reader.readField(), + ); + } + /** * Create an empty PrivateCircuitPublicInputs. * @returns An empty PrivateCircuitPublicInputs object. @@ -237,6 +267,7 @@ export class PrivateCircuitPublicInputs { fields.version, ] as const; } + /** * Serialize this as a buffer. * @returns The buffer. @@ -244,4 +275,11 @@ export class PrivateCircuitPublicInputs { toBuffer(): Buffer { return serializeToBuffer(...PrivateCircuitPublicInputs.getFields(this)); } + + /** + * Serialize this as a field array. + */ + toFields(): Fr[] { + return serializeToFieldArray(...PrivateCircuitPublicInputs.getFields(this)); + } } diff --git a/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.test.ts b/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.test.ts index 531a766e2fe..29ff4513801 100644 --- a/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.test.ts +++ b/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.test.ts @@ -1,8 +1,25 @@ +import { PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH } from '../constants.gen.js'; +import { makePublicCircuitPublicInputs } from '../tests/factories.js'; import { PublicCircuitPublicInputs } from './public_circuit_public_inputs.js'; describe('PublicCircuitPublicInputs', () => { + it('serializes to field array and deserializes it back', () => { + const randomInt = Math.floor(Math.random() * 1000); + const expected = makePublicCircuitPublicInputs(randomInt, undefined); + + const fieldArray = expected.toFields(); + const res = PublicCircuitPublicInputs.fromFields(fieldArray); + expect(res).toEqual(expected); + }); + it(`initializes an empty PrivateCircuitPublicInputs`, () => { const target = PublicCircuitPublicInputs.empty(); expect(target.isEmpty()).toBe(true); }); + + it('number of fields matches constant', () => { + const target = makePublicCircuitPublicInputs(327); + const fields = target.toFields(); + expect(fields.length).toBe(PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH); + }); }); diff --git a/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts b/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts index 572e76e273b..d0d7961f538 100644 --- a/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts @@ -2,7 +2,7 @@ import { makeTuple } from '@aztec/foundation/array'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { isArrayEmpty } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/fields'; -import { BufferReader, Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; +import { FieldReader, Tuple, serializeToBuffer, serializeToFieldArray } from '@aztec/foundation/serialize'; import { FieldsOf } from '@aztec/foundation/types'; import { @@ -12,137 +12,17 @@ import { MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, + NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH, } from '../constants.gen.js'; import { CallContext } from './call_context.js'; -import { Header, SideEffect, SideEffectLinkedToNoteHash } from './index.js'; - -/** - * Contract storage read operation on a specific contract. - * - * Note: Similar to `PublicDataRead` but it's from the POV of contract storage so we are not working with public data - * tree leaf index but storage slot index. - */ -export class ContractStorageRead { - constructor( - /** - * Storage slot we are reading from. - */ - public readonly storageSlot: Fr, - /** - * Value read from the storage slot. - */ - public readonly currentValue: Fr, - /** - * Optional side effect counter tracking position of this event in tx execution. - */ - public readonly sideEffectCounter?: number, - ) {} - - static from(args: { - /** - * Storage slot we are reading from. - */ - storageSlot: Fr; - /** - * Value read from the storage slot. - */ - currentValue: Fr; - /** - * Optional side effect counter tracking position of this event in tx execution. - */ - sideEffectCounter?: number; - }) { - return new ContractStorageRead(args.storageSlot, args.currentValue, args.sideEffectCounter); - } - - toBuffer() { - return serializeToBuffer(this.storageSlot, this.currentValue); - } - - static fromBuffer(buffer: Buffer | BufferReader) { - const reader = BufferReader.asReader(buffer); - return new ContractStorageRead(Fr.fromBuffer(reader), Fr.fromBuffer(reader)); - } - - static empty() { - return new ContractStorageRead(Fr.ZERO, Fr.ZERO); - } - - isEmpty() { - return this.storageSlot.isZero() && this.currentValue.isZero(); - } - - toFriendlyJSON() { - return `Slot=${this.storageSlot.toFriendlyJSON()}: ${this.currentValue.toFriendlyJSON()}`; - } -} - -/** - * Contract storage update request for a slot on a specific contract. - * - * Note: Similar to `PublicDataUpdateRequest` but it's from the POV of contract storage so we are not working with - * public data tree leaf index but storage slot index. - */ -export class ContractStorageUpdateRequest { - constructor( - /** - * Storage slot we are updating. - */ - public readonly storageSlot: Fr, - /** - * Old value of the storage slot. - */ - public readonly oldValue: Fr, - /** - * New value of the storage slot. - */ - public readonly newValue: Fr, - /** - * Optional side effect counter tracking position of this event in tx execution. - */ - public readonly sideEffectCounter?: number, - ) {} - - toBuffer() { - return serializeToBuffer(this.storageSlot, this.oldValue, this.newValue); - } - - static fromBuffer(buffer: Buffer | BufferReader) { - const reader = BufferReader.asReader(buffer); - return new ContractStorageUpdateRequest(Fr.fromBuffer(reader), Fr.fromBuffer(reader), Fr.fromBuffer(reader)); - } - - /** - * Create PublicCallRequest from a fields dictionary. - * @param fields - The dictionary. - * @returns A PublicCallRequest object. - */ - static from(fields: FieldsOf): ContractStorageUpdateRequest { - return new ContractStorageUpdateRequest(...ContractStorageUpdateRequest.getFields(fields)); - } - - /** - * Serialize into a field array. Low-level utility. - * @param fields - Object with fields. - * @returns The array. - */ - static getFields(fields: FieldsOf) { - return [fields.storageSlot, fields.oldValue, fields.newValue, fields.sideEffectCounter] as const; - } - - static empty() { - return new ContractStorageUpdateRequest(Fr.ZERO, Fr.ZERO, Fr.ZERO); - } - - isEmpty() { - return this.storageSlot.isZero() && this.oldValue.isZero() && this.newValue.isZero(); - } - - toFriendlyJSON() { - return `Slot=${this.storageSlot.toFriendlyJSON()}: ${this.oldValue.toFriendlyJSON()} => ${this.newValue.toFriendlyJSON()}`; - } -} +import { + ContractStorageRead, + ContractStorageUpdateRequest, + Header, + SideEffect, + SideEffectLinkedToNoteHash, +} from './index.js'; /** * Public inputs to a public circuit. @@ -291,4 +171,45 @@ export class PublicCircuitPublicInputs { toBuffer(): Buffer { return serializeToBuffer(...PublicCircuitPublicInputs.getFields(this)); } + + toFields(): Fr[] { + return serializeToFieldArray(...PublicCircuitPublicInputs.getFields(this)); + } + + static fromFields(fields: Fr[] | FieldReader): PublicCircuitPublicInputs { + const reader = FieldReader.asReader(fields); + + const callContext = CallContext.fromFields(reader); + const argsHash = reader.readField(); + const returnValues = reader.readFieldArray(RETURN_VALUES_LENGTH); + const contractStorageUpdateRequests = reader.readArray( + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, + ContractStorageUpdateRequest, + ); + const contractStorageReads = reader.readArray(MAX_PUBLIC_DATA_READS_PER_CALL, ContractStorageRead); + const publicCallStackHashes = reader.readFieldArray(MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL); + const newCommitments = reader.readArray(MAX_NEW_COMMITMENTS_PER_CALL, SideEffect); + const newNullifiers = reader.readArray(MAX_NEW_NULLIFIERS_PER_CALL, SideEffectLinkedToNoteHash); + const newL2ToL1Msgs = reader.readFieldArray(MAX_NEW_L2_TO_L1_MSGS_PER_CALL); + const unencryptedLogsHash = reader.readFieldArray(NUM_FIELDS_PER_SHA256); + const unencryptedLogPreimagesLength = reader.readField(); + const historicalHeader = Header.fromFields(reader); + const proverAddress = AztecAddress.fromFields(reader); + + return new PublicCircuitPublicInputs( + callContext, + argsHash, + returnValues, + contractStorageUpdateRequests, + contractStorageReads, + publicCallStackHashes, + newCommitments, + newNullifiers, + newL2ToL1Msgs, + unencryptedLogsHash, + unencryptedLogPreimagesLength, + historicalHeader, + proverAddress, + ); + } } diff --git a/yarn-project/circuits.js/src/structs/rollup/append_only_tree_snapshot.ts b/yarn-project/circuits.js/src/structs/rollup/append_only_tree_snapshot.ts index 09216a8a0a1..6f39790a162 100644 --- a/yarn-project/circuits.js/src/structs/rollup/append_only_tree_snapshot.ts +++ b/yarn-project/circuits.js/src/structs/rollup/append_only_tree_snapshot.ts @@ -1,5 +1,5 @@ import { Fr } from '@aztec/foundation/fields'; -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { STRING_ENCODING, UInt32 } from '../shared.js'; @@ -30,7 +30,7 @@ export class AppendOnlyTreeSnapshot { return serializeToBuffer(this.root, this.nextAvailableLeafIndex); } - toFieldArray(): Fr[] { + toFields(): Fr[] { return [this.root, new Fr(this.nextAvailableLeafIndex)]; } @@ -47,6 +47,12 @@ export class AppendOnlyTreeSnapshot { return AppendOnlyTreeSnapshot.fromBuffer(Buffer.from(str, STRING_ENCODING)); } + static fromFields(fields: Fr[] | FieldReader): AppendOnlyTreeSnapshot { + const reader = FieldReader.asReader(fields); + + return new AppendOnlyTreeSnapshot(reader.readField(), Number(reader.readField().toBigInt())); + } + static zero() { return new AppendOnlyTreeSnapshot(Fr.ZERO, 0); } diff --git a/yarn-project/circuits.js/src/structs/state_reference.ts b/yarn-project/circuits.js/src/structs/state_reference.ts index 284d988e07f..1e312739c15 100644 --- a/yarn-project/circuits.js/src/structs/state_reference.ts +++ b/yarn-project/circuits.js/src/structs/state_reference.ts @@ -1,5 +1,5 @@ import { Fr } from '@aztec/foundation/fields'; -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { PartialStateReference } from './partial_state_reference.js'; import { AppendOnlyTreeSnapshot } from './rollup/append_only_tree_snapshot.js'; @@ -21,7 +21,7 @@ export class StateReference { } toFieldArray(): Fr[] { - return [...this.l1ToL2MessageTree.toFieldArray(), ...this.partial.toFieldArray()]; + return [...this.l1ToL2MessageTree.toFields(), ...this.partial.toFields()]; } static fromBuffer(buffer: Buffer | BufferReader): StateReference { @@ -29,6 +29,15 @@ export class StateReference { return new StateReference(reader.readObject(AppendOnlyTreeSnapshot), reader.readObject(PartialStateReference)); } + static fromFields(fields: Fr[] | FieldReader): StateReference { + const reader = FieldReader.asReader(fields); + + const l1ToL2MessageTree = AppendOnlyTreeSnapshot.fromFields(reader); + const partial = PartialStateReference.fromFields(reader); + + return new StateReference(l1ToL2MessageTree, partial); + } + static empty(): StateReference { return new StateReference(AppendOnlyTreeSnapshot.zero(), PartialStateReference.empty()); } diff --git a/yarn-project/circuits.js/src/structs/tx_context.ts b/yarn-project/circuits.js/src/structs/tx_context.ts index f84a47b5b8c..07becc7f95e 100644 --- a/yarn-project/circuits.js/src/structs/tx_context.ts +++ b/yarn-project/circuits.js/src/structs/tx_context.ts @@ -1,83 +1,10 @@ import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { FieldsOf } from '@aztec/foundation/types'; -import { PublicKey } from '../index.js'; -import { AztecAddress, EthAddress, Fr, Point } from './index.js'; - -/** - * Contract deployment data in a TxContext - * Not to be confused with NewContractData. - */ -export class ContractDeploymentData { - /** Ethereum address of the portal contract on L1. */ - public portalContractAddress: EthAddress; - - constructor( - /** Public key of the contract. */ - public publicKey: PublicKey, - /** Hash of the initialization payload. */ - public initializationHash: Fr, - /** Contract class identifier. */ - public contractClassId: Fr, - /** Contract address salt (used when deriving a contract address). */ - public contractAddressSalt: Fr, - /** - * Ethereum address of the portal contract on L1. - * TODO(AD): union type kludge due to cbind compiler having special needs - */ - portalContractAddress: EthAddress | AztecAddress, - ) { - this.portalContractAddress = EthAddress.fromField(portalContractAddress.toField()); - } - - toBuffer() { - return serializeToBuffer( - this.publicKey, - this.initializationHash, - this.contractClassId, - this.contractAddressSalt, - this.portalContractAddress, - ); - } - - /** - * Returns an empty ContractDeploymentData. - * @returns The empty ContractDeploymentData. - */ - public static empty(): ContractDeploymentData { - return new ContractDeploymentData(Point.ZERO, Fr.ZERO, Fr.ZERO, Fr.ZERO, EthAddress.ZERO); - } - - isEmpty() { - return ( - this.publicKey.isZero() && - this.initializationHash.isZero() && - this.contractClassId.isZero() && - this.contractAddressSalt.isZero() && - this.portalContractAddress.isZero() - ); - } - - /** - * Deserializes contract deployment data rom a buffer or reader. - * @param buffer - Buffer to read from. - * @returns The deserialized ContractDeploymentData. - */ - static fromBuffer(buffer: Buffer | BufferReader): ContractDeploymentData { - const reader = BufferReader.asReader(buffer); - return new ContractDeploymentData( - reader.readObject(Point), - Fr.fromBuffer(reader), - Fr.fromBuffer(reader), - Fr.fromBuffer(reader), - new EthAddress(reader.readBytes(32)), - ); - } -} +import { ContractDeploymentData, Fr } from './index.js'; /** * Transaction context. - * @see cpp/src/aztec3/circuits/abis/tx_context.hpp. */ export class TxContext { constructor( diff --git a/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts b/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts index f5ffb8b415d..83023b8505a 100644 --- a/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts @@ -1,18 +1,23 @@ import { AztecAddress, + BatchCall, CompleteAddress, Contract, + ContractArtifact, ContractDeployer, DebugLogger, EthAddress, Fr, PXE, + SignerlessWallet, TxStatus, Wallet, getContractInstanceFromDeployParams, isContractDeployed, } from '@aztec/aztec.js'; -import { TestContractArtifact } from '@aztec/noir-contracts/Test'; +import { siloNullifier } from '@aztec/circuits.js/abis'; +import { StatefulTestContract } from '@aztec/noir-contracts'; +import { TestContract, TestContractArtifact } from '@aztec/noir-contracts/Test'; import { TokenContractArtifact } from '@aztec/noir-contracts/Token'; import { SequencerClient } from '@aztec/sequencer-client'; @@ -195,4 +200,63 @@ describe('e2e_deploy_contract', () => { }); } }, 60_000); + + // Tests calling a private function in an uninitialized and undeployed contract. Note that + // it still requires registering the contract artifact and instance locally in the pxe. + test.each(['as entrypoint', 'from an account contract'] as const)( + 'executes a function in an undeployed contract %s', + async kind => { + const testWallet = kind === 'as entrypoint' ? new SignerlessWallet(pxe) : wallet; + const contract = await registerContract(testWallet, TestContract); + const receipt = await contract.methods.emit_nullifier(10).send().wait({ debug: true }); + const expected = siloNullifier(contract.address, new Fr(10)); + expect(receipt.debugInfo?.newNullifiers[1]).toEqual(expected); + }, + ); + + // Tests privately initializing an undeployed contract. Also requires pxe registration in advance. + test.each(['as entrypoint', 'from an account contract'] as const)( + 'privately initializes an undeployed contract contract %s', + async kind => { + const testWallet = kind === 'as entrypoint' ? new SignerlessWallet(pxe) : wallet; + const owner = await registerRandomAccount(pxe); + const initArgs: StatefulContractCtorArgs = [owner, 42]; + const contract = await registerContract(testWallet, StatefulTestContract, initArgs); + await contract.methods + .constructor(...initArgs) + .send() + .wait(); + expect(await contract.methods.summed_values(owner).view()).toEqual(42n); + }, + ); + + // Tests privately initializing multiple undeployed contracts on the same tx through an account contract. + it('initializes multiple undeployed contracts in a single tx', async () => { + const owner = await registerRandomAccount(pxe); + const initArgs: StatefulContractCtorArgs[] = [42, 52].map(value => [owner, value]); + const contracts = await Promise.all(initArgs.map(args => registerContract(wallet, StatefulTestContract, args))); + const calls = contracts.map((c, i) => c.methods.constructor(...initArgs[i]).request()); + await new BatchCall(wallet, calls).send().wait(); + expect(await contracts[0].methods.summed_values(owner).view()).toEqual(42n); + expect(await contracts[1].methods.summed_values(owner).view()).toEqual(52n); + }); }); + +type StatefulContractCtorArgs = Parameters; + +async function registerRandomAccount(pxe: PXE): Promise { + const { completeAddress: owner, privateKey } = CompleteAddress.fromRandomPrivateKey(); + await pxe.registerAccount(privateKey, owner.partialAddress); + return owner.address; +} + +type ContractArtifactClass = { + at(address: AztecAddress, wallet: Wallet): Promise; + artifact: ContractArtifact; +}; + +async function registerContract(wallet: Wallet, contractArtifact: ContractArtifactClass, args: any[] = []) { + const instance = getContractInstanceFromDeployParams(contractArtifact.artifact, args); + await wallet.addContracts([{ artifact: contractArtifact.artifact, instance }]); + return contractArtifact.at(instance.address, wallet); +} diff --git a/yarn-project/foundation/src/serialize/serialize.ts b/yarn-project/foundation/src/serialize/serialize.ts index 5ab8a8ac5c4..d8eef08529b 100644 --- a/yarn-project/foundation/src/serialize/serialize.ts +++ b/yarn-project/foundation/src/serialize/serialize.ts @@ -1,5 +1,6 @@ import { toBigIntBE, toBufferBE } from '@aztec/foundation/bigint-buffer'; +import { Fr } from '../fields/fields.js'; import { numToUInt32BE } from './free_funcs.js'; /** @@ -118,6 +119,22 @@ export type Bufferable = } | Bufferable[]; +/** A type that can be converted to a Field or a Field array. */ +export type Fieldeable = + | Fr + | boolean + | number + | bigint + | { + /** Serialize to a field. */ + toField: () => Fr; + } + | { + /** Serialize to an array of fields. */ + toFields: () => Fr[]; + } + | Fieldeable[]; + /** * Checks whether an object implements the toBuffer32 method. * @param obj - The object to check. @@ -168,6 +185,29 @@ export function serializeToBufferArray(...objs: Bufferable[]): Buffer[] { return ret; } +/** + * Serializes a list of objects contiguously. + * @param objs - Objects to serialize. + * @returns An array of fields with the concatenation of all fields. + */ +export function serializeToFieldArray(...objs: Fieldeable[]): Fr[] { + let ret: Fr[] = []; + for (const obj of objs) { + if (Array.isArray(obj)) { + ret = [...ret, ...serializeToFieldArray(...obj)]; + } else if (obj instanceof Fr) { + ret.push(obj); + } else if (typeof obj === 'boolean' || typeof obj === 'number' || typeof obj === 'bigint') { + ret.push(new Fr(obj)); + } else if ('toFields' in obj) { + ret = [...ret, ...obj.toFields()]; + } else { + ret.push(obj.toField()); + } + } + return ret; +} + /** * Serializes a list of objects contiguously. * @param objs - Objects to serialize. diff --git a/yarn-project/merkle-tree/src/interfaces/indexed_tree.ts b/yarn-project/merkle-tree/src/interfaces/indexed_tree.ts index 651f734f09e..13c14f059dc 100644 --- a/yarn-project/merkle-tree/src/interfaces/indexed_tree.ts +++ b/yarn-project/merkle-tree/src/interfaces/indexed_tree.ts @@ -1,5 +1,5 @@ +import { SiblingPath } from '@aztec/circuit-types'; import { IndexedTreeLeaf, IndexedTreeLeafPreimage } from '@aztec/foundation/trees'; -import { SiblingPath } from '@aztec/types/membership'; import { AppendOnlyTree } from './append_only_tree.js'; diff --git a/yarn-project/merkle-tree/src/interfaces/merkle_tree.ts b/yarn-project/merkle-tree/src/interfaces/merkle_tree.ts index 209656f885d..752c07c3142 100644 --- a/yarn-project/merkle-tree/src/interfaces/merkle_tree.ts +++ b/yarn-project/merkle-tree/src/interfaces/merkle_tree.ts @@ -1,4 +1,4 @@ -import { SiblingPath } from '@aztec/types/membership'; +import { SiblingPath } from '@aztec/circuit-types'; /** * Defines the interface for a source of sibling paths. diff --git a/yarn-project/merkle-tree/src/snapshots/append_only_snapshot.ts b/yarn-project/merkle-tree/src/snapshots/append_only_snapshot.ts index 06d6e4b3194..3096688b806 100644 --- a/yarn-project/merkle-tree/src/snapshots/append_only_snapshot.ts +++ b/yarn-project/merkle-tree/src/snapshots/append_only_snapshot.ts @@ -1,6 +1,6 @@ +import { SiblingPath } from '@aztec/circuit-types'; import { AztecKVStore, AztecMap } from '@aztec/kv-store'; import { Hasher } from '@aztec/types/interfaces'; -import { SiblingPath } from '@aztec/types/membership'; import { AppendOnlyTree } from '../interfaces/append_only_tree.js'; import { TreeBase } from '../tree_base.js'; diff --git a/yarn-project/merkle-tree/src/snapshots/base_full_snapshot.ts b/yarn-project/merkle-tree/src/snapshots/base_full_snapshot.ts index 5f1e250c569..01518cfd3f2 100644 --- a/yarn-project/merkle-tree/src/snapshots/base_full_snapshot.ts +++ b/yarn-project/merkle-tree/src/snapshots/base_full_snapshot.ts @@ -1,5 +1,5 @@ +import { SiblingPath } from '@aztec/circuit-types'; import { AztecKVStore, AztecMap } from '@aztec/kv-store'; -import { SiblingPath } from '@aztec/types/membership'; import { TreeBase } from '../tree_base.js'; import { TreeSnapshot, TreeSnapshotBuilder } from './snapshot_builder.js'; diff --git a/yarn-project/merkle-tree/src/snapshots/snapshot_builder.ts b/yarn-project/merkle-tree/src/snapshots/snapshot_builder.ts index 98bbb9051d8..5feb1a2ef00 100644 --- a/yarn-project/merkle-tree/src/snapshots/snapshot_builder.ts +++ b/yarn-project/merkle-tree/src/snapshots/snapshot_builder.ts @@ -1,5 +1,5 @@ +import { SiblingPath } from '@aztec/circuit-types'; import { IndexedTreeLeafPreimage } from '@aztec/foundation/trees'; -import { SiblingPath } from '@aztec/types/membership'; /** * An interface for a tree that can record snapshots of its contents. diff --git a/yarn-project/merkle-tree/src/sparse_tree/sparse_tree.test.ts b/yarn-project/merkle-tree/src/sparse_tree/sparse_tree.test.ts index 2de1db97d34..21ce5c2b767 100644 --- a/yarn-project/merkle-tree/src/sparse_tree/sparse_tree.test.ts +++ b/yarn-project/merkle-tree/src/sparse_tree/sparse_tree.test.ts @@ -1,7 +1,7 @@ +import { SiblingPath } from '@aztec/circuit-types'; import { createDebugLogger } from '@aztec/foundation/log'; import { AztecKVStore, AztecLmdbStore } from '@aztec/kv-store'; import { Hasher } from '@aztec/types/interfaces'; -import { SiblingPath } from '@aztec/types/membership'; import { randomBytes } from 'crypto'; diff --git a/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts b/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts index 316bc2a62df..68a3f588ae2 100644 --- a/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts +++ b/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts @@ -1,10 +1,10 @@ +import { SiblingPath } from '@aztec/circuit-types'; import { TreeInsertionStats } from '@aztec/circuit-types/stats'; import { toBufferBE } from '@aztec/foundation/bigint-buffer'; import { Timer } from '@aztec/foundation/timer'; import { IndexedTreeLeaf, IndexedTreeLeafPreimage } from '@aztec/foundation/trees'; import { AztecKVStore, AztecMap } from '@aztec/kv-store'; import { Hasher } from '@aztec/types/interfaces'; -import { SiblingPath } from '@aztec/types/membership'; import { BatchInsertionResult, IndexedTree, LowLeafWitnessData, PreimageFactory } from '../interfaces/indexed_tree.js'; import { IndexedTreeSnapshotBuilder } from '../snapshots/indexed_tree_snapshot.js'; diff --git a/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree.test.ts b/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree.test.ts index 1e793daf656..6543283df8c 100644 --- a/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree.test.ts +++ b/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree.test.ts @@ -1,3 +1,4 @@ +import { SiblingPath } from '@aztec/circuit-types'; import { Fr, NullifierLeaf, @@ -8,7 +9,6 @@ import { import { toBufferBE } from '@aztec/foundation/bigint-buffer'; import { AztecKVStore, AztecLmdbStore } from '@aztec/kv-store'; import { Hasher } from '@aztec/types/interfaces'; -import { SiblingPath } from '@aztec/types/membership'; import { INITIAL_LEAF, MerkleTree, Pedersen, loadTree, newTree } from '../../index.js'; import { treeTestSuite } from '../../test/test_suite.js'; diff --git a/yarn-project/merkle-tree/src/test/standard_based_test_suite.ts b/yarn-project/merkle-tree/src/test/standard_based_test_suite.ts index bfd95e7b349..f970a9c89aa 100644 --- a/yarn-project/merkle-tree/src/test/standard_based_test_suite.ts +++ b/yarn-project/merkle-tree/src/test/standard_based_test_suite.ts @@ -1,6 +1,6 @@ +import { SiblingPath } from '@aztec/circuit-types'; import { AztecKVStore, AztecLmdbStore } from '@aztec/kv-store'; import { Hasher } from '@aztec/types/interfaces'; -import { SiblingPath } from '@aztec/types/membership'; import { randomBytes } from 'crypto'; diff --git a/yarn-project/merkle-tree/src/test/test_suite.ts b/yarn-project/merkle-tree/src/test/test_suite.ts index fd2d5b038fb..c7fdd51b103 100644 --- a/yarn-project/merkle-tree/src/test/test_suite.ts +++ b/yarn-project/merkle-tree/src/test/test_suite.ts @@ -1,6 +1,6 @@ +import { SiblingPath } from '@aztec/circuit-types'; import { AztecKVStore, AztecLmdbStore } from '@aztec/kv-store'; import { Hasher } from '@aztec/types/interfaces'; -import { SiblingPath } from '@aztec/types/membership'; import { Pedersen } from '../index.js'; import { AppendOnlyTree } from '../interfaces/append_only_tree.js'; diff --git a/yarn-project/merkle-tree/src/tree_base.ts b/yarn-project/merkle-tree/src/tree_base.ts index 972cdf19cc5..21baacdc041 100644 --- a/yarn-project/merkle-tree/src/tree_base.ts +++ b/yarn-project/merkle-tree/src/tree_base.ts @@ -1,8 +1,8 @@ +import { SiblingPath } from '@aztec/circuit-types'; import { toBigIntLE, toBufferLE } from '@aztec/foundation/bigint-buffer'; import { DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { AztecKVStore, AztecMap, AztecSingleton } from '@aztec/kv-store'; import { Hasher } from '@aztec/types/interfaces'; -import { SiblingPath } from '@aztec/types/membership'; import { HasherWithStats } from './hasher_with_stats.js'; import { MerkleTree } from './interfaces/merkle_tree.js'; diff --git a/yarn-project/noir-compiler/src/contract-interface-gen/typescript.ts b/yarn-project/noir-compiler/src/contract-interface-gen/typescript.ts index 2054e9ddc7e..73c0c253623 100644 --- a/yarn-project/noir-compiler/src/contract-interface-gen/typescript.ts +++ b/yarn-project/noir-compiler/src/contract-interface-gen/typescript.ts @@ -169,7 +169,7 @@ function generateAbiStatement(name: string, artifactImportPath: string) { * @returns The corresponding ts code. */ export function generateTypescriptContractInterface(input: ContractArtifact, artifactImportPath?: string) { - const methods = input.functions.filter(f => f.name !== 'constructor' && !f.isInternal).map(generateMethod); + const methods = input.functions.filter(f => !f.isInternal).map(generateMethod); const deploy = artifactImportPath && generateDeploy(input); const ctor = artifactImportPath && generateConstructor(input.name); const at = artifactImportPath && generateAt(input.name); diff --git a/yarn-project/noir-contracts/contracts/stateful_test_contract/src/main.nr b/yarn-project/noir-contracts/contracts/stateful_test_contract/src/main.nr index cfca700e66e..b1e22e2eb42 100644 --- a/yarn-project/noir-contracts/contracts/stateful_test_contract/src/main.nr +++ b/yarn-project/noir-contracts/contracts/stateful_test_contract/src/main.nr @@ -1,6 +1,9 @@ // A contract used for testing a random hodgepodge of small features from simulator and end-to-end tests. contract StatefulTest { - use dep::aztec::protocol_types::address::AztecAddress; + use dep::aztec::protocol_types::{ + address::AztecAddress, + abis::function_selector::FunctionSelector, + }; use dep::std::option::Option; use dep::value_note::{ balance_utils, @@ -47,8 +50,8 @@ contract StatefulTest { #[aztec(private)] fn constructor(owner: AztecAddress, value: Field) { - let loc = storage.notes.at(owner); - increment(loc, value, owner); + let selector = FunctionSelector::from_signature("create_note((Field),Field)"); + let _res = context.call_private_function(context.this_address(), selector, [owner.to_field(), value]); } #[aztec(private)] diff --git a/yarn-project/noir-contracts/package.json b/yarn-project/noir-contracts/package.json index 38bf26e463b..6845b1f90ff 100644 --- a/yarn-project/noir-contracts/package.json +++ b/yarn-project/noir-contracts/package.json @@ -14,7 +14,7 @@ "formatting": "run -T prettier --check ./src && run -T eslint ./src", "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src", "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules $(yarn bin jest) --passWithNoTests", - "build:contracts": "./scripts/compile.sh && ./scripts/transpile.sh && ./scripts/generate-types.sh" + "build:contracts": "./scripts/compile.sh && ./scripts/generate-types.sh" }, "inherits": [ "../package.common.json", diff --git a/yarn-project/noir-contracts/package.local.json b/yarn-project/noir-contracts/package.local.json index 9ea766d3f11..6fb912b0da8 100644 --- a/yarn-project/noir-contracts/package.local.json +++ b/yarn-project/noir-contracts/package.local.json @@ -1,7 +1,7 @@ { "scripts": { "build": "yarn clean && yarn build:contracts && tsc -b", - "build:contracts": "./scripts/compile.sh && ./scripts/transpile.sh && ./scripts/generate-types.sh", + "build:contracts": "./scripts/compile.sh && ./scripts/generate-types.sh", "clean": "rm -rf ./dest .tsbuildinfo ./src ./target" } } diff --git a/yarn-project/noir-protocol-circuits/src/crates/private-kernel-lib/src/private_kernel_init.nr b/yarn-project/noir-protocol-circuits/src/crates/private-kernel-lib/src/private_kernel_init.nr index 04d5d60c0e7..3fe0658498d 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/private-kernel-lib/src/private_kernel_init.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/private-kernel-lib/src/private_kernel_init.nr @@ -429,47 +429,46 @@ mod tests { builder.failed(); } - // #[test(should_fail_with="computed_contract_tree_root does not match purported_contract_tree_root")] - fn private_function_incorrect_contract_leaf_index_fails() { + #[test(should_fail_with="computed contract address does not match expected one")] + fn private_function_incorrect_function_leaf_index_fails() { let mut builder = PrivateKernelInitInputsBuilder::new(); - // Set the leaf index of the contract leaf to a wrong value (the correct value + 1). - // let leaf_index = builder.private_call.contract_leaf_membership_witness.leaf_index; - // builder.private_call.contract_leaf_membership_witness.leaf_index = leaf_index + 1; + // Set the leaf index of the function leaf to a wrong value (the correct value + 1). + let leaf_index = builder.private_call.function_leaf_membership_witness.leaf_index; + builder.private_call.function_leaf_membership_witness.leaf_index = leaf_index + 1; builder.failed(); } - // #[test(should_fail_with="computed_contract_tree_root does not match purported_contract_tree_root")] - fn private_function_incorrect_contract_leaf_sibling_path_fails() { + #[test(should_fail_with="computed contract address does not match expected one")] + fn private_function_incorrect_function_leaf_sibling_path_fails() { let mut builder = PrivateKernelInitInputsBuilder::new(); // Set the first value of the sibling path to a wrong value (the correct value + 1). - // let sibling_path_0 = builder.private_call.contract_leaf_membership_witness.sibling_path[0]; - // builder.private_call.contract_leaf_membership_witness.sibling_path[0] = sibling_path_0 + 1; + let sibling_path_0 = builder.private_call.function_leaf_membership_witness.sibling_path[0]; + builder.private_call.function_leaf_membership_witness.sibling_path[0] = sibling_path_0 + 1; builder.failed(); } - // #[test(should_fail_with="computed_contract_tree_root does not match purported_contract_tree_root")] - fn private_function_incorrect_function_leaf_index_fails() { + #[test(should_fail_with="computed contract address does not match expected one")] + fn private_function_incorrect_contract_class_preimage_fails() { let mut builder = PrivateKernelInitInputsBuilder::new(); - - // Set the leaf index of the function leaf to a wrong value (the correct value + 1). - let leaf_index = builder.private_call.function_leaf_membership_witness.leaf_index; - builder.private_call.function_leaf_membership_witness.leaf_index = leaf_index + 1; - + builder.private_call.contract_class_artifact_hash = builder.private_call.contract_class_artifact_hash + 1; builder.failed(); } - // #[test(should_fail_with="computed_contract_tree_root does not match purported_contract_tree_root")] - fn private_function_incorrect_function_leaf_sibling_path_fails() { + #[test(should_fail_with="computed contract address does not match expected one")] + fn private_function_incorrect_partial_address_preimage_fails() { let mut builder = PrivateKernelInitInputsBuilder::new(); + builder.private_call.salted_initialization_hash.inner = builder.private_call.salted_initialization_hash.inner + 1; + builder.failed(); + } - // Set the first value of the sibling path to a wrong value (the correct value + 1). - let sibling_path_0 = builder.private_call.function_leaf_membership_witness.sibling_path[0]; - builder.private_call.function_leaf_membership_witness.sibling_path[0] = sibling_path_0 + 1; - + #[test(should_fail_with="computed contract address does not match expected one")] + fn private_function_incorrect_address_preimage_fails() { + let mut builder = PrivateKernelInitInputsBuilder::new(); + builder.private_call.public_keys_hash.inner = builder.private_call.public_keys_hash.inner + 1; builder.failed(); } diff --git a/yarn-project/noir-protocol-circuits/src/crates/private-kernel-lib/src/private_kernel_inner.nr b/yarn-project/noir-protocol-circuits/src/crates/private-kernel-lib/src/private_kernel_inner.nr index 47e378306c3..b8f7a7fe114 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/private-kernel-lib/src/private_kernel_inner.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/private-kernel-lib/src/private_kernel_inner.nr @@ -32,7 +32,6 @@ impl PrivateKernelInputsInner { let this_call_stack_item = self.private_call.call_stack_item; let function_data = this_call_stack_item.function_data; assert(function_data.is_private, "Private kernel circuit can only execute a private function"); - assert(function_data.is_constructor == false, "A constructor must be executed as the first tx in the recursion"); assert(self.previous_kernel.public_inputs.is_private, "Can only verify a private kernel snark in the private kernel circuit"); } @@ -176,47 +175,46 @@ mod tests { builder.failed(); } - // #[test(should_fail_with = "computed_contract_tree_root does not match purported_contract_tree_root")] - fn private_function_incorrect_contract_leaf_index_fails() { + #[test(should_fail_with="computed contract address does not match expected one")] + fn private_function_incorrect_function_leaf_index_fails() { let mut builder = PrivateKernelInnerInputsBuilder::new(); - // Set the leaf index of the contract leaf to a wrong value (the correct value + 1). - // let leaf_index = builder.private_call.contract_leaf_membership_witness.leaf_index; - // builder.private_call.contract_leaf_membership_witness.leaf_index = leaf_index + 1; + // Set the leaf index of the function leaf to a wrong value (the correct value + 1). + let leaf_index = builder.private_call.function_leaf_membership_witness.leaf_index; + builder.private_call.function_leaf_membership_witness.leaf_index = leaf_index + 1; builder.failed(); } - // #[test(should_fail_with = "computed_contract_tree_root does not match purported_contract_tree_root")] - fn private_function_incorrect_contract_leaf_sibling_path_fails() { + #[test(should_fail_with="computed contract address does not match expected one")] + fn private_function_incorrect_function_leaf_sibling_path_fails() { let mut builder = PrivateKernelInnerInputsBuilder::new(); // Set the first value of the sibling path to a wrong value (the correct value + 1). - // let sibling_path_0 = builder.private_call.contract_leaf_membership_witness.sibling_path[0]; - // builder.private_call.contract_leaf_membership_witness.sibling_path[0] = sibling_path_0 + 1; + let sibling_path_0 = builder.private_call.function_leaf_membership_witness.sibling_path[0]; + builder.private_call.function_leaf_membership_witness.sibling_path[0] = sibling_path_0 + 1; builder.failed(); } - // #[test(should_fail_with = "computed_contract_tree_root does not match purported_contract_tree_root")] - fn private_function_incorrect_function_leaf_index_fails() { + #[test(should_fail_with="computed contract address does not match expected one")] + fn private_function_incorrect_contract_class_preimage_fails() { let mut builder = PrivateKernelInnerInputsBuilder::new(); - - // Set the leaf index of the function leaf to a wrong value (the correct value + 1). - let leaf_index = builder.private_call.function_leaf_membership_witness.leaf_index; - builder.private_call.function_leaf_membership_witness.leaf_index = leaf_index + 1; - + builder.private_call.contract_class_artifact_hash = builder.private_call.contract_class_artifact_hash + 1; builder.failed(); } - // #[test(should_fail_with = "computed_contract_tree_root does not match purported_contract_tree_root")] - fn private_function_incorrect_function_leaf_sibling_path_fails() { + #[test(should_fail_with="computed contract address does not match expected one")] + fn private_function_incorrect_partial_address_preimage_fails() { let mut builder = PrivateKernelInnerInputsBuilder::new(); + builder.private_call.salted_initialization_hash.inner = builder.private_call.salted_initialization_hash.inner + 1; + builder.failed(); + } - // Set the first value of the sibling path to a wrong value (the correct value + 1). - let sibling_path_0 = builder.private_call.function_leaf_membership_witness.sibling_path[0]; - builder.private_call.function_leaf_membership_witness.sibling_path[0] = sibling_path_0 + 1; - + #[test(should_fail_with="computed contract address does not match expected one")] + fn private_function_incorrect_address_preimage_fails() { + let mut builder = PrivateKernelInnerInputsBuilder::new(); + builder.private_call.public_keys_hash.inner = builder.private_call.public_keys_hash.inner + 1; builder.failed(); } @@ -543,15 +541,6 @@ mod tests { builder.failed(); } - #[test(should_fail_with="A constructor must be executed as the first tx in the recursion")] - fn private_function_is_constructor_fails() { - let mut builder = PrivateKernelInnerInputsBuilder::new(); - - builder.private_call.function_data.is_constructor = true; - - builder.failed(); - } - #[test(should_fail_with="Can only verify a private kernel snark in the private kernel circuit")] fn previous_kernel_is_private_false_fails() { let mut builder = PrivateKernelInnerInputsBuilder::new(); diff --git a/yarn-project/noir-protocol-circuits/src/crates/types/src/constants.nr b/yarn-project/noir-protocol-circuits/src/crates/types/src/constants.nr index baeddd68ea5..40a15506cdf 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/types/src/constants.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/types/src/constants.nr @@ -104,11 +104,11 @@ global CONTRACT_DEPLOYMENT_DATA_LENGTH: Field = 6; // Change this ONLY if you have changed the PrivateCircuitPublicInputs structure. // In other words, if the structure/size of the public inputs of a function call changes then we should change this // constant as well CALL_PRIVATE_FUNCTION_RETURN_SIZE and PRIVATE_CIRCUIT_PUBLIC_INPUTS_HASH_INPUT_LENGTH -global PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH: Field = 200; +global PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH: Field = 204; global CONTRACT_STORAGE_UPDATE_REQUEST_LENGTH: Field = 3; global CONTRACT_STORAGE_READ_LENGTH: Field = 2; // Change this ONLY if you have changed the PublicCircuitPublicInputs structure. -global PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH: Field = 190; +global PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH: Field = 201; global GET_NOTES_ORACLE_RETURN_LENGTH: Field = 674; global CALL_PRIVATE_FUNCTION_RETURN_SIZE: Field = 210; global PUBLIC_CIRCUIT_PUBLIC_INPUTS_HASH_INPUT_LENGTH: Field = 98; diff --git a/yarn-project/noir-protocol-circuits/src/type_conversion.test.ts b/yarn-project/noir-protocol-circuits/src/type_conversion.test.ts index 96295999765..562d9aa09e0 100644 --- a/yarn-project/noir-protocol-circuits/src/type_conversion.test.ts +++ b/yarn-project/noir-protocol-circuits/src/type_conversion.test.ts @@ -59,7 +59,7 @@ describe('Noir<>Circuits.js type conversion test suite', () => { new Fr(29n), new Fr(30n), new Fr(31n), - AztecAddress.random(), + EthAddress.random(), ); it('should map contract deployment data', () => { diff --git a/yarn-project/pxe/src/contract_data_oracle/private_functions_tree.ts b/yarn-project/pxe/src/contract_data_oracle/private_functions_tree.ts index aabda34ccf4..5ae3ea6b0d1 100644 --- a/yarn-project/pxe/src/contract_data_oracle/private_functions_tree.ts +++ b/yarn-project/pxe/src/contract_data_oracle/private_functions_tree.ts @@ -43,7 +43,7 @@ export class PrivateFunctionsTree { if (!artifact) { throw new Error( `Unknown function. Selector ${selector.toString()} not found in the artifact of contract ${this.contract.instance.address.toString()}. Expected one of: ${this.contract.functions - .map(f => f.selector.toString()) + .map(f => `${f.name} (${f.selector.toString()})`) .join(', ')}`, ); } diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index a7b2a4dcd81..57d53e21ad4 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -8,7 +8,15 @@ import { PublicDataWitness, StateInfoProvider, } from '@aztec/circuit-types'; -import { AztecAddress, CompleteAddress, EthAddress, Fr, FunctionSelector, Header } from '@aztec/circuits.js'; +import { + AztecAddress, + CompleteAddress, + EthAddress, + Fr, + FunctionSelector, + Header, + L1_TO_L2_MSG_TREE_HEIGHT, +} from '@aztec/circuits.js'; import { FunctionArtifactWithDebugMetadata } from '@aztec/foundation/abi'; import { createDebugLogger } from '@aztec/foundation/log'; @@ -118,16 +126,12 @@ export class SimulatorOracle implements DBOracle { * @returns A promise that resolves to the message data, a sibling path and the * index of the message in the l1ToL2MessageTree */ - async getL1ToL2Message(msgKey: Fr): Promise { + async getL1ToL2Message(msgKey: Fr): Promise> { const messageAndIndex = await this.stateInfoProvider.getL1ToL2MessageAndIndex(msgKey); - const message = messageAndIndex.message.toFieldArray(); + const message = messageAndIndex.message; const index = messageAndIndex.index; const siblingPath = await this.stateInfoProvider.getL1ToL2MessageSiblingPath('latest', index); - return { - message, - siblingPath: siblingPath.toFieldArray(), - index, - }; + return new MessageLoadOracleInputs(message, index, siblingPath); } /** diff --git a/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts b/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts index 5bb68a03ec3..89d30f0c393 100644 --- a/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts @@ -3,6 +3,7 @@ import { ExtendedContractData, FunctionCall, FunctionL2Logs, + SiblingPath, SimulationError, Tx, TxL2Logs, @@ -35,7 +36,6 @@ import { } from '@aztec/circuits.js/factories'; import { makeTuple } from '@aztec/foundation/array'; import { padArrayEnd, times } from '@aztec/foundation/collection'; -import { SiblingPath } from '@aztec/types/membership'; import { MerkleTreeOperations, TreeInfo } from '@aztec/world-state'; import { MockProxy, mock } from 'jest-mock-extended'; diff --git a/yarn-project/sequencer-client/src/simulator/public_executor.ts b/yarn-project/sequencer-client/src/simulator/public_executor.ts index 9015fb06bc4..7ee16c8e0bd 100644 --- a/yarn-project/sequencer-client/src/simulator/public_executor.ts +++ b/yarn-project/sequencer-client/src/simulator/public_executor.ts @@ -1,6 +1,13 @@ import { CommitmentsDB, MessageLoadOracleInputs, PublicContractsDB, PublicStateDB } from '@aztec/acir-simulator'; import { ContractDataSource, ExtendedContractData, L1ToL2MessageSource, MerkleTreeId, Tx } from '@aztec/circuit-types'; -import { AztecAddress, EthAddress, Fr, FunctionSelector, PublicDataTreeLeafPreimage } from '@aztec/circuits.js'; +import { + AztecAddress, + EthAddress, + Fr, + FunctionSelector, + L1_TO_L2_MSG_TREE_HEIGHT, + PublicDataTreeLeafPreimage, +} from '@aztec/circuits.js'; import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/abis'; import { MerkleTreeOperations } from '@aztec/world-state'; @@ -144,17 +151,16 @@ export class WorldStatePublicDB implements PublicStateDB { export class WorldStateDB implements CommitmentsDB { constructor(private db: MerkleTreeOperations, private l1ToL2MessageSource: L1ToL2MessageSource) {} - public async getL1ToL2Message(messageKey: Fr): Promise { + public async getL1ToL2Message(messageKey: Fr): Promise> { // todo: #697 - make this one lookup. const message = await this.l1ToL2MessageSource.getConfirmedL1ToL2Message(messageKey); const index = (await this.db.findLeafIndex(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, messageKey.toBuffer()))!; - const siblingPath = await this.db.getSiblingPath(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, index); - - return { - message: message.toFieldArray(), - siblingPath: siblingPath.toFieldArray(), + const siblingPath = await this.db.getSiblingPath( + MerkleTreeId.L1_TO_L2_MESSAGE_TREE, index, - }; + ); + + return new MessageLoadOracleInputs(message, index, siblingPath); } public async getCommitmentIndex(commitment: Fr): Promise { diff --git a/yarn-project/types/src/index.ts b/yarn-project/types/src/index.ts index 6815c4130c4..a3df311a469 100644 --- a/yarn-project/types/src/index.ts +++ b/yarn-project/types/src/index.ts @@ -1,2 +1 @@ export * from './interfaces/index.js'; -export * from './sibling-path/index.js'; diff --git a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts index 8c0f0e23351..684e46a84a6 100644 --- a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts +++ b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts @@ -1,10 +1,9 @@ -import { L2Block, L2BlockSource, MerkleTreeId } from '@aztec/circuit-types'; +import { L2Block, L2BlockSource, MerkleTreeId, SiblingPath } from '@aztec/circuit-types'; import { Fr } from '@aztec/circuits.js'; import { createDebugLogger } from '@aztec/foundation/log'; import { sleep } from '@aztec/foundation/sleep'; import { AztecKVStore, AztecLmdbStore } from '@aztec/kv-store'; import { INITIAL_LEAF, Pedersen } from '@aztec/merkle-tree'; -import { SiblingPath } from '@aztec/types/membership'; import { jest } from '@jest/globals'; import { mock } from 'jest-mock-extended'; diff --git a/yarn-project/world-state/src/world-state-db/merkle_tree_operations.ts b/yarn-project/world-state/src/world-state-db/merkle_tree_operations.ts index 8a59832253a..560906b4d69 100644 --- a/yarn-project/world-state/src/world-state-db/merkle_tree_operations.ts +++ b/yarn-project/world-state/src/world-state-db/merkle_tree_operations.ts @@ -1,9 +1,8 @@ -import { L2Block, MerkleTreeId } from '@aztec/circuit-types'; +import { L2Block, MerkleTreeId, SiblingPath } from '@aztec/circuit-types'; import { Header, NullifierLeafPreimage, StateReference } from '@aztec/circuits.js'; import { createDebugLogger } from '@aztec/foundation/log'; import { IndexedTreeLeafPreimage } from '@aztec/foundation/trees'; import { BatchInsertionResult } from '@aztec/merkle-tree'; -import { SiblingPath } from '@aztec/types/membership'; /** * Type alias for the nullifier tree ID. diff --git a/yarn-project/world-state/src/world-state-db/merkle_tree_operations_facade.ts b/yarn-project/world-state/src/world-state-db/merkle_tree_operations_facade.ts index f36513f6e78..26c3cceb896 100644 --- a/yarn-project/world-state/src/world-state-db/merkle_tree_operations_facade.ts +++ b/yarn-project/world-state/src/world-state-db/merkle_tree_operations_facade.ts @@ -1,8 +1,7 @@ -import { L2Block, MerkleTreeId } from '@aztec/circuit-types'; +import { L2Block, MerkleTreeId, SiblingPath } from '@aztec/circuit-types'; import { Header, NullifierLeafPreimage, StateReference } from '@aztec/circuits.js'; import { IndexedTreeLeafPreimage } from '@aztec/foundation/trees'; import { BatchInsertionResult } from '@aztec/merkle-tree'; -import { SiblingPath } from '@aztec/types/membership'; import { MerkleTreeDb } from './merkle_tree_db.js'; import { HandleL2BlockResult, MerkleTreeOperations, TreeInfo } from './merkle_tree_operations.js'; diff --git a/yarn-project/world-state/src/world-state-db/merkle_tree_snapshot_operations_facade.ts b/yarn-project/world-state/src/world-state-db/merkle_tree_snapshot_operations_facade.ts index 79e7a01d16f..8c645dc6ca7 100644 --- a/yarn-project/world-state/src/world-state-db/merkle_tree_snapshot_operations_facade.ts +++ b/yarn-project/world-state/src/world-state-db/merkle_tree_snapshot_operations_facade.ts @@ -1,8 +1,7 @@ -import { MerkleTreeId } from '@aztec/circuit-types'; +import { MerkleTreeId, SiblingPath } from '@aztec/circuit-types'; import { AppendOnlyTreeSnapshot, Fr, Header, PartialStateReference, StateReference } from '@aztec/circuits.js'; import { IndexedTreeLeafPreimage } from '@aztec/foundation/trees'; import { BatchInsertionResult, IndexedTreeSnapshot, TreeSnapshot } from '@aztec/merkle-tree'; -import { SiblingPath } from '@aztec/types/membership'; import { MerkleTreeDb } from './merkle_tree_db.js'; import { HandleL2BlockResult, MerkleTreeOperations, TreeInfo } from './merkle_tree_operations.js'; diff --git a/yarn-project/world-state/src/world-state-db/merkle_trees.ts b/yarn-project/world-state/src/world-state-db/merkle_trees.ts index 3a55636c273..102946f05b2 100644 --- a/yarn-project/world-state/src/world-state-db/merkle_trees.ts +++ b/yarn-project/world-state/src/world-state-db/merkle_trees.ts @@ -1,4 +1,4 @@ -import { L2Block, MerkleTreeId } from '@aztec/circuit-types'; +import { L2Block, MerkleTreeId, SiblingPath } from '@aztec/circuit-types'; import { ARCHIVE_HEIGHT, AppendOnlyTreeSnapshot, @@ -37,7 +37,6 @@ import { newTree, } from '@aztec/merkle-tree'; import { Hasher } from '@aztec/types/interfaces'; -import { SiblingPath } from '@aztec/types/membership'; import { INITIAL_NULLIFIER_TREE_SIZE, INITIAL_PUBLIC_DATA_TREE_SIZE, MerkleTreeDb } from './merkle_tree_db.js'; import { HandleL2BlockResult, IndexedTreeId, MerkleTreeOperations, TreeInfo } from './merkle_tree_operations.js';