diff --git a/barretenberg/.gitrepo b/barretenberg/.gitrepo index c05a1fd68a0..4f5d442d28c 100644 --- a/barretenberg/.gitrepo +++ b/barretenberg/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/AztecProtocol/barretenberg branch = master - commit = c76755f0cba150633949c40506aa53dd4c7e9ec8 - parent = 32a546cbabee6570a3e84bbd15cf0ab3dd58f9a3 + commit = 350fdbe3462299fa87b2e40113448bcbb9754e24 + parent = 9325f6ff987022da1a4dabb771781cdc999af18e method = merge cmdver = 0.4.6 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 bd36912f2f1..d0eb9d767d6 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -1,6 +1,7 @@ #include "acir_format.hpp" #include "barretenberg/common/log.hpp" #include "barretenberg/common/throw_or_abort.hpp" +#include "barretenberg/dsl/acir_format/ivc_recursion_constraint.hpp" #include "barretenberg/stdlib/plonk_recursion/aggregation_state/aggregation_state.hpp" #include "barretenberg/stdlib/primitives/field/field_conversion.hpp" #include "barretenberg/stdlib_circuit_builders/mega_circuit_builder.hpp" @@ -490,6 +491,17 @@ MegaCircuitBuilder create_kernel_circuit(AcirFormat& constraint_system, ASSERT(false); } + // If no witness is provided, populate the VK and public inputs in the recursion constraint with dummy values so + // that the present kernel circuit is constructed correctly. (Used for constructing VKs without witnesses). + if (witness.empty()) { + // Create stdlib representations of each {proof, vkey} pair to be recursively verified + for (auto [constraint, queue_entry] : + zip_view(constraint_system.ivc_recursion_constraints, ivc.verification_queue)) { + + populate_dummy_vk_in_constraint(circuit, queue_entry.honk_verification_key, constraint.key); + } + } + // Construct a stdlib verification key for each constraint based on the verification key witness indices therein std::vector> stdlib_verification_keys; stdlib_verification_keys.reserve(constraint_system.ivc_recursion_constraints.size()); @@ -497,7 +509,6 @@ MegaCircuitBuilder create_kernel_circuit(AcirFormat& constraint_system, stdlib_verification_keys.push_back(std::make_shared( StdlibVerificationKey::from_witness_indices(circuit, constraint.key))); } - // Create stdlib representations of each {proof, vkey} pair to be recursively verified ivc.instantiate_stdlib_verification_queue(circuit, stdlib_verification_keys); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ivc_recursion_constraint.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ivc_recursion_constraint.cpp new file mode 100644 index 00000000000..4100773a486 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ivc_recursion_constraint.cpp @@ -0,0 +1,165 @@ +#include "ivc_recursion_constraint.hpp" +#include "barretenberg/flavor/flavor.hpp" +#include "barretenberg/plonk_honk_shared/types/aggregation_object_type.hpp" +#include "barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.hpp" +#include "barretenberg/stdlib/plonk_recursion/aggregation_state/aggregation_state.hpp" +#include "barretenberg/stdlib/primitives/bigfield/constants.hpp" +#include "barretenberg/stdlib/primitives/curves/bn254.hpp" +#include "barretenberg/stdlib_circuit_builders/ultra_recursive_flavor.hpp" +#include "proof_surgeon.hpp" +#include "recursion_constraint.hpp" + +namespace acir_format { + +using namespace bb; +using field_ct = stdlib::field_t; + +ClientIVC create_mock_ivc_from_constraints(const std::vector& constraints) +{ + ClientIVC ivc; + ivc.trace_settings.structure = TraceStructure::SMALL_TEST; + + for (const auto& constraint : constraints) { + if (static_cast(PROOF_TYPE::OINK) == constraint.proof_type) { + mock_ivc_oink_accumulation(ivc, constraint.public_inputs.size()); + } else if (static_cast(PROOF_TYPE::PG) == constraint.proof_type) { + // perform equivalent mocking for PG accumulation + } + } + + return ivc; +} + +/** + * @brief Populate an IVC instance with data that mimics the state after accumulating the first app (which runs the oink + * prover) + *@details Mock state consists a mock verification queue entry of type OINK (proof, VK) and a mocked merge proof + * + * @param ivc + * @param num_public_inputs_app num pub inputs in accumulated app, excluding fixed components, e.g. pairing points + */ +void mock_ivc_oink_accumulation(ClientIVC& ivc, size_t num_public_inputs_app) +{ + ClientIVC::VerifierInputs oink_entry = + acir_format::create_dummy_vkey_and_proof_oink(ivc.trace_settings, num_public_inputs_app); + ivc.verification_queue.emplace_back(oink_entry); + ivc.merge_verification_queue.emplace_back(acir_format::create_dummy_merge_proof()); + ivc.initialized = true; +} + +/** + * @brief Create a mock oink proof and VK that have the correct structure but are not necessarily valid + * + */ +ClientIVC::VerifierInputs create_dummy_vkey_and_proof_oink(const TraceSettings& trace_settings, + const size_t num_public_inputs = 0) +{ + using Flavor = MegaFlavor; + using VerificationKey = ClientIVC::VerificationKey; + using FF = bb::fr; + + MegaArith::TraceBlocks blocks; + blocks.set_fixed_block_sizes(trace_settings); + blocks.compute_offsets(/*is_structured=*/true); + size_t structured_dyadic_size = blocks.get_structured_dyadic_size(); + size_t pub_inputs_offset = blocks.pub_inputs.trace_offset; + + ClientIVC::VerifierInputs verifier_inputs; + verifier_inputs.type = ClientIVC::QUEUE_TYPE::OINK; + + FF mock_val(5); + + auto mock_commitment = curve::BN254::AffineElement::one() * mock_val; + std::vector mock_commitment_frs = field_conversion::convert_to_bn254_frs(mock_commitment); + + // Set proof preamble (metadata plus public inputs) + size_t total_num_public_inputs = num_public_inputs + bb::PAIRING_POINT_ACCUMULATOR_SIZE; + verifier_inputs.proof.emplace_back(structured_dyadic_size); + verifier_inputs.proof.emplace_back(total_num_public_inputs); + verifier_inputs.proof.emplace_back(pub_inputs_offset); + for (size_t i = 0; i < total_num_public_inputs; ++i) { + verifier_inputs.proof.emplace_back(0); + } + + // Witness polynomial commitments + for (size_t i = 0; i < Flavor::NUM_WITNESS_ENTITIES; ++i) { + for (const FF& val : mock_commitment_frs) { + verifier_inputs.proof.emplace_back(val); + } + } + + // Set relevant VK metadata and commitments + verifier_inputs.honk_verification_key = std::make_shared(); + verifier_inputs.honk_verification_key->circuit_size = structured_dyadic_size; + verifier_inputs.honk_verification_key->num_public_inputs = total_num_public_inputs; + verifier_inputs.honk_verification_key->pub_inputs_offset = blocks.pub_inputs.trace_offset; // must be set correctly + verifier_inputs.honk_verification_key->contains_pairing_point_accumulator = true; + for (auto& commitment : verifier_inputs.honk_verification_key->get_all()) { + commitment = mock_commitment; + } + + return verifier_inputs; +} + +/** + * @brief Create a mock merge proof which has the correct structure but is not necessarily valid + * + * @return ClientIVC::MergeProof + */ +ClientIVC::MergeProof create_dummy_merge_proof() +{ + using FF = bb::fr; + + std::vector proof; + + FF mock_val(5); + auto mock_commitment = curve::BN254::AffineElement::one() * mock_val; + std::vector mock_commitment_frs = field_conversion::convert_to_bn254_frs(mock_commitment); + + // There are 12 entities in the merge protocol (4 columns x 3 components; aggregate transcript, previous aggregate + // transcript, current transcript contribution) + const size_t NUM_TRANSCRIPT_ENTITIES = 12; + + // Transcript poly commitments + for (size_t i = 0; i < NUM_TRANSCRIPT_ENTITIES; ++i) { + for (const FF& val : mock_commitment_frs) { + proof.emplace_back(val); + } + } + // Transcript poly evaluations + for (size_t i = 0; i < NUM_TRANSCRIPT_ENTITIES; ++i) { + proof.emplace_back(mock_val); + } + // Batched KZG quotient commitment + for (const FF& val : mock_commitment_frs) { + proof.emplace_back(val); + } + + return proof; +} + +/** + * @brief Populate VK witness fields from a recursion constraint from a provided VerificationKey + * + * @param builder + * @param mock_verification_key + * @param key_witness_indices + */ +void populate_dummy_vk_in_constraint(MegaCircuitBuilder& builder, + const std::shared_ptr& mock_verification_key, + std::vector& key_witness_indices) +{ + using Flavor = MegaFlavor; + using FF = Flavor::FF; + + // Convert the VerificationKey to fields + std::vector mock_vk_fields = mock_verification_key->to_field_elements(); + ASSERT(mock_vk_fields.size() == key_witness_indices.size()); + + // Add the fields to the witness and set the key witness indices accordingly + for (auto [witness_idx, value] : zip_view(key_witness_indices, mock_vk_fields)) { + witness_idx = builder.add_variable(value); + } +} + +} // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ivc_recursion_constraint.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ivc_recursion_constraint.hpp new file mode 100644 index 00000000000..5ab74ca80e6 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ivc_recursion_constraint.hpp @@ -0,0 +1,27 @@ +#pragma once +#include "barretenberg/client_ivc/client_ivc.hpp" +#include "barretenberg/dsl/acir_format/recursion_constraint.hpp" +#include "barretenberg/stdlib/primitives/bigfield/bigfield.hpp" +#include + +namespace acir_format { + +using namespace bb; + +// TODO(https://github.com/AztecProtocol/barretenberg/issues/1148): logic in this file is incomplete. See issue for +// details. + +ClientIVC create_mock_ivc_from_constraints(const std::vector& constraints); + +void mock_ivc_oink_accumulation(ClientIVC& ivc, size_t num_public_inputs_app = 0); + +ClientIVC::VerifierInputs create_dummy_vkey_and_proof_oink(const TraceSettings& trace_settings, + const size_t num_public_inputs); + +ClientIVC::MergeProof create_dummy_merge_proof(); + +void populate_dummy_vk_in_constraint(MegaCircuitBuilder& builder, + const std::shared_ptr& mock_verification_key, + std::vector& key_witness_indices); + +} // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ivc_recursion_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ivc_recursion_constraint.test.cpp index 285106061be..50da7e166b5 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ivc_recursion_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ivc_recursion_constraint.test.cpp @@ -1,3 +1,4 @@ +#include "barretenberg/dsl/acir_format/ivc_recursion_constraint.hpp" #include "acir_format.hpp" #include "acir_format_mocks.hpp" #include "barretenberg/client_ivc/client_ivc.hpp" @@ -75,39 +76,11 @@ class IvcRecursionConstraintTest : public ::testing::Test { }; } - /** - * @brief Create an arithmetic constraint fixing the first public input witness to it's present value - * @details Meant to mimic the "business logic" of the aztec kernel. Used to facilitate failure testing since this - * will lead to failure of the kernel circuit to verify if a different proof witness is used in the business logic - * VS the recursive verification logic. - * - * @param public_inputs Witness indices of public inputs of some proof to be constrained - * @param witness - * @return ArithmeticConstraint - */ - static ArithmeticConstraint create_public_input_value_constraint(const std::vector& public_inputs, - const SlabVector& witness) - { - const uint32_t pub_input_idx = public_inputs[0]; - const FF pub_input_val = witness[pub_input_idx]; - return { - .a = pub_input_idx, - .b = 0, - .c = 0, - .q_m = 0, - .q_l = -1, - .q_r = 0, - .q_o = 0, - .q_c = pub_input_val, - }; - } - /** * @brief Generate an acir program {constraints, witness} for a mock kernel * @details The IVC contains and internal verification queue that contains proofs to be recursively verified. * Construct an AcirProgram with a RecursionConstraint for each entry in the ivc verification queue. (In practice - * these constraints would come directly from calls to verify_proof in noir). Also add mock "business logic" which - * simply enforces some constraint on the public inputs of the proof. + * these constraints would come directly from calls to verify_proof in noir). * @note This method needs the number of public inputs in each proof-to-be-verified so they can be extracted and * provided separately as is required in the acir constraint system. * @@ -130,14 +103,9 @@ class IvcRecursionConstraintTest : public ::testing::Test { verification_queue[idx], program.witness, inner_circuit_num_pub_inputs[idx])); } - // Add some mock kernel "business logic" which simply fixes one of the public inputs to a particular value - ArithmeticConstraint pub_input_constraint = - create_public_input_value_constraint(ivc_recursion_constraints[0].public_inputs, program.witness); - // Construct a constraint system containing the business logic and ivc recursion constraints program.constraints.varnum = static_cast(program.witness.size()); program.constraints.num_acir_opcodes = static_cast(ivc_recursion_constraints.size()); - program.constraints.poly_triple_constraints = { pub_input_constraint }; program.constraints.ivc_recursion_constraints = ivc_recursion_constraints; program.constraints.original_opcode_indices = create_empty_original_opcode_indices(); mock_opcode_indices(program.constraints); @@ -214,64 +182,110 @@ TEST_F(IvcRecursionConstraintTest, AccumulateFour) EXPECT_TRUE(ivc.prove_and_verify()); } -/** - * @brief Demonstrate failure of the IVC if the proof witness differs between those encoded in the constraint system - * (i.e. those used in the noir program) and those used in constructing the recursive verifiers internally - * @brief The idea is to construct two valid but unique verification queue entries of the form {proof, vkey}. One is - * used to construct the acir constraint system and the other is used to construct the recursive verification logic - * internally in the IVC. Since the proof/vkey witnesses in the constraint system are asserted equal to those used to - * construct the recursive verifiers, the use of different verification queue witnesses should result in failure as long - * as they were used in some nontrivial way in the main logic of the kernel. (Specifically, failure results from a - * failure of the "business logic" of the kernel which constrains one of the public inputs to a particular value). - */ -TEST_F(IvcRecursionConstraintTest, AccumulateTwoFailure) +// Test generation of "init" kernel VK via dummy IVC data +TEST_F(IvcRecursionConstraintTest, GenerateVK) { - // Accumulate a single app in order to construct a valid verification queue entry {proof, vkey} to be used later on. - // Demonstrate that it is indeed valid by completing the IVC with a kernel (which recursively verifies the entry) - // then proving and verifying the full IVC. - VerifierInputs alternative_verification_queue_entry; + const TraceSettings trace_settings{ TraceStructure::SMALL_TEST }; + + // First, construct the kernel VK by running the full IVC (accumulate one app and one kernel) + std::shared_ptr expected_kernel_vk; + size_t num_app_public_inputs = 0; { ClientIVC ivc; - ivc.trace_settings.structure = TraceStructure::SMALL_TEST; + ivc.trace_settings = trace_settings; - // construct and accumulate a mock app circuit with a single unique public input + // Construct and accumulate mock app_circuit Builder app_circuit = construct_mock_app_circuit(ivc); ivc.accumulate(app_circuit); + num_app_public_inputs = app_circuit.public_inputs.size(); - // Save the single entry in the verification queue at this point - alternative_verification_queue_entry = ivc.verification_queue[0]; + // Construct and accumulate kernel consisting only of the kernel completion logic + AcirProgram program = construct_mock_kernel_program(ivc.verification_queue, { num_app_public_inputs }); + Builder kernel = acir_format::create_kernel_circuit(program.constraints, ivc, program.witness); + ivc.accumulate(kernel); + expected_kernel_vk = ivc.verification_queue.back().honk_verification_key; + } + + // Now, construct the kernel VK by mocking the post app accumulation state of the IVC + std::shared_ptr kernel_vk; + { + ClientIVC ivc; + ivc.trace_settings = trace_settings; + + acir_format::mock_ivc_oink_accumulation(ivc, num_app_public_inputs - bb::PAIRING_POINT_ACCUMULATOR_SIZE); - // Construct and accumulate kernel_0 - size_t num_pub_inputs_app = app_circuit.public_inputs.size(); - AcirProgram program_0 = construct_mock_kernel_program(ivc.verification_queue, { num_pub_inputs_app }); - Builder kernel_0 = acir_format::create_kernel_circuit(program_0.constraints, ivc, program_0.witness); - ivc.accumulate(kernel_0); + // Construct kernel consisting only of the kernel completion logic + AcirProgram program = construct_mock_kernel_program(ivc.verification_queue, { num_app_public_inputs }); + Builder kernel = acir_format::create_kernel_circuit(program.constraints, ivc); + // WORKTODO: this would normally happen in accumulate() + kernel.add_pairing_point_accumulator(stdlib::recursion::init_default_agg_obj_indices(kernel)); - EXPECT_TRUE(ivc.prove_and_verify()); + auto proving_key = std::make_shared>(kernel, trace_settings); + MegaProver prover(proving_key); + kernel_vk = std::make_shared(prover.proving_key->proving_key); } - // Repeat a similar IVC but use the alternative queue entry just created to provide different (but independently - // valid) witnesses during constraint system construction VS recursive verifier construction. + // PCS verification keys will not match so set to null before comparing + kernel_vk->pcs_verification_key = nullptr; + expected_kernel_vk->pcs_verification_key = nullptr; - ClientIVC ivc; - ivc.trace_settings.structure = TraceStructure::SMALL_TEST; + EXPECT_EQ(*kernel_vk.get(), *expected_kernel_vk.get()); +} - // construct and accumulate a mock app circuit with a single unique public input - Builder app_circuit = construct_mock_app_circuit(ivc); - ivc.accumulate(app_circuit); +// Test generation of "init" kernel VK via dummy IVC data +TEST_F(IvcRecursionConstraintTest, GenerateVKFromConstraints) +{ + const TraceSettings trace_settings{ TraceStructure::SMALL_TEST }; - // Construct kernel_0 - AcirProgram program_0 = construct_mock_kernel_program(ivc.verification_queue, { app_circuit.public_inputs.size() }); + // First, construct the kernel VK by running the full IVC (accumulate one app and one kernel) + std::shared_ptr expected_kernel_vk; + size_t num_app_public_inputs = 0; + { + ClientIVC ivc; + ivc.trace_settings = trace_settings; - // Replace the existing verification queue entry that was used to construct the acir constraint system for the - // kernel with a different (but valid, as shown above) set of inputs - ivc.verification_queue[0] = alternative_verification_queue_entry; - Builder kernel_0 = acir_format::create_kernel_circuit(program_0.constraints, ivc, program_0.witness); + // Construct and accumulate mock app_circuit + Builder app_circuit = construct_mock_app_circuit(ivc); + ivc.accumulate(app_circuit); + num_app_public_inputs = app_circuit.public_inputs.size(); - // The witness should fail to check due to the business logic of the kernel failing - EXPECT_FALSE(CircuitChecker::check(kernel_0)); - ivc.accumulate(kernel_0); + // Construct and accumulate kernel consisting only of the kernel completion logic + AcirProgram program = construct_mock_kernel_program(ivc.verification_queue, { num_app_public_inputs }); + Builder kernel = acir_format::create_kernel_circuit(program.constraints, ivc, program.witness); - // The full IVC should of course also fail to verify since we've accumulated an invalid witness for the kernel - EXPECT_FALSE(ivc.prove_and_verify()); -} + ivc.accumulate(kernel); + expected_kernel_vk = ivc.verification_queue.back().honk_verification_key; + } + + // Now, construct the kernel VK by mocking the post app accumulation state of the IVC + std::shared_ptr kernel_vk; + { + ClientIVC ivc; + ivc.trace_settings = trace_settings; + + // Construct kernel consisting only of the kernel completion logic + acir_format::mock_ivc_oink_accumulation(ivc, num_app_public_inputs - bb::PAIRING_POINT_ACCUMULATOR_SIZE); + AcirProgram program = construct_mock_kernel_program(ivc.verification_queue, { num_app_public_inputs }); + program.witness = {}; // erase witness to mimic VK construction context + + // Create a mock IVC instance from the IVC recursion constraints in the kernel program + ClientIVC mock_ivc = create_mock_ivc_from_constraints(program.constraints.ivc_recursion_constraints); + + // Create a kernel circuit from the kernel program and the mocked IVC + Builder kernel = acir_format::create_kernel_circuit(program.constraints, mock_ivc); + // Note: adding pairing point normally happens in accumulate() + kernel.add_pairing_point_accumulator(stdlib::recursion::init_default_agg_obj_indices(kernel)); + + // Manually construct the VK for the kernel circuit + auto proving_key = std::make_shared>(kernel, ivc.trace_settings); + MegaProver prover(proving_key); + kernel_vk = std::make_shared(prover.proving_key->proving_key); + } + + // PCS verification keys will not match so set to null before comparing + kernel_vk->pcs_verification_key = nullptr; + expected_kernel_vk->pcs_verification_key = nullptr; + + // Compare the VK constructed via running the IVc with the one constructed via mocking + EXPECT_EQ(*kernel_vk.get(), *expected_kernel_vk.get()); +} \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/proof_surgeon.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/proof_surgeon.hpp index 224d344ae8a..0b2d1768bca 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/proof_surgeon.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/proof_surgeon.hpp @@ -149,6 +149,7 @@ class ProofSurgeon { const size_t num_public_inputs) { // Extract all public inputs except for those corresponding to the aggregation object + ASSERT(num_public_inputs >= bb::PAIRING_POINT_ACCUMULATOR_SIZE); const size_t num_public_inputs_to_extract = num_public_inputs - bb::PAIRING_POINT_ACCUMULATOR_SIZE; std::vector public_input_witnesses = cut_public_inputs_from_proof(proof_witnesses, num_public_inputs_to_extract); diff --git a/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/execution_trace_usage_tracker.hpp b/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/execution_trace_usage_tracker.hpp index 53db481ff59..f1798fa3c76 100644 --- a/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/execution_trace_usage_tracker.hpp +++ b/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/execution_trace_usage_tracker.hpp @@ -67,7 +67,7 @@ struct ExecutionTraceUsageTracker { // The active ranges for the databus and lookup relations (both based on log-deriv lookup argument) must // incorporate both the lookup/read gate blocks as well as the rows containing the data that is being read. // Update the corresponding ranges accordingly. (Note: tables are constructed at the 'bottom' of the trace). - size_t dyadic_circuit_size = circuit.get_circuit_subgroup_size(fixed_sizes.get_total_structured_size()); + size_t dyadic_circuit_size = fixed_sizes.get_structured_dyadic_size(); active_ranges.busread.first = 0; // databus data is stored at the top of the trace active_ranges.busread.second = std::max(max_databus_size, active_ranges.busread.second); active_ranges.lookup.first = std::min(dyadic_circuit_size - max_tables_size, active_ranges.lookup.first); diff --git a/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/mega_arithmetization.hpp b/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/mega_arithmetization.hpp index 7cb03ae310a..8a19c73cc43 100644 --- a/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/mega_arithmetization.hpp +++ b/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/mega_arithmetization.hpp @@ -3,6 +3,7 @@ #include "barretenberg/common/ref_vector.hpp" #include "barretenberg/common/zip_view.hpp" #include "barretenberg/ecc/curves/bn254/fr.hpp" +#include "barretenberg/numeric/bitop/get_msb.hpp" #include "barretenberg/plonk_honk_shared/arithmetization/arithmetization.hpp" #include "barretenberg/plonk_honk_shared/types/circuit_type.hpp" @@ -309,13 +310,18 @@ template class MegaArith { info(""); } - size_t get_total_structured_size() + size_t get_structured_dyadic_size() { - size_t total_size = 0; + size_t total_size = 1; // start at 1 because the 0th row is unused for selectors for Honk for (auto block : this->get()) { total_size += block.get_fixed_size(); } - return total_size; + + auto log2_n = static_cast(numeric::get_msb(total_size)); + if ((1UL << log2_n) != (total_size)) { + ++log2_n; + } + return 1UL << log2_n; } bool operator==(const TraceBlocks& other) const = default; diff --git a/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/ultra_arithmetization.hpp b/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/ultra_arithmetization.hpp index a188d2faef5..d4443a11dbd 100644 --- a/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/ultra_arithmetization.hpp +++ b/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/ultra_arithmetization.hpp @@ -1,6 +1,7 @@ #pragma once #include "barretenberg/common/ref_vector.hpp" +#include "barretenberg/numeric/bitop/get_msb.hpp" #include "barretenberg/plonk_honk_shared/arithmetization/arithmetization.hpp" namespace bb { @@ -167,13 +168,18 @@ template class UltraArith { info("overflow :\t", this->overflow.size()); } - size_t get_total_structured_size() + size_t get_structured_dyadic_size() { size_t total_size = 1; // start at 1 because the 0th row is unused for selectors for Honk for (auto block : this->get()) { total_size += block.get_fixed_size(); } - return total_size; + + auto log2_n = static_cast(numeric::get_msb(total_size)); + if ((1UL << log2_n) != (total_size)) { + ++log2_n; + } + return 1UL << log2_n; } bool operator==(const TraceBlocks& other) const = default; diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/decider_proving_key.hpp b/barretenberg/cpp/src/barretenberg/ultra_honk/decider_proving_key.hpp index dd8c8593b84..1de641beec9 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/decider_proving_key.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/decider_proving_key.hpp @@ -317,11 +317,7 @@ template class DeciderProvingKey_ { * @brief Compute dyadic size based on a structured trace with fixed block size * */ - size_t compute_structured_dyadic_size(Circuit& circuit) - { - size_t minimum_size = circuit.blocks.get_total_structured_size(); - return circuit.get_circuit_subgroup_size(minimum_size); - } + size_t compute_structured_dyadic_size(Circuit& circuit) { return circuit.blocks.get_structured_dyadic_size(); } void construct_databus_polynomials(Circuit&) requires IsGoblinFlavor; diff --git a/docs/docs/reference/developer_references/smart_contract_reference/storage/private_state.md b/docs/docs/reference/developer_references/smart_contract_reference/storage/private_state.md index dfd7d5530ef..cfe794984f6 100644 --- a/docs/docs/reference/developer_references/smart_contract_reference/storage/private_state.md +++ b/docs/docs/reference/developer_references/smart_contract_reference/storage/private_state.md @@ -210,8 +210,6 @@ The `insert_from_public` allow public function to insert notes into private stor The usage is similar to using the `insert` method with the difference that this one is called in public functions. -#include_code insert_from_public /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust - ### `pop_notes` This function pops (gets, removes and returns) the notes the account has access to based on the provided filter. diff --git a/docs/docs/tutorials/codealong/contract_tutorials/token_contract.md b/docs/docs/tutorials/codealong/contract_tutorials/token_contract.md index c9b9410d416..5461f33d380 100644 --- a/docs/docs/tutorials/codealong/contract_tutorials/token_contract.md +++ b/docs/docs/tutorials/codealong/contract_tutorials/token_contract.md @@ -69,7 +69,7 @@ These are functions that have transparent logic, will execute in a publicly veri - `set_minter` enables accounts to be added / removed from the approved minter list - `mint_public` enables tokens to be minted to the public balance of an account - `mint_private` enables tokens to be minted to the private balance of an account (with some caveats we will dig into) -- `shield` enables tokens to be moved from a public balance to a private balance, not necessarily the same account (step 1 of a 2 step process) +- `transfer_to_public` enables tokens to be moved from a public balance to a private balance, not necessarily the same account (step 1 of a 2 step process) - `transfer_public` enables users to transfer tokens from one account's public balance to another account's public balance - `burn_public` enables users to burn tokens @@ -77,8 +77,6 @@ These are functions that have transparent logic, will execute in a publicly veri These are functions that have private logic and will be executed on user devices to maintain privacy. The only data that is submitted to the network is a proof of correct execution, new data commitments and nullifiers, so users will not reveal which contract they are interacting with or which function they are executing. The only information that will be revealed publicly is that someone executed a private transaction on Aztec. -- `redeem_shield` enables accounts to claim tokens that have been made private via `mint_private` or `shield` by providing the secret -- `transfer_to_public` enables an account to send tokens from their private balance to any other account's public balance - `transfer` enables an account to send tokens from their private balance to another account's private balance - `transfer_from` enables an account to send tokens from another account's private balance to another account's private balance - `cancel_authwit` enables an account to cancel an authorization to spend tokens @@ -160,7 +158,6 @@ Reading through the storage variables: - `minters` is a mapping of Aztec addresses in public state. This will store whether an account is an approved minter on the contract. - `balances` is a mapping of private balances. Private balances are stored in a `PrivateSet` of `UintNote`s. The balance is the sum of all of an account's `UintNote`s. - `total_supply` is an unsigned integer (max 128 bit value) stored in public state and represents the total number of tokens minted. -- `pending_shields` is a `PrivateSet` of `TransparentNote`s stored in private state. What is stored publicly is a set of commitments to `TransparentNote`s. - `public_balances` is a mapping of Aztec addresses in public state and represents the publicly viewable balances of accounts. - `symbol`, `name`, and `decimals` are similar in meaning to ERC20 tokens on Ethereum. @@ -178,7 +175,7 @@ This function sets the creator of the contract (passed as `msg_sender` from the Public functions are declared with the `#[public]` macro above the function name. -As described in the [execution contexts section above](#execution-contexts), public function logic and transaction information is transparent to the world. Public functions update public state, but can be used to prepare data to be used in a private context, as we will go over below (e.g. see the [shield](#shield) function). +As described in the [execution contexts section above](#execution-contexts), public function logic and transaction information is transparent to the world. Public functions update public state, but can be used to finalize prepared in a private context (partial notes flow). Storage is referenced as `storage.variable`. @@ -204,17 +201,23 @@ First, storage is initialized. Then the function checks that the `msg_sender` is #### `mint_private` -This public function allows an account approved in the public `minters` mapping to create new private tokens that can be claimed by anyone that has the pre-image to the `secret_hash`. +This public function allows an account approved in the public `minters` mapping to create new private tokens. -First, public storage is initialized. Then it checks that the `msg_sender` is an approved minter. Then a new `TransparentNote` is created with the specified `amount` and `secret_hash`. You can read the details of the `TransparentNote` in the `types.nr` file [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/noir-contracts/contracts/token_contract/src/types.nr#L61). The `amount` is added to the existing public `total_supply` and the storage value is updated. Then the new `TransparentNote` is added to the `pending_shields` using the `insert_from_public` function, which is accessible on the `PrivateSet` type. Then it's ready to be claimed by anyone with the `secret_hash` pre-image using the `redeem_shield` function. +First, partial note is prepared by the call to `_prepare_transfer_to_private` for the minted tokens recipient. Then a public call to `_finalize_mint_to_private_unsafe` is enqueued while `msg_sender`, `amount` and the `hiding_point_slot` are passed in via arguments. Since we set `from` to `msg_sender` here the usage of the unsafe function is safe. The enqueued call then checks the minter permissions of `from` and it finalizes the partial note for `to`. -#include_code mint_private /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust +#include_code mint_to_private /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust -#### `shield` +#### `transfer_to_private` -This public function enables an account to stage tokens from it's `public_balance` to be claimed as a private `TransparentNote` by any account that has the pre-image to the `secret_hash`. +This public function enables an account to send tokens from its `public_balance` to a private balance of an arbitrary recipient. -First, storage is initialized. Then it checks whether the calling contract (`context.msg_sender`) matches the account that the funds will be debited from. +First a partial note is prepared then a call to `_finalize_transfer_to_private_unsafe` is enqueued. The enqueued public call subtracts the `amount` from public balance of `msg_sender` and finalizes the partial note with the `amount`. + +#include_code transfer_to_private /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust + +#### `transfer_public` + +This public function enables public transfers between Aztec accounts. The sender's public balance will be debited the specified `amount` and the recipient's public balances will be credited with that amount. ##### Authorizing token spends @@ -224,14 +227,6 @@ If the `msg_sender` is the same as the account to debit tokens from, the authori It returns `1` to indicate successful execution. -#include_code shield /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust - -#### `transfer_public` - -This public function enables public transfers between Aztec accounts. The sender's public balance will be debited the specified `amount` and the recipient's public balances will be credited with that amount. - -After storage is initialized, the [authorization flow specified above](#authorizing-token-spends) is checked. Then the sender and recipient's balances are updated and saved to storage. - #include_code transfer_public /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust #### `burn_public` @@ -248,26 +243,16 @@ Private functions are declared with the `#[private]` macro above the function na ```rust #[private] - fn redeem_shield( + fn transfer_to_public( ``` As described in the [execution contexts section above](#execution-contexts), private function logic and transaction information is hidden from the world and is executed on user devices. Private functions update private state, but can pass data to the public execution context (e.g. see the [`transfer_to_public`](#transfer_to_public) function). Storage is referenced as `storage.variable`. -#### `redeem_shield` - -This private function enables an account to move tokens from a `TransparentNote` in the `pending_shields` mapping to a `UintNote` in private `balances`. The `UintNote` will be associated with a nullifier key, so any account that knows this key can spend this note. - -Going through the function logic, first the `secret_hash` is generated from the given secret. This ensures that only the entity possessing the secret can use it to redeem the note. Following this, a `TransparentNote` is retrieved from the set, using the provided amount and secret. The note is subsequently removed from the set, allowing it to be redeemed only once. The recipient's private balance is then increased using the `increment` helper function from the `value_note` [library (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/value-note/src/utils.nr). - -The function returns `1` to indicate successful execution. - -#include_code redeem_shield /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust - #### `transfer_to_public` -This private function enables un-shielding of private `UintNote`s stored in `balances` to any Aztec account's `public_balance`. +This private function enables transferring of private balance (`UintNote` stored in `balances`) to any Aztec account's `public_balance`. After initializing storage, the function checks that the `msg_sender` is authorized to spend tokens. See [the Authorizing token spends section](#authorizing-token-spends) above for more detail--the only difference being that `assert_valid_message_for` is modified to work specifically in the private context. After the authorization check, the sender's private balance is decreased using the `decrement` helper function for the `value_note` library. Then it stages a public function call on this contract ([`_increase_public_balance`](#_increase_public_balance)) to be executed in the [public execution phase](#execution-contexts) of transaction execution. `_increase_public_balance` is marked as an `internal` function, so can only be called by this token contract. @@ -303,7 +288,7 @@ Internal functions are functions that can only be called by this contract. The f #### `_increase_public_balance` -This function is called from [`transfer_to_public`](#transfer_to_public). The account's private balance is decremented in `shield` and the public balance is increased in this function. +This function is called from [`transfer_to_public`](#transfer_to_public). The account's private balance is decremented in `transfer_to_public` and the public balance is increased in this function. #include_code increase_public_balance /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust diff --git a/noir-projects/aztec-nr/.gitrepo b/noir-projects/aztec-nr/.gitrepo index df6f10a1617..e14975ed4fe 100644 --- a/noir-projects/aztec-nr/.gitrepo +++ b/noir-projects/aztec-nr/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/AztecProtocol/aztec-nr branch = master - commit = 94008214c5d3678ad62d01c1160b71404809806c + commit = 24f191b111fe626acbbb161760c162b9ffed27f2 method = merge cmdver = 0.4.6 - parent = fcdf11d871833074e4ebb6e064e1ca64edda6ea9 + parent = 146f49f41e8ec9437bb3ceea088b172ce5dee7e2 diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr index 003a6d49613..471ede3d9d5 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr @@ -41,6 +41,8 @@ impl PrivateSet where Note: NoteInterface + NullifiableNote, { + // TODO: This function is still around because of a stale blacklist token. It should most likely be nuked. If you + // need this functionality use partial notes instead. // docs:start:insert_from_public pub fn insert_from_public(self, note: &mut Note) { create_note_hash_from_public(self.context, self.storage_slot, note); diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index d7d33b87c64..d22ae39e5cf 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -22,11 +22,8 @@ contract Token { context::{PrivateCallInterface, PrivateContext}, encrypted_logs::{ encrypted_event_emission::encode_and_encrypt_event_unconstrained, - encrypted_note_emission::{ - encode_and_encrypt_note, encode_and_encrypt_note_unconstrained, - }, + encrypted_note_emission::encode_and_encrypt_note_unconstrained, }, - hash::compute_secret_hash, keys::getters::get_public_keys, macros::{ events::event, @@ -35,11 +32,9 @@ contract Token { }, oracle::random::random, prelude::{ - AztecAddress, FunctionSelector, Map, NoteGetterOptions, PrivateSet, PublicContext, - PublicMutable, SharedImmutable, + AztecAddress, FunctionSelector, Map, PublicContext, PublicMutable, SharedImmutable, }, protocol_types::{point::Point, traits::Serialize}, - utils::comparison::Comparator, }; use dep::uint_note::uint_note::UintNote; @@ -51,7 +46,7 @@ contract Token { }; // docs:end:import_authwit - use crate::types::{balance_set::BalanceSet, transparent_note::TransparentNote}; + use crate::types::balance_set::BalanceSet; // docs:end::imports @@ -85,9 +80,6 @@ contract Token { balances: Map, Context>, // docs:end:storage_balances total_supply: PublicMutable, - // docs:start:storage_pending_shields - pending_shields: PrivateSet, - // docs:end:storage_pending_shields public_balances: Map, Context>, symbol: SharedImmutable, name: SharedImmutable, @@ -111,6 +103,7 @@ contract Token { // docs:end:initialize_decimals } // docs:end:constructor + // docs:start:set_admin #[public] fn set_admin(new_admin: AztecAddress) { @@ -120,6 +113,7 @@ contract Token { // docs:end:write_admin } // docs:end:set_admin + #[public] #[view] fn public_get_name() -> FieldCompressedString { @@ -131,16 +125,19 @@ contract Token { fn private_get_name() -> FieldCompressedString { storage.name.read_private() } + #[public] #[view] fn public_get_symbol() -> pub FieldCompressedString { storage.symbol.read_public() } + #[private] #[view] fn private_get_symbol() -> pub FieldCompressedString { storage.symbol.read_private() } + #[public] #[view] fn public_get_decimals() -> pub u8 { @@ -155,6 +152,7 @@ contract Token { storage.decimals.read_private() // docs:end:read_decimals_private } + // docs:start:admin #[public] #[view] @@ -162,6 +160,7 @@ contract Token { storage.admin.read().to_field() } // docs:end:admin + // docs:start:is_minter #[public] #[view] @@ -169,6 +168,7 @@ contract Token { storage.minters.at(minter).read() } // docs:end:is_minter + // docs:start:total_supply #[public] #[view] @@ -176,6 +176,7 @@ contract Token { storage.total_supply.read().to_integer() } // docs:end:total_supply + // docs:start:balance_of_public #[public] #[view] @@ -183,6 +184,7 @@ contract Token { storage.public_balances.at(owner).read().to_integer() } // docs:end:balance_of_public + // docs:start:set_minter #[public] fn set_minter(minter: AztecAddress, approve: bool) { @@ -194,6 +196,7 @@ contract Token { // docs:end:write_minter } // docs:end:set_minter + // docs:start:mint_public #[public] fn mint_public(to: AztecAddress, amount: Field) { @@ -207,37 +210,7 @@ contract Token { storage.total_supply.write(supply); } // docs:end:mint_public - // docs:start:mint_private - // TODO(benesjan): To be nuked in a followup PR. - #[public] - fn mint_private_old(amount: Field, secret_hash: Field) { - assert(storage.minters.at(context.msg_sender()).read(), "caller is not minter"); - let pending_shields = storage.pending_shields; - let mut note = TransparentNote::new(amount, secret_hash); - let supply = storage.total_supply.read().add(U128::from_integer(amount)); - storage.total_supply.write(supply); - // docs:start:insert_from_public - pending_shields.insert_from_public(&mut note); - // docs:end:insert_from_public - } - // docs:end:mint_private - // docs:start:shield - #[public] - fn shield(from: AztecAddress, amount: Field, secret_hash: Field, nonce: Field) { - if (!from.eq(context.msg_sender())) { - // The redeem is only spendable once, so we need to ensure that you cannot insert multiple shields from the same message. - assert_current_call_valid_authwit_public(&mut context, from); - } else { - assert(nonce == 0, "invalid nonce"); - } - let amount = U128::from_integer(amount); - let from_balance = storage.public_balances.at(from).read().sub(amount); - let pending_shields = storage.pending_shields; - let mut note = TransparentNote::new(amount.to_field(), secret_hash); - storage.public_balances.at(from).write(from_balance); - pending_shields.insert_from_public(&mut note); - } - // docs:end:shield + // docs:start:transfer_public #[public] fn transfer_public(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { @@ -253,6 +226,7 @@ contract Token { storage.public_balances.at(to).write(to_balance); } // docs:end:transfer_public + // docs:start:burn_public #[public] fn burn_public(from: AztecAddress, amount: Field, nonce: Field) { @@ -270,32 +244,7 @@ contract Token { storage.total_supply.write(new_supply); } // docs:end:burn_public - // docs:start:redeem_shield - #[private] - fn redeem_shield(to: AztecAddress, amount: Field, secret: Field) { - let secret_hash = compute_secret_hash(secret); - // Pop 1 note (set_limit(1)) which has an amount stored in a field with index 0 (select(0, amount)) and - // a secret_hash stored in a field with index 1 (select(1, secret_hash)). - let mut options = NoteGetterOptions::new(); - options = options - .select(TransparentNote::properties().amount, Comparator.EQ, amount) - .select(TransparentNote::properties().secret_hash, Comparator.EQ, secret_hash) - .set_limit(1); - let notes = storage.pending_shields.pop_notes(options); - assert(notes.len() == 1, "note not popped"); - // Add the token note to user's balances set - // Note: Using context.msg_sender() as a sender below makes this incompatible with escrows because we send - // outgoing logs to that address and to send outgoing logs you need to get a hold of ovsk_m. - let from = context.msg_sender(); - let from_ovpk_m = get_public_keys(from).ovpk_m; - storage.balances.at(to).add(to, U128::from_integer(amount)).emit(encode_and_encrypt_note( - &mut context, - from_ovpk_m, - to, - context.msg_sender(), - )); - } - // docs:end:redeem_shield + // docs:start:transfer_to_public #[private] fn transfer_to_public(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { @@ -314,6 +263,7 @@ contract Token { Token::at(context.this_address())._increase_public_balance(to, amount).enqueue(&mut context); } // docs:end:transfer_to_public + // docs:start:transfer #[private] fn transfer(to: AztecAddress, amount: Field) { @@ -356,6 +306,7 @@ contract Token { ); } // docs:end:transfer + #[contract_library_method] fn subtract_balance( context: &mut PrivateContext, @@ -380,6 +331,7 @@ contract Token { compute_recurse_subtract_balance_call(*context, account, remaining).call(context) } } + // TODO(#7729): apply no_predicates to the contract interface method directly instead of having to use a wrapper // like we do here. #[no_predicates] @@ -391,6 +343,7 @@ contract Token { ) -> PrivateCallInterface<25, U128> { Token::at(context.this_address())._recurse_subtract_balance(account, remaining.to_field()) } + // TODO(#7728): even though the amount should be a U128, we can't have that type in a contract interface due to // serialization issues. #[internal] @@ -404,6 +357,7 @@ contract Token { RECURSIVE_TRANSFER_CALL_MAX_NOTES, ) } + /** * Cancel a private authentication witness. * @param inner_hash The inner hash of the authwit to cancel. @@ -416,6 +370,7 @@ contract Token { context.push_nullifier(nullifier); } // docs:end:cancel_authwit + // docs:start:transfer_from #[private] fn transfer_from(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { @@ -451,6 +406,7 @@ contract Token { )); } // docs:end:transfer_from + // docs:start:burn #[private] fn burn(from: AztecAddress, amount: Field, nonce: Field) { @@ -469,11 +425,11 @@ contract Token { } // docs:end:burn + // docs:start:transfer_to_private // Transfers token `amount` from public balance of message sender to a private balance of `to`. #[private] fn transfer_to_private(to: AztecAddress, amount: Field) { - // We check the minter permissions in the enqueued call as that allows us to avoid the need for `SharedMutable` - // which is less efficient. + // `from` is the owner of the public balance from which we'll subtract the `amount`. let from = context.msg_sender(); let token = Token::at(context.this_address()); @@ -486,6 +442,7 @@ contract Token { &mut context, ); } + // docs:end:transfer_to_private /// Prepares a transfer to a private balance of `to`. The transfer then needs to be /// finalized by calling `finalize_transfer_to_private`. Returns a hiding point slot. @@ -588,6 +545,7 @@ contract Token { finalization_payload.emit(); } + // docs:start:mint_to_private /// Mints token `amount` to a private balance of `to`. Message sender has to have minter permissions (checked /// in the enqueud call). #[private] @@ -608,6 +566,7 @@ contract Token { ._finalize_mint_to_private_unsafe(context.msg_sender(), amount, hiding_point_slot) .enqueue(&mut context); } + // docs:end:mint_to_private /// Finalizes a mint of token `amount` to a private balance of `to`. The mint must be prepared by calling /// `prepare_transfer_to_private` first and the resulting @@ -828,6 +787,7 @@ contract Token { storage.public_balances.at(to).write(new_balance); } // docs:end:increase_public_balance + // docs:start:reduce_total_supply #[public] #[internal] @@ -837,6 +797,7 @@ contract Token { storage.total_supply.write(new_supply); } // docs:end:reduce_total_supply + /// Unconstrained /// // docs:start:balance_of_private pub(crate) unconstrained fn balance_of_private(owner: AztecAddress) -> pub Field { diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr index 35a4c1f1aff..b746bbe1f6c 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr @@ -8,4 +8,3 @@ mod transfer_to_public; mod refunds; mod minting; mod reading_constants; -mod shielding; diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr index bc4f877d2a3..1b572b872ff 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr @@ -1,6 +1,4 @@ -use crate::test::utils; -use crate::{Token, types::transparent_note::TransparentNote}; -use dep::aztec::{oracle::random::random, test::helpers::cheatcodes}; +use crate::{test::utils, Token}; #[test] unconstrained fn mint_public_success() { diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr deleted file mode 100644 index aef1d98f9cc..00000000000 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr +++ /dev/null @@ -1,179 +0,0 @@ -use crate::test::utils; -use crate::{Token, types::transparent_note::TransparentNote}; -use dep::authwit::cheatcodes as authwit_cheatcodes; -use dep::aztec::{hash::compute_secret_hash, oracle::random::random}; - -#[test] -unconstrained fn shielding_on_behalf_of_self() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, _, mint_amount) = - utils::setup_and_mint_public(/* with_account_contracts */ false); - let secret = random(); - let secret_hash = compute_secret_hash(secret); - // Shield tokens - let shield_amount = mint_amount / 10; - Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0).call( - &mut env.public(), - ); - - // We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery. - env.add_note( - &mut TransparentNote::new(shield_amount, secret_hash), - Token::storage_layout().pending_shields.slot, - token_contract_address, - ); - - // Redeem our shielded tokens - Token::at(token_contract_address).redeem_shield(owner, shield_amount, secret).call( - &mut env.private(), - ); - - // Check balances - utils::check_public_balance(token_contract_address, owner, mint_amount - shield_amount); - utils::check_private_balance(token_contract_address, owner, shield_amount); -} - -#[test] -unconstrained fn shielding_on_behalf_of_other() { - let (env, token_contract_address, owner, recipient, mint_amount) = - utils::setup_and_mint_public(/* with_account_contracts */ true); - let secret = random(); - let secret_hash = compute_secret_hash(secret); - - // Shield tokens on behalf of owner - let shield_amount = 1000; - let shield_call_interface = - Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); - authwit_cheatcodes::add_public_authwit_from_call_interface( - owner, - recipient, - shield_call_interface, - ); - // Impersonate recipient to perform the call - env.impersonate(recipient); - // Shield tokens - shield_call_interface.call(&mut env.public()); - - // Become owner again - env.impersonate(owner); - // We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery. - env.add_note( - &mut TransparentNote::new(shield_amount, secret_hash), - Token::storage_layout().pending_shields.slot, - token_contract_address, - ); - - // Redeem our shielded tokens - Token::at(token_contract_address).redeem_shield(owner, shield_amount, secret).call( - &mut env.private(), - ); - - // Check balances - utils::check_public_balance(token_contract_address, owner, mint_amount - shield_amount); - utils::check_private_balance(token_contract_address, owner, shield_amount); -} - -#[test] -unconstrained fn shielding_failure_on_behalf_of_self_more_than_balance() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, _, mint_amount) = - utils::setup_and_mint_public(/* with_account_contracts */ true); - let secret = random(); - let secret_hash = compute_secret_hash(secret); - // Shield tokens - let shield_amount = mint_amount + 1; - let shield_call_interface = - Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); - env.assert_public_call_fails(shield_call_interface); - - // Check balances - utils::check_public_balance(token_contract_address, owner, mint_amount); - utils::check_private_balance(token_contract_address, owner, 0); -} - -#[test] -unconstrained fn shielding_failure_on_behalf_of_self_invalid_nonce() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, _, mint_amount) = - utils::setup_and_mint_public(/* with_account_contracts */ true); - let secret = random(); - let secret_hash = compute_secret_hash(secret); - // Shield tokens - let shield_amount = mint_amount / 10; - let shield_call_interface = - Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, random()); - env.assert_public_call_fails(shield_call_interface); - - // Check balances - utils::check_public_balance(token_contract_address, owner, mint_amount); - utils::check_private_balance(token_contract_address, owner, 0); -} - -#[test] -unconstrained fn shielding_failure_on_behalf_of_other_more_than_balance() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, recipient, mint_amount) = - utils::setup_and_mint_public(/* with_account_contracts */ true); - let secret = random(); - let secret_hash = compute_secret_hash(secret); - // Shield tokens on behalf of owner - let shield_amount = mint_amount + 1; - let shield_call_interface = - Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); - authwit_cheatcodes::add_public_authwit_from_call_interface( - owner, - recipient, - shield_call_interface, - ); - // Impersonate recipient to perform the call - env.impersonate(recipient); - // Shield tokens - env.assert_public_call_fails(shield_call_interface); - - // Check balances - utils::check_public_balance(token_contract_address, owner, mint_amount); - utils::check_private_balance(token_contract_address, owner, 0); -} - -#[test] -unconstrained fn shielding_failure_on_behalf_of_other_wrong_caller() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, recipient, mint_amount) = - utils::setup_and_mint_public(/* with_account_contracts */ true); - let secret = random(); - let secret_hash = compute_secret_hash(secret); - // Shield tokens on behalf of owner - let shield_amount = mint_amount + 1; - let shield_call_interface = - Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); - authwit_cheatcodes::add_public_authwit_from_call_interface(owner, owner, shield_call_interface); - // Impersonate recipient to perform the call - env.impersonate(recipient); - // Shield tokens - env.assert_public_call_fails(shield_call_interface); - - // Check balances - utils::check_public_balance(token_contract_address, owner, mint_amount); - utils::check_private_balance(token_contract_address, owner, 0); -} - -#[test] -unconstrained fn shielding_failure_on_behalf_of_other_without_approval() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, recipient, mint_amount) = - utils::setup_and_mint_public(/* with_account_contracts */ true); - let secret = random(); - let secret_hash = compute_secret_hash(secret); - // Shield tokens on behalf of owner - let shield_amount = mint_amount + 1; - let shield_call_interface = - Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); - // Impersonate recipient to perform the call - env.impersonate(recipient); - // Shield tokens - env.assert_public_call_fails(shield_call_interface); - - // Check balances - utils::check_public_balance(token_contract_address, owner, mint_amount); - utils::check_private_balance(token_contract_address, owner, 0); -} diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/types.nr b/noir-projects/noir-contracts/contracts/token_contract/src/types.nr index cad3fda646e..1bc5f644e4f 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/types.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/types.nr @@ -1,2 +1 @@ -pub(crate) mod transparent_note; pub(crate) mod balance_set; diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/types/transparent_note.nr b/noir-projects/noir-contracts/contracts/token_contract/src/types/transparent_note.nr deleted file mode 100644 index ed1f08e0d74..00000000000 --- a/noir-projects/noir-contracts/contracts/token_contract/src/types/transparent_note.nr +++ /dev/null @@ -1,63 +0,0 @@ -// docs:start:token_types_all -use dep::aztec::{ - macros::notes::note, - note::utils::compute_note_hash_for_nullify, - prelude::{NoteHeader, NullifiableNote, PrivateContext}, - protocol_types::{ - constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator, - }, -}; - -use dep::std::mem::zeroed; - -// Transparent note represents a note that is created in the clear (public execution), but can only be spent by those -// that know the preimage of the "secret_hash" (the secret). This is typically used when shielding a token balance. -// Owner of the tokens provides a "secret_hash" as an argument to the public "shield" function and then the tokens -// can be redeemed in private by presenting the preimage of the "secret_hash" (the secret). -#[note] -pub struct TransparentNote { - amount: Field, - secret_hash: Field, -} - -impl NullifiableNote for TransparentNote { - // Computing a nullifier in a transparent note is not guarded by making secret a part of the nullifier preimage (as - // is common in other cases) and instead is guarded by the functionality of "redeem_shield" function. There we do - // the following: - // 1) We pass the secret as an argument to the function and use it to compute a secret hash, - // 2) we fetch a note via the "get_notes" oracle which accepts the secret hash as an argument, - // 3) the "get_notes" oracle constrains that the secret hash in the returned note matches the one computed in - // circuit. - // This achieves that the note can only be spent by the party that knows the secret. - fn compute_nullifier( - self, - _context: &mut PrivateContext, - _note_hash_for_nullify: Field, - ) -> Field { - let note_hash_for_nullify = compute_note_hash_for_nullify(self); - poseidon2_hash_with_separator( - [note_hash_for_nullify], - GENERATOR_INDEX__NOTE_NULLIFIER as Field, - ) - } - - unconstrained fn compute_nullifier_without_context(self) -> Field { - // compute_nullifier ignores both of its parameters so we can reuse it here - self.compute_nullifier(zeroed(), zeroed()) - } -} - -impl TransparentNote { - // CONSTRUCTORS - pub fn new(amount: Field, secret_hash: Field) -> Self { - TransparentNote { amount, secret_hash, header: NoteHeader::empty() } - } -} - -impl Eq for TransparentNote { - fn eq(self, other: Self) -> bool { - (self.amount == other.amount) & (self.secret_hash == other.secret_hash) - } -} - -// docs:end:token_types_all diff --git a/yarn-project/end-to-end/dapp1-script.ts b/yarn-project/end-to-end/dapp1-script.ts deleted file mode 100644 index c588ffc68a3..00000000000 --- a/yarn-project/end-to-end/dapp1-script.ts +++ /dev/null @@ -1 +0,0 @@ -Docker images not found. They need to be built with 'earthly ./yarn-project/+export-end-to-end' or otherwise tagged with aztecprotocol/end-to-end:7f000e4b57c12ce035c84b2e79034bfe9ab298f3. diff --git a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts index 7623818516f..5f5d8a12522 100644 --- a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts +++ b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts @@ -222,8 +222,7 @@ export class BlacklistTokenContractTest { this.logger.verbose(`Public balance of wallet 0: ${publicBalance}`); expect(publicBalance).toEqual(this.tokenSim.balanceOfPublic(address)); - tokenSim.mintPrivate(amount); - tokenSim.redeemShield(address, amount); + tokenSim.mintPrivate(address, amount); const privateBalance = await asset.methods.balance_of_private(address).simulate(); this.logger.verbose(`Private balance of wallet 0: ${privateBalance}`); expect(privateBalance).toEqual(tokenSim.balanceOfPrivate(address)); diff --git a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/minting.test.ts b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/minting.test.ts index 19dd05a9aad..62a1d0af5ca 100644 --- a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/minting.test.ts +++ b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/minting.test.ts @@ -83,13 +83,10 @@ describe('e2e_blacklist_token_contract mint', () => { }); describe('Mint flow', () => { - it('mint_private as minter', async () => { + it('mint_private as minter and redeem as recipient', async () => { const receipt = await asset.methods.mint_private(amount, secretHash).send().wait(); - tokenSim.mintPrivate(amount); txHash = receipt.txHash; - }); - it('redeem as recipient', async () => { await t.addPendingShieldNoteToPXE(0, amount, secretHash, txHash); const receiptClaim = await asset.methods @@ -97,7 +94,7 @@ describe('e2e_blacklist_token_contract mint', () => { .send() .wait({ debug: true }); - tokenSim.redeemShield(wallets[0].getAddress(), amount); + tokenSim.mintPrivate(wallets[0].getAddress(), amount); // 1 note should be created containing `amount` of tokens const { visibleIncomingNotes } = receiptClaim.debugInfo!; expect(visibleIncomingNotes.length).toBe(1); diff --git a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/shielding.test.ts b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/shielding.test.ts index de9a3e1529d..c2955daf770 100644 --- a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/shielding.test.ts +++ b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/shielding.test.ts @@ -37,14 +37,13 @@ describe('e2e_blacklist_token_contract shield + redeem_shield', () => { const receipt = await asset.methods.shield(wallets[0].getAddress(), amount, secretHash, 0).send().wait(); - tokenSim.shield(wallets[0].getAddress(), amount); - await t.tokenSim.check(); - // Redeem it await t.addPendingShieldNoteToPXE(0, amount, secretHash, receipt.txHash); await asset.methods.redeem_shield(wallets[0].getAddress(), amount, secret).send().wait(); - tokenSim.redeemShield(wallets[0].getAddress(), amount); + // Check that the result matches token sim + tokenSim.transferToPrivate(wallets[0].getAddress(), wallets[0].getAddress(), amount); + await t.tokenSim.check(); }); it('on behalf of other', async () => { @@ -59,9 +58,6 @@ describe('e2e_blacklist_token_contract shield + redeem_shield', () => { const receipt = await action.send().wait(); - tokenSim.shield(wallets[0].getAddress(), amount); - await t.tokenSim.check(); - // Check that replaying the shield should fail! await expect( asset.withWallet(wallets[1]).methods.shield(wallets[0].getAddress(), amount, secretHash, nonce).simulate(), @@ -71,7 +67,9 @@ describe('e2e_blacklist_token_contract shield + redeem_shield', () => { await t.addPendingShieldNoteToPXE(0, amount, secretHash, receipt.txHash); await asset.methods.redeem_shield(wallets[0].getAddress(), amount, secret).send().wait(); - tokenSim.redeemShield(wallets[0].getAddress(), amount); + // Check that the result matches token sim + tokenSim.transferToPrivate(wallets[0].getAddress(), wallets[0].getAddress(), amount); + await t.tokenSim.check(); }); describe('failure cases', () => { diff --git a/yarn-project/end-to-end/src/e2e_fees/fees_test.ts b/yarn-project/end-to-end/src/e2e_fees/fees_test.ts index aa930a1a56e..fad11c79369 100644 --- a/yarn-project/end-to-end/src/e2e_fees/fees_test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/fees_test.ts @@ -4,12 +4,8 @@ import { type AztecAddress, type AztecNode, type DebugLogger, - ExtendedNote, - Fr, - Note, type PXE, SignerlessWallet, - type TxHash, createDebugLogger, sleep, } from '@aztec/aztec.js'; @@ -126,22 +122,6 @@ export class FeesTest { expect(balanceAfter).toEqual(balanceBefore + amount); } - /** Adds a pending shield transparent node for the banana coin token contract to the pxe. */ - // TODO(benesjan): nuke this - async addPendingShieldNoteToPXE(owner: AztecAddress | AccountWallet, amount: bigint, secretHash: Fr, txHash: TxHash) { - const note = new Note([new Fr(amount), secretHash]); - const ownerAddress = 'getAddress' in owner ? owner.getAddress() : owner; - const extendedNote = new ExtendedNote( - note, - ownerAddress, - this.bananaCoin.address, - BananaCoin.storage.pending_shields.slot, - BananaCoin.notes.TransparentNote.id, - txHash, - ); - await this.pxe.addNote(extendedNote, ownerAddress); - } - public async applyBaseSnapshots() { await this.applyInitialAccountsSnapshot(); await this.applyPublicDeployAccountsSnapshot(); diff --git a/yarn-project/end-to-end/src/e2e_fees/private_payments.test.ts b/yarn-project/end-to-end/src/e2e_fees/private_payments.test.ts index bf9fdb65ba8..870f40707c9 100644 --- a/yarn-project/end-to-end/src/e2e_fees/private_payments.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/private_payments.test.ts @@ -1,12 +1,4 @@ -import { - type AccountWallet, - type AztecAddress, - BatchCall, - Fr, - PrivateFeePaymentMethod, - computeSecretHash, - sleep, -} from '@aztec/aztec.js'; +import { type AccountWallet, type AztecAddress, BatchCall, PrivateFeePaymentMethod, sleep } from '@aztec/aztec.js'; import { type GasSettings } from '@aztec/circuits.js'; import { type TokenContract as BananaCoin, FPCContract } from '@aztec/noir-contracts.js'; @@ -179,8 +171,8 @@ describe('e2e_fees private_payment', () => { * BC increase total supply * * PUBLIC TEARDOWN - * increase sequencer/fee recipient/FPC admin private banana balance by feeAmount by finalizing partial note - * increase Alice's private banana balance by feeAmount by finalizing partial note + * increase sequencer/fee recipient/FPC admin private banana balance by feeAmount by finalizing partial note + * increase Alice's private banana balance by feeAmount by finalizing partial note */ const newlyMintedBananas = 10n; const from = aliceAddress; // we are setting from to Alice here because of TODO(#9887) @@ -221,23 +213,20 @@ describe('e2e_fees private_payment', () => { * setup fee and refund partial notes * setup public teardown call * - * * PRIVATE APP LOGIC - * N/A + * a partial note is prepared * * PUBLIC APP LOGIC * BC decrease Alice public balance by shieldedBananas - * BC create transparent note of shieldedBananas + * BC finalizes the partial note with an amount --> this is where the note is created in public * * PUBLIC TEARDOWN - * increase sequencer/fee recipient/FPC admin private banana balance by feeAmount by finalizing partial note - * increase Alice's private banana balance by feeAmount by finalizing partial note + * increase sequencer/fee recipient/FPC admin private banana balance by feeAmount by finalizing partial note + * increase Alice's private banana balance by feeAmount by finalizing partial note */ - const shieldedBananas = 1n; - const shieldSecret = Fr.random(); - const shieldSecretHash = computeSecretHash(shieldSecret); + const amountTransferredToPrivate = 1n; const tx = await bananaCoin.methods - .shield(aliceAddress, shieldedBananas, shieldSecretHash, 0n) + .transfer_to_private(aliceAddress, amountTransferredToPrivate) .send({ fee: { gasSettings, @@ -256,30 +245,27 @@ describe('e2e_fees private_payment', () => { await expectMapping( t.getBananaPrivateBalanceFn, [aliceAddress, bananaFPC.address, sequencerAddress], - [initialAlicePrivateBananas - feeAmount, 0n, initialSequencerPrivateBananas + feeAmount], + [ + initialAlicePrivateBananas - feeAmount + amountTransferredToPrivate, + 0n, + initialSequencerPrivateBananas + feeAmount, + ], ); await expectMapping( t.getBananaPublicBalanceFn, [aliceAddress, bananaFPC.address, sequencerAddress], - [initialAlicePublicBananas - shieldedBananas, initialFPCPublicBananas, 0n], + [initialAlicePublicBananas - amountTransferredToPrivate, initialFPCPublicBananas, 0n], ); await expectMapping( t.getGasBalanceFn, [aliceAddress, bananaFPC.address, sequencerAddress], [initialAliceGas, initialFPCGas - feeAmount, initialSequencerGas], ); - - await expect( - t.addPendingShieldNoteToPXE(t.aliceWallet, shieldedBananas, shieldSecretHash, tx.txHash), - ).resolves.toBeUndefined(); }); it('pays fees for tx that creates notes in both private and public', async () => { - const privateTransfer = 1n; - const shieldedBananas = 1n; - const shieldSecret = Fr.random(); - const shieldSecretHash = computeSecretHash(shieldSecret); - + const amountTransferredInPrivate = 1n; + const amountTransferredToPrivate = 2n; /** * PRIVATE SETUP * check authwit @@ -290,18 +276,19 @@ describe('e2e_fees private_payment', () => { * PRIVATE APP LOGIC * reduce Alice's private balance by privateTransfer * create note for Bob with privateTransfer amount of private BC + * prepare partial note (in the transfer to private) * * PUBLIC APP LOGIC - * BC decrease Alice public balance by shieldedBananas - * BC create transparent note of shieldedBananas + * BC decrease Alice public balance by amountTransferredToPrivate + * BC finalize partial note with amountTransferredToPrivate (this is where the note is created in public) * * PUBLIC TEARDOWN - * increase sequencer/fee recipient/FPC admin private banana balance by feeAmount by finalizing partial note - * increase Alice's private banana balance by feeAmount by finalizing partial note + * increase sequencer/fee recipient/FPC admin private banana balance by feeAmount by finalizing partial note + * increase Alice's private banana balance by feeAmount by finalizing partial note */ const tx = await new BatchCall(aliceWallet, [ - bananaCoin.methods.transfer(bobAddress, privateTransfer).request(), - bananaCoin.methods.shield(aliceAddress, shieldedBananas, shieldSecretHash, 0n).request(), + bananaCoin.methods.transfer(bobAddress, amountTransferredInPrivate).request(), + bananaCoin.methods.transfer_to_private(aliceAddress, amountTransferredToPrivate).request(), ]) .send({ fee: { @@ -322,8 +309,8 @@ describe('e2e_fees private_payment', () => { t.getBananaPrivateBalanceFn, [aliceAddress, bobAddress, bananaFPC.address, sequencerAddress], [ - initialAlicePrivateBananas - feeAmount - privateTransfer, - initialBobPrivateBananas + privateTransfer, + initialAlicePrivateBananas - feeAmount - amountTransferredInPrivate + amountTransferredToPrivate, + initialBobPrivateBananas + amountTransferredInPrivate, 0n, initialSequencerPrivateBananas + feeAmount, ], @@ -331,17 +318,13 @@ describe('e2e_fees private_payment', () => { await expectMapping( t.getBananaPublicBalanceFn, [aliceAddress, bananaFPC.address, sequencerAddress], - [initialAlicePublicBananas - shieldedBananas, initialFPCPublicBananas, 0n], + [initialAlicePublicBananas - amountTransferredToPrivate, initialFPCPublicBananas, 0n], ); await expectMapping( t.getGasBalanceFn, [aliceAddress, bananaFPC.address, sequencerAddress], [initialAliceGas, initialFPCGas - feeAmount, initialSequencerGas], ); - - await expect( - t.addPendingShieldNoteToPXE(t.aliceWallet, shieldedBananas, shieldSecretHash, tx.txHash), - ).resolves.toBeUndefined(); }); it('rejects txs that dont have enough balance to cover gas costs', async () => { diff --git a/yarn-project/end-to-end/src/e2e_lending_contract.test.ts b/yarn-project/end-to-end/src/e2e_lending_contract.test.ts index cb356d6ebda..a80b6264203 100644 --- a/yarn-project/end-to-end/src/e2e_lending_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_lending_contract.test.ts @@ -107,11 +107,9 @@ describe('e2e_lending_contract', () => { } lendingSim.mintStableCoinOutsideLoan(lendingAccount.address, 10000n, true); - lendingSim.stableCoin.redeemShield(lendingAccount.address, 10000n); lendingSim.mintStableCoinOutsideLoan(lendingAccount.address, 10000n, false); - lendingSim.collateralAsset.mintPrivate(10000n); - lendingSim.collateralAsset.redeemShield(lendingAccount.address, 10000n); + lendingSim.collateralAsset.mintPrivate(lendingAccount.address, 10000n); lendingSim.collateralAsset.mintPublic(lendingAccount.address, 10000n); }); diff --git a/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts b/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts index 737575c3597..2df25d15ab6 100644 --- a/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts +++ b/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts @@ -8,12 +8,9 @@ import { type DebugLogger, type DeployL1Contracts, EthAddress, - ExtendedNote, type Fq, Fr, - Note, type PXE, - type TxHash, createDebugLogger, deployL1Contract, } from '@aztec/aztec.js'; @@ -327,19 +324,6 @@ export class FullProverTest { await this.acvmConfigCleanup?.(); } - async addPendingShieldNoteToPXE(accountIndex: number, amount: bigint, secretHash: Fr, txHash: TxHash) { - const note = new Note([new Fr(amount), secretHash]); - const extendedNote = new ExtendedNote( - note, - this.accounts[accountIndex].address, - this.fakeProofsAsset.address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - txHash, - ); - await this.wallets[accountIndex].addNote(extendedNote); - } - async applyMintSnapshot() { await this.snapshotManager.snapshot( 'mint', @@ -375,8 +359,7 @@ export class FullProverTest { this.logger.verbose(`Public balance of wallet 0: ${publicBalance}`); expect(publicBalance).toEqual(this.tokenSim.balanceOfPublic(address)); - tokenSim.mintPrivate(amount); - tokenSim.redeemShield(address, amount); + tokenSim.mintPrivate(address, amount); const privateBalance = await asset.methods.balance_of_private(address).simulate(); this.logger.verbose(`Private balance of wallet 0: ${privateBalance}`); expect(privateBalance).toEqual(tokenSim.balanceOfPrivate(address)); diff --git a/yarn-project/end-to-end/src/e2e_synching.test.ts b/yarn-project/end-to-end/src/e2e_synching.test.ts index 448b36e69c6..af1963d24b9 100644 --- a/yarn-project/end-to-end/src/e2e_synching.test.ts +++ b/yarn-project/end-to-end/src/e2e_synching.test.ts @@ -36,7 +36,6 @@ import { getSchnorrAccount } from '@aztec/accounts/schnorr'; import { createArchiver } from '@aztec/archiver'; import { AztecNodeService } from '@aztec/aztec-node'; import { - type AccountWallet, type AccountWalletWithSecretKey, AnvilTestWatcher, BatchCall, @@ -48,7 +47,7 @@ import { sleep, } from '@aztec/aztec.js'; // eslint-disable-next-line no-restricted-imports -import { ExtendedNote, L2Block, LogType, Note, type TxHash, tryStop } from '@aztec/circuit-types'; +import { L2Block, LogType, tryStop } from '@aztec/circuit-types'; import { type AztecAddress } from '@aztec/circuits.js'; import { getL1ContractsConfigEnvVars } from '@aztec/ethereum'; import { Timer } from '@aztec/foundation/timer'; @@ -184,27 +183,6 @@ class TestVariant { } } - private async addPendingShieldNoteToPXE(args: { - amount: bigint; - secretHash: Fr; - txHash: TxHash; - accountAddress: AztecAddress; - assetAddress: AztecAddress; - wallet: AccountWallet; - }) { - const { accountAddress, assetAddress, amount, secretHash, txHash, wallet } = args; - const note = new Note([new Fr(amount), secretHash]); - const extendedNote = new ExtendedNote( - note, - accountAddress, - assetAddress, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - txHash, - ); - await wallet.addNote(extendedNote); - } - async createAndSendTxs() { if (!this.pxe) { throw new Error('Undefined PXE'); diff --git a/yarn-project/end-to-end/src/e2e_token_contract/shielding.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/shielding.test.ts deleted file mode 100644 index d7a576e09b5..00000000000 --- a/yarn-project/end-to-end/src/e2e_token_contract/shielding.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { Fr, computeSecretHash } from '@aztec/aztec.js'; - -import { U128_UNDERFLOW_ERROR } from '../fixtures/fixtures.js'; -import { TokenContractTest } from './token_contract_test.js'; - -describe('e2e_token_contract shield + redeem shield', () => { - const t = new TokenContractTest('shielding'); - let { asset, accounts, tokenSim, wallets } = t; - const secret = Fr.random(); - let secretHash: Fr; - - beforeAll(async () => { - await t.applyBaseSnapshots(); - await t.applyMintSnapshot(); - await t.setup(); - // Have to destructure again to ensure we have latest refs. - ({ asset, accounts, tokenSim, wallets } = t); - secretHash = computeSecretHash(secret); - }); - - afterAll(async () => { - await t.teardown(); - }); - - afterEach(async () => { - await t.tokenSim.check(); - }); - - it('on behalf of self', async () => { - const balancePub = await asset.methods.balance_of_public(accounts[0].address).simulate(); - const amount = balancePub / 2n; - expect(amount).toBeGreaterThan(0n); - - const receipt = await asset.methods.shield(accounts[0].address, amount, secretHash, 0).send().wait(); - - tokenSim.shield(accounts[0].address, amount); - await tokenSim.check(); - - // Redeem it - await t.addPendingShieldNoteToPXE(0, amount, secretHash, receipt.txHash); - await asset.methods.redeem_shield(accounts[0].address, amount, secret).send().wait(); - - tokenSim.redeemShield(accounts[0].address, amount); - }); - - it('on behalf of other', async () => { - const balancePub = await asset.methods.balance_of_public(accounts[0].address).simulate(); - const amount = balancePub / 2n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset.withWallet(wallets[1]).methods.shield(accounts[0].address, amount, secretHash, nonce); - await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); - - const receipt = await action.send().wait(); - - tokenSim.shield(accounts[0].address, amount); - await tokenSim.check(); - - // Check that replaying the shield should fail! - await expect( - asset.withWallet(wallets[1]).methods.shield(accounts[0].address, amount, secretHash, nonce).simulate(), - ).rejects.toThrow(/unauthorized/); - - // Redeem it - await t.addPendingShieldNoteToPXE(0, amount, secretHash, receipt.txHash); - await asset.methods.redeem_shield(accounts[0].address, amount, secret).send().wait(); - - tokenSim.redeemShield(accounts[0].address, amount); - }); - - describe('failure cases', () => { - it('on behalf of self (more than balance)', async () => { - const balancePub = await asset.methods.balance_of_public(accounts[0].address).simulate(); - const amount = balancePub + 1n; - expect(amount).toBeGreaterThan(0n); - - await expect(asset.methods.shield(accounts[0].address, amount, secretHash, 0).simulate()).rejects.toThrow( - U128_UNDERFLOW_ERROR, - ); - }); - - it('on behalf of self (invalid nonce)', async () => { - const balancePub = await asset.methods.balance_of_public(accounts[0].address).simulate(); - const amount = balancePub + 1n; - expect(amount).toBeGreaterThan(0n); - - await expect(asset.methods.shield(accounts[0].address, amount, secretHash, 1).simulate()).rejects.toThrow( - 'Assertion failed: invalid nonce', - ); - }); - - it('on behalf of other (more than balance)', async () => { - const balancePub = await asset.methods.balance_of_public(accounts[0].address).simulate(); - const amount = balancePub + 1n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset.withWallet(wallets[1]).methods.shield(accounts[0].address, amount, secretHash, nonce); - await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); - - await expect(action.simulate()).rejects.toThrow(U128_UNDERFLOW_ERROR); - }); - - it('on behalf of other (wrong designated caller)', async () => { - const balancePub = await asset.methods.balance_of_public(accounts[0].address).simulate(); - const amount = balancePub + 1n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - // We need to compute the message we want to sign and add it to the wallet as approved - const action = asset.withWallet(wallets[2]).methods.shield(accounts[0].address, amount, secretHash, nonce); - await wallets[0].setPublicAuthWit({ caller: accounts[1].address, action }, true).send().wait(); - - await expect(action.simulate()).rejects.toThrow(/unauthorized/); - }); - - it('on behalf of other (without approval)', async () => { - const balance = await asset.methods.balance_of_public(accounts[0].address).simulate(); - const amount = balance / 2n; - const nonce = Fr.random(); - expect(amount).toBeGreaterThan(0n); - - await expect( - asset.withWallet(wallets[1]).methods.shield(accounts[0].address, amount, secretHash, nonce).simulate(), - ).rejects.toThrow(/unauthorized/); - }); - }); -}); diff --git a/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts b/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts index 6ede484f6d2..9d1d76123bc 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts @@ -1,14 +1,5 @@ import { getSchnorrAccount } from '@aztec/accounts/schnorr'; -import { - type AccountWallet, - type CompleteAddress, - type DebugLogger, - ExtendedNote, - Fr, - Note, - type TxHash, - createDebugLogger, -} from '@aztec/aztec.js'; +import { type AccountWallet, type CompleteAddress, type DebugLogger, createDebugLogger } from '@aztec/aztec.js'; import { DocsExampleContract, TokenContract } from '@aztec/noir-contracts.js'; import { jest } from '@jest/globals'; @@ -123,19 +114,6 @@ export class TokenContractTest { await this.snapshotManager.teardown(); } - async addPendingShieldNoteToPXE(accountIndex: number, amount: bigint, secretHash: Fr, txHash: TxHash) { - const note = new Note([new Fr(amount), secretHash]); - const extendedNote = new ExtendedNote( - note, - this.accounts[accountIndex].address, - this.asset.address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - txHash, - ); - await this.wallets[accountIndex].addNote(extendedNote); - } - async applyMintSnapshot() { await this.snapshotManager.snapshot( 'mint', @@ -164,8 +142,7 @@ export class TokenContractTest { this.logger.verbose(`Public balance of wallet 0: ${publicBalance}`); expect(publicBalance).toEqual(this.tokenSim.balanceOfPublic(address)); - tokenSim.mintPrivate(amount); - tokenSim.redeemShield(address, amount); + tokenSim.mintPrivate(address, amount); const privateBalance = await asset.methods.balance_of_private(address).simulate(); this.logger.verbose(`Private balance of wallet 0: ${privateBalance}`); expect(privateBalance).toEqual(tokenSim.balanceOfPrivate(address)); diff --git a/yarn-project/end-to-end/src/e2e_token_contract/transfer_to_private.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/transfer_to_private.test.ts new file mode 100644 index 00000000000..c0f6c1368b2 --- /dev/null +++ b/yarn-project/end-to-end/src/e2e_token_contract/transfer_to_private.test.ts @@ -0,0 +1,59 @@ +import { U128_UNDERFLOW_ERROR } from '../fixtures/fixtures.js'; +import { TokenContractTest } from './token_contract_test.js'; + +describe('e2e_token_contract transfer_to_private', () => { + const t = new TokenContractTest('transfer_to_private'); + let { asset, accounts, tokenSim } = t; + + beforeAll(async () => { + await t.applyBaseSnapshots(); + await t.applyMintSnapshot(); + await t.setup(); + // Have to destructure again to ensure we have latest refs. + ({ asset, accounts, tokenSim } = t); + }); + + afterAll(async () => { + await t.teardown(); + }); + + afterEach(async () => { + await t.tokenSim.check(); + }); + + it('to self', async () => { + const balancePub = await asset.methods.balance_of_public(accounts[0].address).simulate(); + const amount = balancePub / 2n; + expect(amount).toBeGreaterThan(0n); + + await asset.methods.transfer_to_private(accounts[0].address, amount).send().wait(); + + // Check that the result matches token sim + tokenSim.transferToPrivate(accounts[0].address, accounts[0].address, amount); + await tokenSim.check(); + }); + + it('to someone else', async () => { + const balancePub = await asset.methods.balance_of_public(accounts[0].address).simulate(); + const amount = balancePub / 2n; + expect(amount).toBeGreaterThan(0n); + + await asset.methods.transfer_to_private(accounts[1].address, amount).send().wait(); + + // Check that the result matches token sim + tokenSim.transferToPrivate(accounts[0].address, accounts[1].address, amount); + await tokenSim.check(); + }); + + describe('failure cases', () => { + it('to self (more than balance)', async () => { + const balancePub = await asset.methods.balance_of_public(accounts[0].address).simulate(); + const amount = balancePub + 1n; + expect(amount).toBeGreaterThan(0n); + + await expect(asset.methods.transfer_to_private(accounts[0].address, amount).simulate()).rejects.toThrow( + U128_UNDERFLOW_ERROR, + ); + }); + }); +}); diff --git a/yarn-project/end-to-end/src/guides/dapp_testing.test.ts b/yarn-project/end-to-end/src/guides/dapp_testing.test.ts index 1fea3491017..ffe17819f62 100644 --- a/yarn-project/end-to-end/src/guides/dapp_testing.test.ts +++ b/yarn-project/end-to-end/src/guides/dapp_testing.test.ts @@ -121,7 +121,10 @@ describe('guides/dapp/testing', () => { it('checks public storage', async () => { // docs:start:public-storage await token.methods.mint_public(owner.getAddress(), 100n).send().wait(); - const ownerPublicBalanceSlot = cheats.aztec.computeSlotInMap(6n, owner.getAddress()); + const ownerPublicBalanceSlot = cheats.aztec.computeSlotInMap( + TokenContract.storage.public_balances.slot, + owner.getAddress(), + ); const balance = await pxe.getPublicStorageAt(token.address, ownerPublicBalanceSlot); expect(balance.value).toEqual(100n); // docs:end:public-storage @@ -179,7 +182,10 @@ describe('guides/dapp/testing', () => { const call = token.methods.transfer_public(owner.getAddress(), recipient.getAddress(), 1000n, 0); const receipt = await call.send({ skipPublicSimulation: true }).wait({ dontThrowOnRevert: true }); expect(receipt.status).toEqual(TxStatus.APP_LOGIC_REVERTED); - const ownerPublicBalanceSlot = cheats.aztec.computeSlotInMap(6n, owner.getAddress()); + const ownerPublicBalanceSlot = cheats.aztec.computeSlotInMap( + TokenContract.storage.public_balances.slot, + owner.getAddress(), + ); const balance = await pxe.getPublicStorageAt(token.address, ownerPublicBalanceSlot); expect(balance.value).toEqual(100n); // docs:end:pub-reverted diff --git a/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts b/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts index b561aa03488..687fe8148c7 100644 --- a/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts +++ b/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts @@ -4,17 +4,14 @@ import { type AztecNode, type DebugLogger, EthAddress, - ExtendedNote, type FieldsOf, Fr, type L1TokenManager, L1TokenPortalManager, type L2AmountClaim, type L2AmountClaimWithRecipient, - Note, type PXE, type SiblingPath, - type TxHash, type TxReceipt, type Wallet, deployL1Contract, @@ -343,20 +340,6 @@ export class CrossChainTestHarness { await this.l2Token.methods.transfer_to_private(this.ownerAddress, shieldAmount).send().wait(); } - async addPendingShieldNoteToPXE(shieldAmount: bigint, secretHash: Fr, txHash: TxHash) { - this.logger.info('Adding note to PXE'); - const note = new Note([new Fr(shieldAmount), secretHash]); - const extendedNote = new ExtendedNote( - note, - this.ownerAddress, - this.l2Token.address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - txHash, - ); - await this.ownerWallet.addNote(extendedNote); - } - async transferToPublicOnL2(amount: bigint, nonce = Fr.ZERO) { this.logger.info('Transferring tokens to public'); await this.l2Token.methods.transfer_to_public(this.ownerAddress, this.ownerAddress, amount, nonce).send().wait(); diff --git a/yarn-project/end-to-end/src/simulators/lending_simulator.ts b/yarn-project/end-to-end/src/simulators/lending_simulator.ts index 288cac32eaf..d09741b637e 100644 --- a/yarn-project/end-to-end/src/simulators/lending_simulator.ts +++ b/yarn-project/end-to-end/src/simulators/lending_simulator.ts @@ -166,7 +166,7 @@ export class LendingSimulator { mintStableCoinOutsideLoan(recipient: AztecAddress, amount: bigint, priv = false) { if (priv) { - this.stableCoin.mintPrivate(amount); + this.stableCoin.mintPrivate(recipient, amount); } else { this.stableCoin.mintPublic(recipient, amount); } diff --git a/yarn-project/end-to-end/src/simulators/token_simulator.ts b/yarn-project/end-to-end/src/simulators/token_simulator.ts index 03caf19ab16..53a4c5d6e2c 100644 --- a/yarn-project/end-to-end/src/simulators/token_simulator.ts +++ b/yarn-project/end-to-end/src/simulators/token_simulator.ts @@ -26,8 +26,9 @@ export class TokenSimulator { this.lookupProvider.set(account.toString(), wallet); } - public mintPrivate(amount: bigint) { + public mintPrivate(to: AztecAddress, amount: bigint) { this.totalSupply += amount; + this.balancesPrivate.set(to.toString(), (this.balancesPrivate.get(to.toString()) || 0n) + amount); } public mintPublic(to: AztecAddress, amount: bigint) { @@ -54,13 +55,10 @@ export class TokenSimulator { this.balancesPrivate.set(to.toString(), toBalance + amount); } - public shield(from: AztecAddress, amount: bigint) { + public transferToPrivate(from: AztecAddress, to: AztecAddress, amount: bigint) { const fromBalance = this.balancePublic.get(from.toString()) || 0n; expect(fromBalance).toBeGreaterThanOrEqual(amount); this.balancePublic.set(from.toString(), fromBalance - amount); - } - - public redeemShield(to: AztecAddress, amount: bigint) { const toBalance = this.balancesPrivate.get(to.toString()) || 0n; this.balancesPrivate.set(to.toString(), toBalance + amount); }