diff --git a/.release-please-manifest.json b/.release-please-manifest.json index be5c3462ef5..730ce3899bb 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,7 +1,7 @@ { - ".": "0.26.6", - "yarn-project/cli": "0.26.6", - "yarn-project/aztec": "0.26.6", - "barretenberg": "0.26.6", - "barretenberg/ts": "0.26.6" + ".": "0.27.0", + "yarn-project/cli": "0.27.0", + "yarn-project/aztec": "0.27.0", + "barretenberg": "0.27.0", + "barretenberg/ts": "0.27.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index e8f45aaea9e..e3b71031ea3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,51 @@ # Changelog +## [0.27.0](https://github.com/AztecProtocol/aztec-packages/compare/aztec-packages-v0.26.6...aztec-packages-v0.27.0) (2024-03-12) + + +### ⚠ BREAKING CHANGES + +* Remove open keyword from Noir ([#4967](https://github.com/AztecProtocol/aztec-packages/issues/4967)) + +### Features + +* Add api for inclusion proof of outgoing message in block [#4562](https://github.com/AztecProtocol/aztec-packages/issues/4562) ([#4899](https://github.com/AztecProtocol/aztec-packages/issues/4899)) ([26d2643](https://github.com/AztecProtocol/aztec-packages/commit/26d26437022567e2d54052f21b1c937259f26c94)) +* **avm-simulator:** External calls + integration ([#5051](https://github.com/AztecProtocol/aztec-packages/issues/5051)) ([dde33f4](https://github.com/AztecProtocol/aztec-packages/commit/dde33f498b0432e5c4adce84191d3517176077dd)) +* **avm-simulator:** External static calls + integration ([#5089](https://github.com/AztecProtocol/aztec-packages/issues/5089)) ([428d950](https://github.com/AztecProtocol/aztec-packages/commit/428d950ec1f2dc7b129b61380d7d1426a7b7441d)) +* **avm:** Equivalence check between Main trace and Mem trace ([#5032](https://github.com/AztecProtocol/aztec-packages/issues/5032)) ([7f216eb](https://github.com/AztecProtocol/aztec-packages/commit/7f216eb064fc95791de1286c7695e89575e02b40)), closes [#4955](https://github.com/AztecProtocol/aztec-packages/issues/4955) +* **avm:** Fix some Brillig problems ([#5091](https://github.com/AztecProtocol/aztec-packages/issues/5091)) ([07dd821](https://github.com/AztecProtocol/aztec-packages/commit/07dd8215dffd2c3c6d22e0f430f5072b4ff7c763)) +* Initial integration avm prover ([#4878](https://github.com/AztecProtocol/aztec-packages/issues/4878)) ([2e2554e](https://github.com/AztecProtocol/aztec-packages/commit/2e2554e6a055ff7124e18d1566371d5d108c5d5d)) +* Noir pull action ([#5062](https://github.com/AztecProtocol/aztec-packages/issues/5062)) ([b2d7d14](https://github.com/AztecProtocol/aztec-packages/commit/b2d7d14996722c50c769dfcd9f7b0c324b2e3a7e)) +* Restore contract inclusion proofs ([#5141](https://github.com/AztecProtocol/aztec-packages/issues/5141)) ([a39cd61](https://github.com/AztecProtocol/aztec-packages/commit/a39cd6192022cd14b824d159b4262c10669b7de3)) +* Update the core of SMT Circuit class ([#5096](https://github.com/AztecProtocol/aztec-packages/issues/5096)) ([1519d3b](https://github.com/AztecProtocol/aztec-packages/commit/1519d3b07664f471a43d3f6bbb3dbe2d387289fc)) +* Updating archiver with new inbox ([#5025](https://github.com/AztecProtocol/aztec-packages/issues/5025)) ([f6d17c9](https://github.com/AztecProtocol/aztec-packages/commit/f6d17c972d2cf9c5aa468c8cf954431b42240f87)), closes [#4828](https://github.com/AztecProtocol/aztec-packages/issues/4828) + + +### Bug Fixes + +* Duplicate factory code temporarily to unblock ([#5099](https://github.com/AztecProtocol/aztec-packages/issues/5099)) ([8b10600](https://github.com/AztecProtocol/aztec-packages/commit/8b1060013e35a3b4e73d75b18bb2a8c16985e662)) +* Remove hard coded canonical gas address ([#5106](https://github.com/AztecProtocol/aztec-packages/issues/5106)) ([dc2fd9e](https://github.com/AztecProtocol/aztec-packages/commit/dc2fd9e584d987bdc5d2d7a117b76cb50a20b969)) + + +### Miscellaneous + +* **avm-simulator:** Enable compressed strings unencrypted log test ([#5083](https://github.com/AztecProtocol/aztec-packages/issues/5083)) ([8f7519b](https://github.com/AztecProtocol/aztec-packages/commit/8f7519bdacd3c8b3a91d4361e4648688ec5d47bc)) +* **avm-simulator:** Formatting and fixes ([#5092](https://github.com/AztecProtocol/aztec-packages/issues/5092)) ([b3fa084](https://github.com/AztecProtocol/aztec-packages/commit/b3fa08469658bd7220863e514d8e4b069d40a00f)) +* **AVM:** Negative unit tests for inter table relations ([#5143](https://github.com/AztecProtocol/aztec-packages/issues/5143)) ([a74dccb](https://github.com/AztecProtocol/aztec-packages/commit/a74dccbdef0939b77978ddec3875b1afc2d0b530)), closes [#5033](https://github.com/AztecProtocol/aztec-packages/issues/5033) +* Aztec-macros refactor ([#5127](https://github.com/AztecProtocol/aztec-packages/issues/5127)) ([2195441](https://github.com/AztecProtocol/aztec-packages/commit/2195441afde4d6e78ad0c6027d0a7dbc8671817d)) +* **ci:** Fail on clippy warnings in noir ([#5101](https://github.com/AztecProtocol/aztec-packages/issues/5101)) ([54af648](https://github.com/AztecProtocol/aztec-packages/commit/54af648b5928b200cd40c8d90a21c155bc2e43bd)) +* Extract bb binary in bs fast ([#5128](https://github.com/AztecProtocol/aztec-packages/issues/5128)) ([9ca41ef](https://github.com/AztecProtocol/aztec-packages/commit/9ca41ef6951566622ab9e68924958dbb66b160df)) +* Increase bytecode size limit ([#5098](https://github.com/AztecProtocol/aztec-packages/issues/5098)) ([53b2381](https://github.com/AztecProtocol/aztec-packages/commit/53b238190a9d123c292c3079bb23ed2ecff824c8)) +* Increase permitted bytecode size ([#5136](https://github.com/AztecProtocol/aztec-packages/issues/5136)) ([6865c34](https://github.com/AztecProtocol/aztec-packages/commit/6865c34fccfd74f83525c8d47b5c516d1696c432)) +* Join-split example Part 2 ([#5016](https://github.com/AztecProtocol/aztec-packages/issues/5016)) ([0718320](https://github.com/AztecProtocol/aztec-packages/commit/07183200b136ec39087c2b35e5799686319d561b)) +* Move alpine containers to ubuntu ([#5026](https://github.com/AztecProtocol/aztec-packages/issues/5026)) ([d483e67](https://github.com/AztecProtocol/aztec-packages/commit/d483e678e4b2558f74c3b79083cf2257d6eafe0c)), closes [#4708](https://github.com/AztecProtocol/aztec-packages/issues/4708) +* Nicer snapshots ([#5133](https://github.com/AztecProtocol/aztec-packages/issues/5133)) ([9a737eb](https://github.com/AztecProtocol/aztec-packages/commit/9a737eb9674a757ca3ac9c7a6607ed0f39304d52)) +* Pin foundry ([#5151](https://github.com/AztecProtocol/aztec-packages/issues/5151)) ([69bd7dd](https://github.com/AztecProtocol/aztec-packages/commit/69bd7dd45af6b197b23c25dc883a1a5485955203)) +* Remove old contract deployment flow ([#4970](https://github.com/AztecProtocol/aztec-packages/issues/4970)) ([6d15947](https://github.com/AztecProtocol/aztec-packages/commit/6d1594736e96cd744ea691a239fcd3a46bdade60)) +* Remove open keyword from Noir ([#4967](https://github.com/AztecProtocol/aztec-packages/issues/4967)) ([401557e](https://github.com/AztecProtocol/aztec-packages/commit/401557e1119c1dc4968c16f51381f3306ed8e876)) +* Run nargo fmt on each nargo project ([#5102](https://github.com/AztecProtocol/aztec-packages/issues/5102)) ([b327254](https://github.com/AztecProtocol/aztec-packages/commit/b32725421171f39d510619c8f78a39c182738725)) +* Use context interface in mark-as-initialized ([#5142](https://github.com/AztecProtocol/aztec-packages/issues/5142)) ([932c1d5](https://github.com/AztecProtocol/aztec-packages/commit/932c1d5006ad793ee05ed7cdbae05d59c04334d8)) + ## [0.26.6](https://github.com/AztecProtocol/aztec-packages/compare/aztec-packages-v0.26.5...aztec-packages-v0.26.6) (2024-03-08) diff --git a/barretenberg/CHANGELOG.md b/barretenberg/CHANGELOG.md index d6f4bfe1fe5..b473c000d3d 100644 --- a/barretenberg/CHANGELOG.md +++ b/barretenberg/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## [0.27.0](https://github.com/AztecProtocol/aztec-packages/compare/barretenberg-v0.26.6...barretenberg-v0.27.0) (2024-03-12) + + +### Features + +* **avm:** Equivalence check between Main trace and Mem trace ([#5032](https://github.com/AztecProtocol/aztec-packages/issues/5032)) ([7f216eb](https://github.com/AztecProtocol/aztec-packages/commit/7f216eb064fc95791de1286c7695e89575e02b40)), closes [#4955](https://github.com/AztecProtocol/aztec-packages/issues/4955) +* Initial integration avm prover ([#4878](https://github.com/AztecProtocol/aztec-packages/issues/4878)) ([2e2554e](https://github.com/AztecProtocol/aztec-packages/commit/2e2554e6a055ff7124e18d1566371d5d108c5d5d)) +* Update the core of SMT Circuit class ([#5096](https://github.com/AztecProtocol/aztec-packages/issues/5096)) ([1519d3b](https://github.com/AztecProtocol/aztec-packages/commit/1519d3b07664f471a43d3f6bbb3dbe2d387289fc)) + + +### Miscellaneous + +* **AVM:** Negative unit tests for inter table relations ([#5143](https://github.com/AztecProtocol/aztec-packages/issues/5143)) ([a74dccb](https://github.com/AztecProtocol/aztec-packages/commit/a74dccbdef0939b77978ddec3875b1afc2d0b530)), closes [#5033](https://github.com/AztecProtocol/aztec-packages/issues/5033) +* Extract bb binary in bs fast ([#5128](https://github.com/AztecProtocol/aztec-packages/issues/5128)) ([9ca41ef](https://github.com/AztecProtocol/aztec-packages/commit/9ca41ef6951566622ab9e68924958dbb66b160df)) +* Join-split example Part 2 ([#5016](https://github.com/AztecProtocol/aztec-packages/issues/5016)) ([0718320](https://github.com/AztecProtocol/aztec-packages/commit/07183200b136ec39087c2b35e5799686319d561b)) +* Move alpine containers to ubuntu ([#5026](https://github.com/AztecProtocol/aztec-packages/issues/5026)) ([d483e67](https://github.com/AztecProtocol/aztec-packages/commit/d483e678e4b2558f74c3b79083cf2257d6eafe0c)), closes [#4708](https://github.com/AztecProtocol/aztec-packages/issues/4708) +* Pin foundry ([#5151](https://github.com/AztecProtocol/aztec-packages/issues/5151)) ([69bd7dd](https://github.com/AztecProtocol/aztec-packages/commit/69bd7dd45af6b197b23c25dc883a1a5485955203)) + ## [0.26.6](https://github.com/AztecProtocol/aztec-packages/compare/barretenberg-v0.26.5...barretenberg-v0.26.6) (2024-03-08) diff --git a/barretenberg/cpp/CMakeLists.txt b/barretenberg/cpp/CMakeLists.txt index 8a7cd0012d8..15a4003fb68 100644 --- a/barretenberg/cpp/CMakeLists.txt +++ b/barretenberg/cpp/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.24 FATAL_ERROR) project( Barretenberg DESCRIPTION "BN254 elliptic curve library, and PLONK SNARK prover" - VERSION 0.26.6 # x-release-please-version + VERSION 0.27.0 # x-release-please-version LANGUAGES CXX C ) # Insert version into `bb` config file diff --git a/barretenberg/cpp/scripts/analyze_client_ivc_bench.py b/barretenberg/cpp/scripts/analyze_client_ivc_bench.py index d9b5e047b1f..0e95053b443 100644 --- a/barretenberg/cpp/scripts/analyze_client_ivc_bench.py +++ b/barretenberg/cpp/scripts/analyze_client_ivc_bench.py @@ -7,8 +7,7 @@ # Single out an independent set of functions accounting for most of BENCHMARK's real_time to_keep = [ - "construct_mock_function_circuit(t)", - "construct_mock_folding_kernel(t)", + "construct_circuits(t)", "ProverInstance(Circuit&)(t)", "ProtogalaxyProver::fold_instances(t)", "Decider::construct_proof(t)", @@ -42,3 +41,9 @@ totals = totals.format( sum_of_kept_times_ms, total_time_ms, sum_of_kept_times_ms/total_time_ms) print(totals) + +print('\nBreakdown of ECCVMProver::create_prover:') +for key in ["ECCVMComposer::compute_witness(t)", "ECCVMComposer::create_proving_key(t)"]: + time_ms = bench[key]/1e6 + total_time_ms = bench["ECCVMComposer::create_prover(t)"]/1e6 + print(f"{key:<{MAX_LABEL_LENGTH}}{time_ms:>8.0f} {time_ms/total_time_ms:>8.2%}") diff --git a/barretenberg/cpp/scripts/benchmark.sh b/barretenberg/cpp/scripts/benchmark.sh index 93b96377173..6b6758700f4 100755 --- a/barretenberg/cpp/scripts/benchmark.sh +++ b/barretenberg/cpp/scripts/benchmark.sh @@ -2,16 +2,19 @@ set -eu BENCHMARK=${1:-goblin_bench} -COMMAND=${2:-./bin/$BENCHMARK} +COMMAND=${2:-./$BENCHMARK} +PRESET=${3:-clang16} +BUILD_DIR=${4:-build} + # Move above script dir. cd $(dirname $0)/.. # Configure and build. -cmake --preset clang16 -cmake --build --preset clang16 --target $BENCHMARK +cmake --preset $PRESET +cmake --build --preset $PRESET --target $BENCHMARK -cd build +cd $BUILD_DIR # Consistency with _wasm.sh targets / shorter $COMMAND. cp ./bin/$BENCHMARK . $COMMAND \ No newline at end of file diff --git a/barretenberg/cpp/scripts/benchmark_client_ivc.sh b/barretenberg/cpp/scripts/benchmark_client_ivc.sh index 08e43f012ca..17a193c6d82 100755 --- a/barretenberg/cpp/scripts/benchmark_client_ivc.sh +++ b/barretenberg/cpp/scripts/benchmark_client_ivc.sh @@ -21,5 +21,5 @@ cd $BUILD_DIR scp $BB_SSH_KEY $BB_SSH_INSTANCE:$BB_SSH_CPP_PATH/build/$TARGET.json . # Analyze the results -cd $(dirname $0)/.. +cd ../ python3 ./scripts/analyze_client_ivc_bench.py diff --git a/barretenberg/cpp/src/barretenberg/benchmark/client_ivc_bench/client_ivc.bench.cpp b/barretenberg/cpp/src/barretenberg/benchmark/client_ivc_bench/client_ivc.bench.cpp index f65385da92e..2ace58eb6e6 100644 --- a/barretenberg/cpp/src/barretenberg/benchmark/client_ivc_bench/client_ivc.bench.cpp +++ b/barretenberg/cpp/src/barretenberg/benchmark/client_ivc_bench/client_ivc.bench.cpp @@ -2,6 +2,7 @@ #include #include "barretenberg/client_ivc/client_ivc.hpp" +#include "barretenberg/common/op_count.hpp" #include "barretenberg/common/op_count_google_bench.hpp" #include "barretenberg/goblin/mock_circuits.hpp" #include "barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp" @@ -46,9 +47,12 @@ class ClientIVCBench : public benchmark::Fixture { std::vector initial_function_circuits(2); // Construct 2 starting function circuits in parallel - parallel_for(2, [&](size_t circuit_index) { - GoblinMockCircuits::construct_mock_function_circuit(initial_function_circuits[circuit_index]); - }); + { + BB_OP_COUNT_TIME_NAME("construct_circuits"); + parallel_for(2, [&](size_t circuit_index) { + GoblinMockCircuits::construct_mock_function_circuit(initial_function_circuits[circuit_index]); + }); + }; // Prepend queue to the first circuit initial_function_circuits[0].op_queue->prepend_previous_queue(*ivc.goblin.op_queue); @@ -81,25 +85,28 @@ class ClientIVCBench : public benchmark::Fixture { Builder kernel_circuit{ size_hint, ivc.goblin.op_queue }; Builder function_circuit{ size_hint }; // Construct function and kernel circuits in parallel - parallel_for(2, [&](size_t workload_idx) { - // workload index is 0 for kernel and 1 for function - if (workload_idx == 0) { - if (circuit_idx == 0) { - - // Create the first folding kernel which only verifies the accumulation of a - // function circuit - kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel( - kernel_circuit, function_fold_output, {}, kernel_verifier_accumulator); + { + BB_OP_COUNT_TIME_NAME("construct_circuits"); + parallel_for(2, [&](size_t workload_idx) { + // workload index is 0 for kernel and 1 for function + if (workload_idx == 0) { + if (circuit_idx == 0) { + + // Create the first folding kernel which only verifies the accumulation of a + // function circuit + kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel( + kernel_circuit, function_fold_output, {}, kernel_verifier_accumulator); + } else { + // Create kernel circuit containing the recursive folding verification of a function circuit + // and a kernel circuit + kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel( + kernel_circuit, function_fold_output, kernel_fold_output, kernel_verifier_accumulator); + } } else { - // Create kernel circuit containing the recursive folding verification of a function circuit and - // a kernel circuit - kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel( - kernel_circuit, function_fold_output, kernel_fold_output, kernel_verifier_accumulator); + GoblinMockCircuits::construct_mock_function_circuit(function_circuit); } - } else { - GoblinMockCircuits::construct_mock_function_circuit(function_circuit); - } - }); + }); + }; // No need to prepend queue, it's the same after last swap // Accumulate kernel circuit @@ -127,14 +134,20 @@ class ClientIVCBench : public benchmark::Fixture { // Create and accumulate the first folding kernel which only verifies the accumulation of a function circuit Builder kernel_circuit{ size_hint, ivc.goblin.op_queue }; auto kernel_verifier_accumulator = std::make_shared(ivc.vks.first_func_vk); - kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel( - kernel_circuit, function_fold_output, {}, kernel_verifier_accumulator); + { + BB_OP_COUNT_TIME_NAME("construct_circuits"); + kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel( + kernel_circuit, function_fold_output, {}, kernel_verifier_accumulator); + } auto kernel_fold_proof = ivc.accumulate(kernel_circuit); kernel_fold_output = { kernel_fold_proof, ivc.vks.first_kernel_vk }; } else { Builder kernel_circuit{ size_hint, ivc.goblin.op_queue }; - kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel( - kernel_circuit, function_fold_output, kernel_fold_output, kernel_verifier_accumulator); + { + BB_OP_COUNT_TIME_NAME("construct_circuits"); + kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel( + kernel_circuit, function_fold_output, kernel_fold_output, kernel_verifier_accumulator); + } auto kernel_fold_proof = ivc.accumulate(kernel_circuit); kernel_fold_output = { kernel_fold_proof, ivc.vks.kernel_vk }; diff --git a/barretenberg/cpp/src/barretenberg/benchmark/ultra_bench/ultra_honk_rounds.bench.cpp b/barretenberg/cpp/src/barretenberg/benchmark/ultra_bench/ultra_honk_rounds.bench.cpp index 11ad5e6e15f..aebc60b1912 100644 --- a/barretenberg/cpp/src/barretenberg/benchmark/ultra_bench/ultra_honk_rounds.bench.cpp +++ b/barretenberg/cpp/src/barretenberg/benchmark/ultra_bench/ultra_honk_rounds.bench.cpp @@ -45,11 +45,11 @@ BB_PROFILE static void test_round_inner(State& state, GoblinUltraProver& prover, } }; - time_if_index(PREAMBLE, [&] { prover.execute_preamble_round(); }); - time_if_index(WIRE_COMMITMENTS, [&] { prover.execute_wire_commitments_round(); }); - time_if_index(SORTED_LIST_ACCUMULATOR, [&] { prover.execute_sorted_list_accumulator_round(); }); - time_if_index(LOG_DERIVATIVE_INVERSE, [&] { prover.execute_log_derivative_inverse_round(); }); - time_if_index(GRAND_PRODUCT_COMPUTATION, [&] { prover.execute_grand_product_computation_round(); }); + time_if_index(PREAMBLE, [&] { prover.oink_prover.execute_preamble_round(); }); + time_if_index(WIRE_COMMITMENTS, [&] { prover.oink_prover.execute_wire_commitments_round(); }); + time_if_index(SORTED_LIST_ACCUMULATOR, [&] { prover.oink_prover.execute_sorted_list_accumulator_round(); }); + time_if_index(LOG_DERIVATIVE_INVERSE, [&] { prover.oink_prover.execute_log_derivative_inverse_round(); }); + time_if_index(GRAND_PRODUCT_COMPUTATION, [&] { prover.oink_prover.execute_grand_product_computation_round(); }); time_if_index(RELATION_CHECK, [&] { prover.execute_relation_check_rounds(); }); time_if_index(ZEROMORPH, [&] { prover.execute_zeromorph_rounds(); }); } @@ -62,7 +62,10 @@ BB_PROFILE static void test_round(State& state, size_t index) noexcept auto prover = bb::mock_proofs::get_prover( &bb::mock_proofs::generate_basic_arithmetic_circuit, log2_num_gates); for (auto _ : state) { + state.PauseTiming(); test_round_inner(state, prover, index); + state.ResumeTiming(); + // NOTE: google bench is very finnicky, must end in ResumeTiming() for correctness } } #define ROUND_BENCHMARK(round) \ diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.cpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.cpp index 3f8d20b019f..bf5b4c7316c 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.cpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.cpp @@ -10,6 +10,8 @@ namespace bb { */ template void ECCVMComposer_::compute_witness(CircuitConstructor& circuit_constructor) { + BB_OP_COUNT_TIME_NAME("ECCVMComposer::compute_witness"); + if (computed_witness) { return; } @@ -67,6 +69,8 @@ template std::shared_ptr ECCVMComposer_::compute_proving_key( CircuitConstructor& circuit_constructor) { + BB_OP_COUNT_TIME_NAME("ECCVMComposer::create_proving_key"); + if (proving_key) { return proving_key; } diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.hpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.hpp index 03630f5b239..fdfbd3990c0 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.hpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.hpp @@ -34,8 +34,7 @@ template class ECCVMComposer_ { std::vector recursive_proof_public_input_indices; bool contains_recursive_proof = false; bool computed_witness = false; - ECCVMComposer_() - requires(std::same_as) + ECCVMComposer_() requires(std::same_as) { crs_factory_ = bb::srs::get_grumpkin_crs_factory(); }; @@ -70,6 +69,7 @@ template class ECCVMComposer_ { void compute_commitment_key(size_t circuit_size) { + BB_OP_COUNT_TIME_NAME("ECCVMComposer::compute_commitment_key"); commitment_key = std::make_shared(circuit_size); }; }; diff --git a/barretenberg/cpp/src/barretenberg/goblin/mock_circuits.hpp b/barretenberg/cpp/src/barretenberg/goblin/mock_circuits.hpp index 884e5c27ab8..58cc8fc2d2c 100644 --- a/barretenberg/cpp/src/barretenberg/goblin/mock_circuits.hpp +++ b/barretenberg/cpp/src/barretenberg/goblin/mock_circuits.hpp @@ -109,7 +109,6 @@ class GoblinMockCircuits { */ static void construct_mock_function_circuit(GoblinUltraBuilder& builder, bool large = false) { - BB_OP_COUNT_TIME(); // Determine number of times to execute the below operations that constitute the mock circuit logic. Note that // the circuit size does not scale linearly with number of iterations due to e.g. amortization of lookup costs const size_t NUM_ITERATIONS_LARGE = 13; // results in circuit size 2^19 (521327 gates) @@ -233,7 +232,6 @@ class GoblinMockCircuits { const VerifierFoldData& kernel, std::shared_ptr& prev_kernel_accum) { - BB_OP_COUNT_TIME(); using GURecursiveFlavor = GoblinUltraRecursiveFlavor_; using RecursiveVerifierInstances = bb::stdlib::recursion::honk::RecursiveVerifierInstances_; diff --git a/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy_prover.cpp b/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy_prover.cpp index 65df1840efa..131f52d4c8b 100644 --- a/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy_prover.cpp @@ -1,93 +1,29 @@ #include "protogalaxy_prover.hpp" #include "barretenberg/flavor/flavor.hpp" +#include "barretenberg/ultra_honk/oink_prover.hpp" namespace bb { template void ProtoGalaxyProver_::finalise_and_send_instance(std::shared_ptr instance, const std::string& domain_separator) { - instance->initialize_prover_polynomials(); + OinkProver oink_prover(instance, commitment_key, transcript, domain_separator + '_'); - const auto instance_size = static_cast(instance->proving_key->circuit_size); - const auto num_public_inputs = static_cast(instance->proving_key->num_public_inputs); - transcript->send_to_verifier(domain_separator + "_instance_size", instance_size); - transcript->send_to_verifier(domain_separator + "_public_input_size", num_public_inputs); + // Add circuit size public input size and public inputs to transcript + oink_prover.execute_preamble_round(); - for (size_t i = 0; i < instance->proving_key->public_inputs.size(); ++i) { - auto public_input_i = instance->proving_key->public_inputs[i]; - transcript->send_to_verifier(domain_separator + "_public_input_" + std::to_string(i), public_input_i); - } - transcript->send_to_verifier(domain_separator + "_pub_inputs_offset", - static_cast(instance->proving_key->pub_inputs_offset)); - - auto& witness_commitments = instance->witness_commitments; - - // Commit to the first three wire polynomials of the instance - // We only commit to the fourth wire polynomial after adding memory recordss - witness_commitments.w_l = commitment_key->commit(instance->proving_key->w_l); - witness_commitments.w_r = commitment_key->commit(instance->proving_key->w_r); - witness_commitments.w_o = commitment_key->commit(instance->proving_key->w_o); - - auto wire_comms = witness_commitments.get_wires(); - auto commitment_labels = instance->commitment_labels; - auto wire_labels = commitment_labels.get_wires(); - for (size_t idx = 0; idx < 3; ++idx) { - transcript->send_to_verifier(domain_separator + "_" + wire_labels[idx], wire_comms[idx]); - } - - if constexpr (IsGoblinFlavor) { - // Commit to Goblin ECC op wires - witness_commitments.ecc_op_wire_1 = commitment_key->commit(instance->proving_key->ecc_op_wire_1); - witness_commitments.ecc_op_wire_2 = commitment_key->commit(instance->proving_key->ecc_op_wire_2); - witness_commitments.ecc_op_wire_3 = commitment_key->commit(instance->proving_key->ecc_op_wire_3); - witness_commitments.ecc_op_wire_4 = commitment_key->commit(instance->proving_key->ecc_op_wire_4); - - auto op_wire_comms = instance->witness_commitments.get_ecc_op_wires(); - auto labels = commitment_labels.get_ecc_op_wires(); - for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) { - transcript->send_to_verifier(domain_separator + "_" + labels[idx], op_wire_comms[idx]); - } - // Commit to DataBus columns - witness_commitments.calldata = commitment_key->commit(instance->proving_key->calldata); - witness_commitments.calldata_read_counts = commitment_key->commit(instance->proving_key->calldata_read_counts); - transcript->send_to_verifier(domain_separator + "_" + commitment_labels.calldata, - instance->witness_commitments.calldata); - transcript->send_to_verifier(domain_separator + "_" + commitment_labels.calldata_read_counts, - instance->witness_commitments.calldata_read_counts); - } - - auto eta = transcript->template get_challenge(domain_separator + "_eta"); - instance->compute_sorted_accumulator_polynomials(eta); - - // Commit to the sorted witness-table accumulator and the finalized (i.e. with memory records) fourth wire - // polynomial - witness_commitments.sorted_accum = commitment_key->commit(instance->prover_polynomials.sorted_accum); - witness_commitments.w_4 = commitment_key->commit(instance->prover_polynomials.w_4); + // Compute first three wire commitments + oink_prover.execute_wire_commitments_round(); - transcript->send_to_verifier(domain_separator + "_" + commitment_labels.sorted_accum, - witness_commitments.sorted_accum); - transcript->send_to_verifier(domain_separator + "_" + commitment_labels.w_4, witness_commitments.w_4); - - auto [beta, gamma] = - transcript->template get_challenges(domain_separator + "_beta", domain_separator + "_gamma"); - - if constexpr (IsGoblinFlavor) { - // Compute and commit to the logderivative inverse used in DataBus - instance->compute_logderivative_inverse(beta, gamma); - instance->witness_commitments.lookup_inverses = - commitment_key->commit(instance->prover_polynomials.lookup_inverses); - transcript->send_to_verifier(domain_separator + "_" + commitment_labels.lookup_inverses, - instance->witness_commitments.lookup_inverses); - } + // Compute sorted list accumulator and commitment + oink_prover.execute_sorted_list_accumulator_round(); - instance->compute_grand_product_polynomials(beta, gamma); + // Fiat-Shamir: beta & gamma + oink_prover.execute_log_derivative_inverse_round(); - witness_commitments.z_perm = commitment_key->commit(instance->prover_polynomials.z_perm); - witness_commitments.z_lookup = commitment_key->commit(instance->prover_polynomials.z_lookup); + // Compute grand product(s) and commitments. + oink_prover.execute_grand_product_computation_round(); - transcript->send_to_verifier(domain_separator + "_" + commitment_labels.z_perm, - instance->witness_commitments.z_perm); - transcript->send_to_verifier(domain_separator + "_" + commitment_labels.z_lookup, - instance->witness_commitments.z_lookup); + // Generate relation separators alphas for sumcheck for (size_t idx = 0; idx < NUM_SUBRELATIONS - 1; idx++) { instance->alphas[idx] = transcript->template get_challenge(domain_separator + "_alpha_" + std::to_string(idx)); diff --git a/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy_verifier.cpp b/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy_verifier.cpp index 667460b5b20..cf08f67d30a 100644 --- a/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy_verifier.cpp @@ -8,11 +8,13 @@ void ProtoGalaxyVerifier_::receive_and_finalise_instance(cons { // Get circuit parameters and the public inputs inst->verification_key->circuit_size = - transcript->template receive_from_prover(domain_separator + "_instance_size"); + transcript->template receive_from_prover(domain_separator + "_circuit_size"); inst->verification_key->log_circuit_size = static_cast(numeric::get_msb(inst->verification_key->circuit_size)); inst->verification_key->num_public_inputs = transcript->template receive_from_prover(domain_separator + "_public_input_size"); + inst->verification_key->pub_inputs_offset = + transcript->template receive_from_prover(domain_separator + "_pub_inputs_offset"); inst->verification_key->public_inputs.clear(); for (size_t i = 0; i < inst->verification_key->num_public_inputs; ++i) { auto public_input_i = @@ -20,9 +22,6 @@ void ProtoGalaxyVerifier_::receive_and_finalise_instance(cons inst->verification_key->public_inputs.emplace_back(public_input_i); } - inst->verification_key->pub_inputs_offset = - transcript->template receive_from_prover(domain_separator + "_pub_inputs_offset"); - // Get commitments to first three wire polynomials auto labels = inst->commitment_labels; auto& witness_commitments = inst->witness_commitments; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/protogalaxy_recursive_verifier.cpp b/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/protogalaxy_recursive_verifier.cpp index b460fe76b5b..2eb8fd4f209 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/protogalaxy_recursive_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/protogalaxy_recursive_verifier.cpp @@ -9,13 +9,17 @@ void ProtoGalaxyRecursiveVerifier_::receive_and_finalise_inst const std::shared_ptr& inst, const std::string& domain_separator) { // Get circuit parameters and the public inputs - const auto instance_size = transcript->template receive_from_prover(domain_separator + "_instance_size"); + const auto instance_size = transcript->template receive_from_prover(domain_separator + "_circuit_size"); const auto public_input_size = transcript->template receive_from_prover(domain_separator + "_public_input_size"); inst->verification_key->circuit_size = uint32_t(instance_size.get_value()); inst->verification_key->log_circuit_size = static_cast(numeric::get_msb(inst->verification_key->circuit_size)); inst->verification_key->num_public_inputs = uint32_t(public_input_size.get_value()); + const auto pub_inputs_offset = + transcript->template receive_from_prover(domain_separator + "_pub_inputs_offset"); + inst->verification_key->pub_inputs_offset = uint32_t(pub_inputs_offset.get_value()); + inst->verification_key->public_inputs.clear(); for (size_t i = 0; i < inst->verification_key->num_public_inputs; ++i) { auto public_input_i = @@ -23,11 +27,6 @@ void ProtoGalaxyRecursiveVerifier_::receive_and_finalise_inst inst->verification_key->public_inputs.emplace_back(public_input_i); } - const auto pub_inputs_offset = - transcript->template receive_from_prover(domain_separator + "_pub_inputs_offset"); - - inst->verification_key->pub_inputs_offset = uint32_t(pub_inputs_offset.get_value()); - // Get commitments to first three wire polynomials auto labels = inst->commitment_labels; auto& witness_commitments = inst->witness_commitments; diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.cpp new file mode 100644 index 00000000000..bfb1d0ad97f --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.cpp @@ -0,0 +1,133 @@ +#include "barretenberg/ultra_honk/oink_prover.hpp" + +namespace bb { + +/** + * @brief Add circuit size, public input size, and public inputs to transcript + * + */ +template void OinkProver::execute_preamble_round() +{ + const auto circuit_size = static_cast(instance->proving_key->circuit_size); + const auto num_public_inputs = static_cast(instance->proving_key->num_public_inputs); + transcript->send_to_verifier(domain_separator + "circuit_size", circuit_size); + transcript->send_to_verifier(domain_separator + "public_input_size", num_public_inputs); + transcript->send_to_verifier(domain_separator + "pub_inputs_offset", + static_cast(instance->proving_key->pub_inputs_offset)); + + ASSERT(instance->proving_key->num_public_inputs == instance->proving_key->public_inputs.size()); + + for (size_t i = 0; i < instance->proving_key->num_public_inputs; ++i) { + auto public_input_i = instance->proving_key->public_inputs[i]; + transcript->send_to_verifier(domain_separator + "public_input_" + std::to_string(i), public_input_i); + } +} + +/** + * @brief Commit to the wire polynomials (part of the witness), with the exception of the fourth wire, which is + * only commited to after adding memory records. In the Goblin Flavor, we also commit to the ECC OP wires and the + * DataBus columns. + */ +template void OinkProver::execute_wire_commitments_round() +{ + auto& witness_commitments = instance->witness_commitments; + + // Commit to the first three wire polynomials of the instance + // We only commit to the fourth wire polynomial after adding memory recordss + witness_commitments.w_l = commitment_key->commit(instance->proving_key->w_l); + witness_commitments.w_r = commitment_key->commit(instance->proving_key->w_r); + witness_commitments.w_o = commitment_key->commit(instance->proving_key->w_o); + + auto wire_comms = witness_commitments.get_wires(); + auto& commitment_labels = instance->commitment_labels; + auto wire_labels = commitment_labels.get_wires(); + for (size_t idx = 0; idx < 3; ++idx) { + transcript->send_to_verifier(domain_separator + wire_labels[idx], wire_comms[idx]); + } + + if constexpr (IsGoblinFlavor) { + // Commit to Goblin ECC op wires + witness_commitments.ecc_op_wire_1 = commitment_key->commit(instance->proving_key->ecc_op_wire_1); + witness_commitments.ecc_op_wire_2 = commitment_key->commit(instance->proving_key->ecc_op_wire_2); + witness_commitments.ecc_op_wire_3 = commitment_key->commit(instance->proving_key->ecc_op_wire_3); + witness_commitments.ecc_op_wire_4 = commitment_key->commit(instance->proving_key->ecc_op_wire_4); + + auto op_wire_comms = witness_commitments.get_ecc_op_wires(); + auto labels = commitment_labels.get_ecc_op_wires(); + for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) { + transcript->send_to_verifier(domain_separator + labels[idx], op_wire_comms[idx]); + } + // Commit to DataBus columns + witness_commitments.calldata = commitment_key->commit(instance->proving_key->calldata); + witness_commitments.calldata_read_counts = commitment_key->commit(instance->proving_key->calldata_read_counts); + transcript->send_to_verifier(domain_separator + commitment_labels.calldata, witness_commitments.calldata); + transcript->send_to_verifier(domain_separator + commitment_labels.calldata_read_counts, + witness_commitments.calldata_read_counts); + } +} + +/** + * @brief Compute sorted witness-table accumulator and commit to the resulting polynomials. + * + */ +template void OinkProver::execute_sorted_list_accumulator_round() +{ + auto& witness_commitments = instance->witness_commitments; + const auto& commitment_labels = instance->commitment_labels; + + auto eta = transcript->template get_challenge(domain_separator + "eta"); + instance->compute_sorted_accumulator_polynomials(eta); + + // Commit to the sorted witness-table accumulator and the finalized (i.e. with memory records) fourth wire + // polynomial + witness_commitments.sorted_accum = commitment_key->commit(instance->prover_polynomials.sorted_accum); + witness_commitments.w_4 = commitment_key->commit(instance->prover_polynomials.w_4); + + transcript->send_to_verifier(domain_separator + commitment_labels.sorted_accum, witness_commitments.sorted_accum); + transcript->send_to_verifier(domain_separator + commitment_labels.w_4, witness_commitments.w_4); +} + +/** + * @brief Compute log derivative inverse polynomial and its commitment, if required + * + */ +template void OinkProver::execute_log_derivative_inverse_round() +{ + auto& witness_commitments = instance->witness_commitments; + const auto& commitment_labels = instance->commitment_labels; + + auto [beta, gamma] = transcript->template get_challenges(domain_separator + "beta", domain_separator + "gamma"); + instance->relation_parameters.beta = beta; + instance->relation_parameters.gamma = gamma; + if constexpr (IsGoblinFlavor) { + // Compute and commit to the logderivative inverse used in DataBus + instance->compute_logderivative_inverse(beta, gamma); + witness_commitments.lookup_inverses = commitment_key->commit(instance->prover_polynomials.lookup_inverses); + transcript->send_to_verifier(domain_separator + commitment_labels.lookup_inverses, + witness_commitments.lookup_inverses); + } +} + +/** + * @brief Compute permutation and lookup grand product polynomials and their commitments + * + */ +template void OinkProver::execute_grand_product_computation_round() +{ + auto& witness_commitments = instance->witness_commitments; + const auto& commitment_labels = instance->commitment_labels; + + instance->compute_grand_product_polynomials(instance->relation_parameters.beta, + instance->relation_parameters.gamma); + + witness_commitments.z_perm = commitment_key->commit(instance->prover_polynomials.z_perm); + witness_commitments.z_lookup = commitment_key->commit(instance->prover_polynomials.z_lookup); + + transcript->send_to_verifier(domain_separator + commitment_labels.z_perm, witness_commitments.z_perm); + transcript->send_to_verifier(domain_separator + commitment_labels.z_lookup, witness_commitments.z_lookup); +} + +template class OinkProver; +template class OinkProver; + +} // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.hpp b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.hpp new file mode 100644 index 00000000000..470794d8237 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.hpp @@ -0,0 +1,49 @@ +#pragma once +#include + +#include "barretenberg/flavor/goblin_ultra.hpp" +#include "barretenberg/flavor/ultra.hpp" +#include "barretenberg/sumcheck/instance/prover_instance.hpp" +#include "barretenberg/transcript/transcript.hpp" + +namespace bb { + +/** + * @brief Class for all the oink rounds, which are shared between the folding prover and ultra prover. + * @details This class contains execute_preamble_round(), execute_wire_commitments_round(), + * execute_sorted_list_accumulator_round(), execute_log_derivative_inverse_round(), and + * execute_grand_product_computation_round(). + * + * @tparam Flavor + */ +template class OinkProver { + using CommitmentKey = typename Flavor::CommitmentKey; + using Instance = ProverInstance_; + using Transcript = typename Flavor::Transcript; + using FF = typename Flavor::FF; + + public: + std::shared_ptr instance; + std::shared_ptr transcript; + std::shared_ptr commitment_key; + std::string domain_separator; + + OinkProver(const std::shared_ptr>& inst, + const std::shared_ptr& commitment_key, + const std::shared_ptr& transcript, + std::string domain_separator = "") + : instance(inst) + , transcript(transcript) + , commitment_key(commitment_key) + , domain_separator(std::move(domain_separator)) + { + instance->initialize_prover_polynomials(); + } + + void execute_preamble_round(); + void execute_wire_commitments_round(); + void execute_sorted_list_accumulator_round(); + void execute_log_derivative_inverse_round(); + void execute_grand_product_computation_round(); +}; +} // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.cpp index f6d800558b5..fe78a0d7d88 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.cpp @@ -15,9 +15,8 @@ UltraProver_::UltraProver_(const std::shared_ptr& inst, const : instance(std::move(inst)) , transcript(transcript) , commitment_key(instance->proving_key->commitment_key) -{ - instance->initialize_prover_polynomials(); -} + , oink_prover(inst, commitment_key, transcript, "") +{} /** * Create UltraProver_ from a circuit. @@ -31,130 +30,8 @@ UltraProver_::UltraProver_(Builder& circuit) : instance(std::make_shared(circuit)) , transcript(std::make_shared()) , commitment_key(instance->proving_key->commitment_key) -{ - instance->initialize_prover_polynomials(); -} - -/** - * @brief Add circuit size, public input size, and public inputs to transcript - * - */ -template void UltraProver_::execute_preamble_round() -{ - auto proving_key = instance->proving_key; - const auto circuit_size = static_cast(proving_key->circuit_size); - const auto num_public_inputs = static_cast(proving_key->num_public_inputs); - - transcript->send_to_verifier("circuit_size", circuit_size); - transcript->send_to_verifier("public_input_size", num_public_inputs); - transcript->send_to_verifier("pub_inputs_offset", static_cast(proving_key->pub_inputs_offset)); - - for (size_t i = 0; i < proving_key->num_public_inputs; ++i) { - auto public_input_i = proving_key->public_inputs[i]; - transcript->send_to_verifier("public_input_" + std::to_string(i), public_input_i); - } -} - -/** - * @brief Commit to the wire polynomials (part of the witness), with the exception of the fourth wire, which is - * only commited to after adding memory records. In the Goblin Flavor, we also commit to the ECC OP wires and the - * DataBus columns. - */ -template void UltraProver_::execute_wire_commitments_round() -{ - auto& witness_commitments = instance->witness_commitments; - auto& proving_key = instance->proving_key; - - // Commit to the first three wire polynomials - // We only commit to the fourth wire polynomial after adding memory recordss - witness_commitments.w_l = commitment_key->commit(proving_key->w_l); - witness_commitments.w_r = commitment_key->commit(proving_key->w_r); - witness_commitments.w_o = commitment_key->commit(proving_key->w_o); - - auto wire_comms = witness_commitments.get_wires(); - auto labels = commitment_labels.get_wires(); - for (size_t idx = 0; idx < 3; ++idx) { - transcript->send_to_verifier(labels[idx], wire_comms[idx]); - } - - if constexpr (IsGoblinFlavor) { - // Commit to Goblin ECC op wires - witness_commitments.ecc_op_wire_1 = commitment_key->commit(proving_key->ecc_op_wire_1); - witness_commitments.ecc_op_wire_2 = commitment_key->commit(proving_key->ecc_op_wire_2); - witness_commitments.ecc_op_wire_3 = commitment_key->commit(proving_key->ecc_op_wire_3); - witness_commitments.ecc_op_wire_4 = commitment_key->commit(proving_key->ecc_op_wire_4); - - auto op_wire_comms = instance->witness_commitments.get_ecc_op_wires(); - auto labels = commitment_labels.get_ecc_op_wires(); - for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) { - transcript->send_to_verifier(labels[idx], op_wire_comms[idx]); - } - - // Commit to DataBus columns - witness_commitments.calldata = commitment_key->commit(proving_key->calldata); - witness_commitments.calldata_read_counts = commitment_key->commit(proving_key->calldata_read_counts); - transcript->send_to_verifier(commitment_labels.calldata, instance->witness_commitments.calldata); - transcript->send_to_verifier(commitment_labels.calldata_read_counts, - instance->witness_commitments.calldata_read_counts); - } -} - -/** - * @brief Compute sorted witness-table accumulator and commit to the resulting polynomials. - * - */ -template void UltraProver_::execute_sorted_list_accumulator_round() -{ - FF eta = transcript->template get_challenge("eta"); - - instance->compute_sorted_accumulator_polynomials(eta); - - auto& witness_commitments = instance->witness_commitments; - // Commit to the sorted witness-table accumulator and the finalized (i.e. with memory records) fourth wire - // polynomial - witness_commitments.sorted_accum = commitment_key->commit(instance->prover_polynomials.sorted_accum); - witness_commitments.w_4 = commitment_key->commit(instance->prover_polynomials.w_4); - - transcript->send_to_verifier(commitment_labels.sorted_accum, instance->witness_commitments.sorted_accum); - transcript->send_to_verifier(commitment_labels.w_4, instance->witness_commitments.w_4); -} - -/** - * @brief Compute log derivative inverse polynomial and its commitment, if required - * - */ -template void UltraProver_::execute_log_derivative_inverse_round() -{ - auto& proving_key = instance->proving_key; - - // Compute and store challenges beta and gamma - auto [beta, gamma] = transcript->template get_challenges("beta", "gamma"); - relation_parameters.beta = beta; - relation_parameters.gamma = gamma; - - if constexpr (IsGoblinFlavor) { - instance->compute_logderivative_inverse(beta, gamma); - instance->witness_commitments.lookup_inverses = commitment_key->commit(proving_key->lookup_inverses); - transcript->send_to_verifier(commitment_labels.lookup_inverses, instance->witness_commitments.lookup_inverses); - } -} - -/** - * @brief Compute permutation and lookup grand product polynomials and their commitments - * - */ -template void UltraProver_::execute_grand_product_computation_round() -{ - auto& proving_key = instance->proving_key; - - instance->compute_grand_product_polynomials(relation_parameters.beta, relation_parameters.gamma); - - auto& witness_commitments = instance->witness_commitments; - witness_commitments.z_perm = commitment_key->commit(proving_key->z_perm); - witness_commitments.z_lookup = commitment_key->commit(proving_key->z_lookup); - transcript->send_to_verifier(commitment_labels.z_perm, instance->witness_commitments.z_perm); - transcript->send_to_verifier(commitment_labels.z_lookup, instance->witness_commitments.z_lookup); -} + , oink_prover(instance, commitment_key, transcript, "") +{} /** * @brief Run Sumcheck resulting in u = (u_1,...,u_d) challenges and all evaluations at u being calculated. @@ -203,19 +80,19 @@ template HonkProof& UltraProver_::export_proof() template HonkProof& UltraProver_::construct_proof() { // Add circuit size public input size and public inputs to transcript-> - execute_preamble_round(); + oink_prover.execute_preamble_round(); // Compute first three wire commitments - execute_wire_commitments_round(); + oink_prover.execute_wire_commitments_round(); // Compute sorted list accumulator and commitment - execute_sorted_list_accumulator_round(); + oink_prover.execute_sorted_list_accumulator_round(); // Fiat-Shamir: beta & gamma - execute_log_derivative_inverse_round(); + oink_prover.execute_log_derivative_inverse_round(); // Compute grand product(s) and commitments. - execute_grand_product_computation_round(); + oink_prover.execute_grand_product_computation_round(); // Fiat-Shamir: alpha // Run sumcheck subprotocol. diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.hpp b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.hpp index dd822986f11..46aa631f6cb 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.hpp @@ -7,6 +7,7 @@ #include "barretenberg/sumcheck/instance/prover_instance.hpp" #include "barretenberg/sumcheck/sumcheck_output.hpp" #include "barretenberg/transcript/transcript.hpp" +#include "barretenberg/ultra_honk/oink_prover.hpp" namespace bb { @@ -25,6 +26,21 @@ template class UltraProver_ { using Instance = ProverInstance; using Transcript = typename Flavor::Transcript; using RelationSeparator = typename Flavor::RelationSeparator; + using ZeroMorph = ZeroMorphProver_; + + std::shared_ptr instance; + + std::shared_ptr transcript; + + bb::RelationParameters relation_parameters; + + Polynomial quotient_W; + + SumcheckOutput sumcheck_output; + + std::shared_ptr commitment_key; + + OinkProver oink_prover; explicit UltraProver_(const std::shared_ptr&, const std::shared_ptr& transcript = std::make_shared()); @@ -42,22 +58,6 @@ template class UltraProver_ { HonkProof& export_proof(); HonkProof& construct_proof(); - std::shared_ptr instance; - - std::shared_ptr transcript; - - bb::RelationParameters relation_parameters; - - CommitmentLabels commitment_labels; - - Polynomial quotient_W; - - SumcheckOutput sumcheck_output; - - std::shared_ptr commitment_key; - - using ZeroMorph = ZeroMorphProver_; - private: HonkProof proof; }; diff --git a/barretenberg/ts/CHANGELOG.md b/barretenberg/ts/CHANGELOG.md index 71c48c7cd02..05ca4120681 100644 --- a/barretenberg/ts/CHANGELOG.md +++ b/barretenberg/ts/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.27.0](https://github.com/AztecProtocol/aztec-packages/compare/barretenberg.js-v0.26.6...barretenberg.js-v0.27.0) (2024-03-12) + + +### Miscellaneous + +* Move alpine containers to ubuntu ([#5026](https://github.com/AztecProtocol/aztec-packages/issues/5026)) ([d483e67](https://github.com/AztecProtocol/aztec-packages/commit/d483e678e4b2558f74c3b79083cf2257d6eafe0c)), closes [#4708](https://github.com/AztecProtocol/aztec-packages/issues/4708) + ## [0.26.6](https://github.com/AztecProtocol/aztec-packages/compare/barretenberg.js-v0.26.5...barretenberg.js-v0.26.6) (2024-03-08) diff --git a/barretenberg/ts/package.json b/barretenberg/ts/package.json index 33295f00aff..57454a8df1d 100644 --- a/barretenberg/ts/package.json +++ b/barretenberg/ts/package.json @@ -1,6 +1,6 @@ { "name": "@aztec/bb.js", - "version": "0.26.6", + "version": "0.27.0", "homepage": "https://github.com/AztecProtocol/aztec-packages/tree/master/barretenberg/ts", "license": "MIT", "type": "module", diff --git a/l1-contracts/slither_output.md b/l1-contracts/slither_output.md index a0a7f05ae3c..1d5dc10c830 100644 --- a/l1-contracts/slither_output.md +++ b/l1-contracts/slither_output.md @@ -321,15 +321,15 @@ src/core/messagebridge/Inbox.sol#L148-L153 Impact: Informational Confidence: Medium - [ ] ID-35 -Variable [Constants.LOGS_HASHES_NUM_BYTES_PER_BASE_ROLLUP](src/core/libraries/ConstantsGen.sol#L129) is too similar to [Constants.NOTE_HASHES_NUM_BYTES_PER_BASE_ROLLUP](src/core/libraries/ConstantsGen.sol#L122) +Variable [Constants.LOGS_HASHES_NUM_BYTES_PER_BASE_ROLLUP](src/core/libraries/ConstantsGen.sol#L131) is too similar to [Constants.NOTE_HASHES_NUM_BYTES_PER_BASE_ROLLUP](src/core/libraries/ConstantsGen.sol#L124) -src/core/libraries/ConstantsGen.sol#L129 +src/core/libraries/ConstantsGen.sol#L131 - [ ] ID-36 -Variable [Constants.L1_TO_L2_MESSAGE_LENGTH](src/core/libraries/ConstantsGen.sol#L109) is too similar to [Constants.L2_TO_L1_MESSAGE_LENGTH](src/core/libraries/ConstantsGen.sol#L110) +Variable [Constants.L1_TO_L2_MESSAGE_LENGTH](src/core/libraries/ConstantsGen.sol#L111) is too similar to [Constants.L2_TO_L1_MESSAGE_LENGTH](src/core/libraries/ConstantsGen.sol#L112) -src/core/libraries/ConstantsGen.sol#L109 +src/core/libraries/ConstantsGen.sol#L111 - [ ] ID-37 diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index 6410f22ac56..c953f7d54ea 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -25,6 +25,7 @@ library Constants { uint256 internal constant MAX_PUBLIC_DATA_READS_PER_CALL = 16; uint256 internal constant MAX_NOTE_HASH_READ_REQUESTS_PER_CALL = 32; uint256 internal constant MAX_NULLIFIER_READ_REQUESTS_PER_CALL = 2; + uint256 internal constant MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL = 2; uint256 internal constant MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL = 1; uint256 internal constant MAX_NEW_NOTE_HASHES_PER_TX = 64; uint256 internal constant MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX = 8; @@ -45,6 +46,7 @@ library Constants { uint256 internal constant MAX_NEW_L2_TO_L1_MSGS_PER_TX = 2; uint256 internal constant MAX_NOTE_HASH_READ_REQUESTS_PER_TX = 128; uint256 internal constant MAX_NULLIFIER_READ_REQUESTS_PER_TX = 8; + uint256 internal constant MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX = 8; uint256 internal constant MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX = 4; uint256 internal constant NUM_ENCRYPTED_LOGS_HASHES_PER_TX = 1; uint256 internal constant NUM_UNENCRYPTED_LOGS_HASHES_PER_TX = 1; @@ -113,7 +115,7 @@ library Constants { uint256 internal constant PARTIAL_STATE_REFERENCE_LENGTH = 6; uint256 internal constant PRIVATE_CALL_STACK_ITEM_LENGTH = 214; uint256 internal constant PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH = 209; - uint256 internal constant PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 196; + uint256 internal constant PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 200; uint256 internal constant STATE_REFERENCE_LENGTH = 8; uint256 internal constant TX_CONTEXT_DATA_LENGTH = 4; uint256 internal constant TX_REQUEST_LENGTH = 10; diff --git a/noir-projects/aztec-nr/aztec/src/context/private_context.nr b/noir-projects/aztec-nr/aztec/src/context/private_context.nr index ac987de4251..4bc91a32835 100644 --- a/noir-projects/aztec-nr/aztec/src/context/private_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/private_context.nr @@ -23,7 +23,7 @@ use dep::protocol_types::{ MAX_NEW_NOTE_HASHES_PER_CALL, MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL, MAX_PRIVATE_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, - MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_READ_REQUESTS_PER_CALL, + MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH }, contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest}, @@ -451,6 +451,7 @@ impl PrivateContext { args_hash: reader.read(), return_values: [0; RETURN_VALUES_LENGTH], nullifier_read_requests: [ReadRequest::empty(); MAX_NULLIFIER_READ_REQUESTS_PER_CALL], + nullifier_non_existent_read_requests: [ReadRequest::empty(); MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL], contract_storage_update_requests: [StorageUpdateRequest::empty(); MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL], contract_storage_reads: [StorageRead::empty(); MAX_PUBLIC_DATA_READS_PER_CALL], public_call_stack_hashes: [0; MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL], diff --git a/noir-projects/aztec-nr/aztec/src/context/public_context.nr b/noir-projects/aztec-nr/aztec/src/context/public_context.nr index 510df6b0d0b..352e8f03f38 100644 --- a/noir-projects/aztec-nr/aztec/src/context/public_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/public_context.nr @@ -15,7 +15,8 @@ use dep::protocol_types::{ MAX_NEW_NOTE_HASHES_PER_CALL, MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, - MAX_NULLIFIER_READ_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH + MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, + NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH }, contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest}, hash::hash_args, header::Header, messaging::l2_to_l1_message::L2ToL1Message, utils::reader::Reader @@ -29,6 +30,7 @@ struct PublicContext { return_values : BoundedVec, nullifier_read_requests: BoundedVec, + nullifier_non_existent_read_requests: BoundedVec, contract_storage_update_requests: BoundedVec, contract_storage_reads: BoundedVec, public_call_stack_hashes: BoundedVec, @@ -102,6 +104,7 @@ impl PublicContext { args_hash, return_values: BoundedVec::new(), nullifier_read_requests: BoundedVec::new(), + nullifier_non_existent_read_requests: BoundedVec::new(), contract_storage_update_requests: BoundedVec::new(), contract_storage_reads: BoundedVec::new(), public_call_stack_hashes: BoundedVec::new(), @@ -143,6 +146,7 @@ impl PublicContext { call_context: self.inputs.call_context, // Done args_hash: self.args_hash, // Done nullifier_read_requests: self.nullifier_read_requests.storage, + nullifier_non_existent_read_requests: self.nullifier_non_existent_read_requests.storage, contract_storage_update_requests: self.contract_storage_update_requests.storage, contract_storage_reads: self.contract_storage_reads.storage, return_values: self.return_values.storage, @@ -165,6 +169,12 @@ impl PublicContext { self.side_effect_counter = self.side_effect_counter + 1; } + pub fn push_nullifier_non_existent_read_request(&mut self, nullifier: Field) { + let request = ReadRequest { value: nullifier, counter: self.side_effect_counter }; + self.nullifier_non_existent_read_requests.push(request); + self.side_effect_counter = self.side_effect_counter + 1; + } + pub fn message_portal(&mut self, recipient: EthAddress, content: Field) { let message = L2ToL1Message { recipient, content }; self.new_l2_to_l1_msgs.push(message); diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/common.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/common.nr index 4313375bed7..43c012dc9ba 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/common.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/common.nr @@ -20,9 +20,10 @@ use dep::types::{ hash::{ compute_constructor_hash, compute_l2_to_l1_hash, compute_logs_hash, compute_new_contract_address_hash, function_tree_root_from_siblings, pedersen_hash, - private_functions_root_from_siblings, root_from_sibling_path, silo_note_hash, silo_nullifier, + private_functions_root_from_siblings, silo_note_hash, silo_nullifier, stdlib_recursion_verification_key_compress_native_vk }, + merkle_tree::check_membership, utils::{arrays::{array_length, array_to_bounded_vec, validate_array}}, traits::{is_empty, is_empty_array} }; @@ -72,8 +73,14 @@ pub fn validate_note_hash_read_requests( // but we use the leaf index as a placeholder to detect a 'pending note read'. if (read_request != 0) & (witness.is_transient == false) { - let root_for_read_request = root_from_sibling_path(read_request, witness.leaf_index, witness.sibling_path); - assert(root_for_read_request == historical_note_hash_tree_root, "note hash tree root mismatch"); + assert( + check_membership( + read_request, + witness.leaf_index, + witness.sibling_path, + historical_note_hash_tree_root + ), "note hash tree root mismatch" + ); // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1354): do we need to enforce // that a non-transient read_request was derived from the proper/current contract address? } diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr index b6d6de9fab3..46f9cda62a5 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr @@ -1,12 +1,11 @@ use crate::common; use dep::std::{cmp::Eq, option::Option, unsafe}; -use dep::reset_kernel_lib::{NullifierReadRequestResetHints, reset_read_requests}; +use dep::reset_kernel_lib::{NullifierReadRequestHints, reset_read_requests}; use dep::types::{ abis::{ call_request::CallRequest, nullifier_key_validation_request::NullifierKeyValidationRequestContext, kernel_data::{PrivateKernelInnerData, PrivateKernelTailData}, kernel_circuit_public_inputs::{PrivateKernelCircuitPublicInputsBuilder, PrivateKernelTailCircuitPublicInputs}, - membership_witness::{MembershipWitness, NullifierMembershipWitness}, side_effect::{SideEffect, SideEffectLinkedToNoteHash, Ordered} }, constants::{ @@ -26,7 +25,7 @@ struct PrivateKernelTailCircuitPrivateInputs { read_commitment_hints: [u64; MAX_NOTE_HASH_READ_REQUESTS_PER_TX], sorted_new_nullifiers: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX], sorted_new_nullifiers_indexes: [u64; MAX_NEW_NULLIFIERS_PER_TX], - nullifier_read_request_reset_hints: NullifierReadRequestResetHints, + nullifier_read_request_hints: NullifierReadRequestHints, nullifier_commitment_hints: [u64; MAX_NEW_NULLIFIERS_PER_TX], master_nullifier_secret_keys: [GrumpkinPrivateKey; MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX], } @@ -43,7 +42,7 @@ impl PrivateKernelTailCircuitPrivateInputs { let pending_nullifiers = self.previous_kernel.public_inputs.end.new_nullifiers; - let hints = self.nullifier_read_request_reset_hints; + let hints = self.nullifier_read_request_hints; let nullifier_tree_root = public_inputs.constants.historical_header.state.partial.nullifier_tree.root; @@ -256,7 +255,7 @@ mod tests { use dep::std::{cmp::Eq, unsafe}; use crate::{private_kernel_tail::PrivateKernelTailCircuitPrivateInputs}; use dep::reset_kernel_lib::{ - NullifierReadRequestResetHintsBuilder, + tests::nullifier_read_request_hints_builder::NullifierReadRequestHintsBuilder, read_request_reset::{PendingReadHint, ReadRequestState, ReadRequestStatus} }; use dep::types::constants::{ @@ -277,7 +276,7 @@ mod tests { previous_kernel: PreviousKernelDataBuilder, read_commitment_hints: [u64; MAX_NOTE_HASH_READ_REQUESTS_PER_TX], nullifier_commitment_hints: [u64; MAX_NEW_NULLIFIERS_PER_TX], - nullifier_read_request_reset_hints_builder: NullifierReadRequestResetHintsBuilder, + nullifier_read_request_hints_builder: NullifierReadRequestHintsBuilder, } impl PrivateKernelTailInputsBuilder { @@ -286,7 +285,7 @@ mod tests { previous_kernel: PreviousKernelDataBuilder::new(false), read_commitment_hints: [0; MAX_NOTE_HASH_READ_REQUESTS_PER_TX], nullifier_commitment_hints: [0; MAX_NEW_NULLIFIERS_PER_TX], - nullifier_read_request_reset_hints_builder: NullifierReadRequestResetHintsBuilder::new(MAX_NULLIFIER_READ_REQUESTS_PER_TX) + nullifier_read_request_hints_builder: NullifierReadRequestHintsBuilder::new(MAX_NULLIFIER_READ_REQUESTS_PER_TX) } } @@ -326,10 +325,10 @@ mod tests { pub fn add_nullifier_pending_read(&mut self, nullifier_index_offset_one: u64) { let nullifier_index = nullifier_index_offset_one + 1; // + 1 is for the first nullifier let read_request_index = self.previous_kernel.add_read_request_for_pending_nullifier(nullifier_index); - let hint_index = self.nullifier_read_request_reset_hints_builder.pending_read_hints.len(); + let hint_index = self.nullifier_read_request_hints_builder.pending_read_hints.len(); let hint = PendingReadHint { read_request_index, pending_value_index: nullifier_index }; - self.nullifier_read_request_reset_hints_builder.pending_read_hints.push(hint); - self.nullifier_read_request_reset_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index }; + self.nullifier_read_request_hints_builder.pending_read_hints.push(hint); + self.nullifier_read_request_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index }; } pub fn nullify_transient_commitment(&mut self, nullifier_index: Field, commitment_index: u64) { @@ -383,7 +382,7 @@ mod tests { read_commitment_hints: sorted_read_commitment_hints, sorted_new_nullifiers, sorted_new_nullifiers_indexes, - nullifier_read_request_reset_hints: self.nullifier_read_request_reset_hints_builder.to_hints(), + nullifier_read_request_hints: self.nullifier_read_request_hints_builder.to_hints(), nullifier_commitment_hints: sorted_nullifier_commitment_hints, master_nullifier_secret_keys: unsafe::zeroed() }; @@ -480,10 +479,10 @@ mod tests { builder.append_nullifiers(3); builder.add_nullifier_pending_read(1); - let mut hint = builder.nullifier_read_request_reset_hints_builder.pending_read_hints.pop(); + let mut hint = builder.nullifier_read_request_hints_builder.pending_read_hints.pop(); assert(hint.pending_value_index == 2); hint.pending_value_index = 1; - builder.nullifier_read_request_reset_hints_builder.pending_read_hints.push(hint); + builder.nullifier_read_request_hints_builder.pending_read_hints.push(hint); builder.failed(); } diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr index 7df1fac5ffa..550649723f8 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr @@ -10,9 +10,10 @@ use dep::types::{ contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest}, constants::{ MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NOTE_HASHES_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL, - MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_PUBLIC_DATA_READS_PER_CALL, NUM_FIELDS_PER_SHA256, - MAX_REVERTIBLE_PUBLIC_DATA_READS_PER_TX, MAX_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, + MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_PUBLIC_DATA_READS_PER_CALL, NUM_FIELDS_PER_SHA256, MAX_REVERTIBLE_PUBLIC_DATA_READS_PER_TX, + MAX_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX }, hash::{silo_note_hash, silo_nullifier, compute_l2_to_l1_hash, accumulate_sha256}, @@ -89,6 +90,7 @@ pub fn initialize_end_values( let start_non_revertible = previous_kernel.public_inputs.end_non_revertible; circuit_outputs.end_non_revertible.public_call_stack = array_to_bounded_vec(start_non_revertible.public_call_stack); circuit_outputs.end_non_revertible.nullifier_read_requests = array_to_bounded_vec(start_non_revertible.nullifier_read_requests); + circuit_outputs.end_non_revertible.nullifier_non_existent_read_requests = array_to_bounded_vec(start_non_revertible.nullifier_non_existent_read_requests); } fn perform_static_call_checks(public_call: PublicCallData) { @@ -161,6 +163,7 @@ pub fn update_public_end_non_revertible_values( circuit_outputs.end_non_revertible.public_call_stack.extend_from_bounded_vec(public_call_requests); propagate_nullifier_read_requests_non_revertible(public_call, circuit_outputs); + propagate_nullifier_non_existent_read_requests_non_revertible(public_call, circuit_outputs); propagate_new_nullifiers_non_revertible(public_call, circuit_outputs); propagate_new_note_hashes_non_revertible(public_call, circuit_outputs); propagate_valid_non_revertible_public_data_update_requests(public_call, circuit_outputs); @@ -182,6 +185,8 @@ pub fn update_public_end_values(public_call: PublicCallData, circuit_outputs: &m circuit_outputs.end.public_call_stack.extend_from_bounded_vec(public_call_requests); propagate_nullifier_read_requests_revertible(public_call, circuit_outputs); + propagate_nullifier_non_existent_read_requests_non_revertible(public_call, circuit_outputs); // TODO - Requests are not revertible and should be propagated to "validation_requests". + propagate_new_nullifiers(public_call, circuit_outputs); propagate_new_note_hashes(public_call, circuit_outputs); @@ -224,6 +229,22 @@ fn propagate_nullifier_read_requests_revertible( } } +fn propagate_nullifier_non_existent_read_requests_non_revertible( + public_call: PublicCallData, + circuit_outputs: &mut PublicKernelCircuitPublicInputsBuilder +) { + let public_call_public_inputs = public_call.call_stack_item.public_inputs; + let nullifier_non_existent_read_requests = public_call_public_inputs.nullifier_non_existent_read_requests; + let storage_contract_address = public_call_public_inputs.call_context.storage_contract_address; + + for i in 0..MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL { + let request = nullifier_non_existent_read_requests[i]; + if !is_empty(request) { + circuit_outputs.end_non_revertible.nullifier_non_existent_read_requests.push(request.to_context(storage_contract_address)); + } + } +} + fn propagate_valid_public_data_update_requests( public_call: PublicCallData, circuit_outputs: &mut PublicKernelCircuitPublicInputsBuilder diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_app_logic.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_app_logic.nr index 546e026f156..defa25ed06e 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_app_logic.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_app_logic.nr @@ -75,14 +75,14 @@ mod tests { use dep::types::{ abis::{ kernel_circuit_public_inputs::PublicKernelCircuitPublicInputs, public_data_read::PublicDataRead, - public_data_update_request::PublicDataUpdateRequest, + public_data_update_request::PublicDataUpdateRequest, read_request::ReadRequest, side_effect::{SideEffect, SideEffectLinkedToNoteHash} }, address::{AztecAddress, EthAddress}, contract_class_id::ContractClassId, hash::{compute_l2_to_l1_hash, compute_logs_hash, silo_note_hash, silo_nullifier}, messaging::l2_to_l1_message::L2ToL1Message, tests::{kernel_data_builder::PreviousKernelDataBuilder, public_call_data_builder::PublicCallDataBuilder}, - utils::{arrays::array_eq} + utils::arrays::{array_eq, array_length} }; use dep::types::constants::{MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL}; @@ -419,4 +419,30 @@ mod tests { ); assert_eq_public_data_reads(public_inputs.end.public_data_reads, read_requests); } + + #[test] + fn propagate_nullifier_non_existent_read_requests() { + let mut builder = PublicKernelAppLogicCircuitPrivateInputsBuilder::new(); + let storage_contract_address = builder.public_call.public_inputs.call_context.storage_contract_address; + + let request_0 = ReadRequest { value: 123, counter: 4567 }; + builder.public_call.public_inputs.nullifier_non_existent_read_requests.push(request_0); + let request_1 = ReadRequest { value: 777888, counter: 90 }; + builder.public_call.public_inputs.nullifier_non_existent_read_requests.push(request_1); + + let public_inputs = builder.execute(); + + let end_requests = public_inputs.end_non_revertible.nullifier_non_existent_read_requests; + assert_eq(array_length(end_requests), 2); + + let request_context = end_requests[0]; + assert_eq(request_context.value, request_0.value); + assert_eq(request_context.counter, request_0.counter); + assert_eq(request_context.contract_address, storage_contract_address); + + let request_context = end_requests[1]; + assert_eq(request_context.value, request_1.value); + assert_eq(request_context.counter, request_1.counter); + assert_eq(request_context.contract_address, storage_contract_address); + } } diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_setup.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_setup.nr index a08b158dd3f..560b2b848e6 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_setup.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_setup.nr @@ -78,7 +78,8 @@ mod tests { abis::{ call_request::CallRequest, function_selector::FunctionSelector, kernel_circuit_public_inputs::PublicKernelCircuitPublicInputs, public_data_read::PublicDataRead, - public_data_update_request::PublicDataUpdateRequest, public_call_data::PublicCallData + public_data_update_request::PublicDataUpdateRequest, public_call_data::PublicCallData, + read_request::ReadRequest }, address::{AztecAddress, EthAddress}, contract_class_id::ContractClassId, contrakt::storage_read::StorageRead, hash::compute_logs_hash, @@ -496,4 +497,30 @@ mod tests { let expected_unencrypted_logs_hash = compute_logs_hash(prev_unencrypted_logs_hash, unencrypted_logs_hash); assert_eq(public_inputs.end.unencrypted_logs_hash, expected_unencrypted_logs_hash); } + + #[test] + fn propagate_nullifier_non_existent_read_requests() { + let mut builder = PublicKernelSetupCircuitPrivateInputsBuilder::new(); + let storage_contract_address = builder.public_call.public_inputs.call_context.storage_contract_address; + + let request_0 = ReadRequest { value: 123, counter: 4567 }; + builder.public_call.public_inputs.nullifier_non_existent_read_requests.push(request_0); + let request_1 = ReadRequest { value: 777888, counter: 90 }; + builder.public_call.public_inputs.nullifier_non_existent_read_requests.push(request_1); + + let public_inputs = builder.execute(); + + let end_requests = public_inputs.end_non_revertible.nullifier_non_existent_read_requests; + assert_eq(array_length(end_requests), 2); + + let request_context = end_requests[0]; + assert_eq(request_context.value, request_0.value); + assert_eq(request_context.counter, request_0.counter); + assert_eq(request_context.contract_address, storage_contract_address); + + let request_context = end_requests[1]; + assert_eq(request_context.value, request_1.value); + assert_eq(request_context.counter, request_1.counter); + assert_eq(request_context.contract_address, storage_contract_address); + } } diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr index d6242fe3292..f0ed14abc3c 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr @@ -1,17 +1,23 @@ use crate::common; -use dep::reset_kernel_lib::{NullifierReadRequestResetHints, reset_read_requests}; +use dep::reset_kernel_lib::{ + NullifierReadRequestHints, NullifierNonExistentReadRequestHints, reset_non_existent_read_requests, + reset_read_requests +}; use dep::types::{ abis::{ kernel_circuit_public_inputs::{PublicKernelCircuitPublicInputs, PublicKernelCircuitPublicInputsBuilder}, kernel_data::PublicKernelData, side_effect::SideEffectLinkedToNoteHash }, - constants::MAX_NEW_NULLIFIERS_PER_TX, utils::{arrays::{array_length, array_merge, array_concat}} + constants::MAX_NEW_NULLIFIERS_PER_TX, + utils::{arrays::{array_length, array_merge, array_concat, array_to_bounded_vec, assert_sorted_array}}, + hash::silo_nullifier, traits::is_empty }; use dep::std::unsafe; struct PublicKernelTailCircuitPrivateInputs { previous_kernel: PublicKernelData, - nullifier_read_request_reset_hints: NullifierReadRequestResetHints, + nullifier_read_request_hints: NullifierReadRequestHints, + nullifier_non_existent_read_request_hints: NullifierNonExistentReadRequestHints, } impl PublicKernelTailCircuitPrivateInputs { @@ -44,7 +50,7 @@ impl PublicKernelTailCircuitPrivateInputs { let pending_nullifiers: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX] = array_concat(end_non_revertible.new_nullifiers, end.new_nullifiers); - let hints = self.nullifier_read_request_reset_hints; + let hints = self.nullifier_read_request_hints; let nullifier_tree_root = public_inputs.constants.historical_header.state.partial.nullifier_tree.root; @@ -62,6 +68,42 @@ impl PublicKernelTailCircuitPrivateInputs { ); } + fn validate_nullifier_non_existent_read_requests(self, public_inputs: &mut PublicKernelCircuitPublicInputsBuilder) { + let end_non_revertible = self.previous_kernel.public_inputs.end_non_revertible; + let end = self.previous_kernel.public_inputs.end; + + // The values of the read requests here need to be siloed. + // Notice that it's not the case for regular read requests, which can be run between two kernel iterations, and will to be verified against unsiloed pending values. + let mut read_requests = end_non_revertible.nullifier_non_existent_read_requests; + for i in 0..read_requests.len() { + let read_request = read_requests[i]; + if !is_empty(read_request) { + read_requests[i].value = silo_nullifier(read_request.contract_address, read_request.value); + } + } + + let nullifier_tree_root = public_inputs.constants.historical_header.state.partial.nullifier_tree.root; + + let hints = self.nullifier_non_existent_read_request_hints; + + let pending_nullifiers: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX] = array_concat(end_non_revertible.new_nullifiers, end.new_nullifiers); + assert_sorted_array( + pending_nullifiers, + hints.sorted_pending_values, + hints.sorted_pending_value_index_hints, + |a: SideEffectLinkedToNoteHash, b: SideEffectLinkedToNoteHash| a.value.lt(b.value) + ); + let sorted_pending_nullifiers = array_to_bounded_vec(hints.sorted_pending_values); + + reset_non_existent_read_requests( + read_requests, + hints.non_membership_hints, + nullifier_tree_root, + sorted_pending_nullifiers, + hints.next_pending_value_indices + ); + } + pub fn public_kernel_tail(self) -> PublicKernelCircuitPublicInputs { let mut public_inputs: PublicKernelCircuitPublicInputsBuilder = unsafe::zeroed(); @@ -73,6 +115,8 @@ impl PublicKernelTailCircuitPrivateInputs { self.validate_nullifier_read_requests(&mut public_inputs); + self.validate_nullifier_non_existent_read_requests(&mut public_inputs); + public_inputs.to_inner() } } @@ -80,57 +124,114 @@ impl PublicKernelTailCircuitPrivateInputs { mod tests { use crate::{public_kernel_tail::PublicKernelTailCircuitPrivateInputs}; use dep::reset_kernel_lib::{ - NullifierReadRequestResetHintsBuilder, + tests::{ + nullifier_non_existent_read_request_hints_builder::NullifierNonExistentReadRequestHintsBuilder, + nullifier_read_request_hints_builder::NullifierReadRequestHintsBuilder + }, read_request_reset::{PendingReadHint, ReadRequestState, ReadRequestStatus} }; use dep::types::{ abis::{ kernel_circuit_public_inputs::{PublicKernelCircuitPublicInputs, PublicKernelCircuitPublicInputsBuilder}, - kernel_data::PublicKernelData + kernel_data::PublicKernelData, nullifier_leaf_preimage::NullifierLeafPreimage }, - constants::MAX_NULLIFIER_READ_REQUESTS_PER_TX, - tests::{kernel_data_builder::PreviousKernelDataBuilder} + constants::{ + MAX_NEW_NULLIFIERS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT, + NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_SUBTREE_HEIGHT + }, + hash::silo_nullifier, + tests::{kernel_data_builder::PreviousKernelDataBuilder, merkle_tree_utils::NonEmptyMerkleTree}, + utils::arrays::array_concat }; + fn build_nullifier_tree() -> NonEmptyMerkleTree { + let mut pre_existing_nullifiers = [NullifierLeafPreimage::empty(); MAX_NEW_NULLIFIERS_PER_TX]; + pre_existing_nullifiers[0] = NullifierLeafPreimage { nullifier: 0, next_nullifier: 100, next_index: 1 }; + pre_existing_nullifiers[1] = NullifierLeafPreimage { nullifier: 100, next_nullifier: 0, next_index: 0 }; + NonEmptyMerkleTree::new( + pre_existing_nullifiers.map(|preimage: NullifierLeafPreimage| preimage.hash()), + [0; NULLIFIER_TREE_HEIGHT], + [0; NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT], + [0; NULLIFIER_SUBTREE_HEIGHT] + ) + } + struct PublicKernelTailCircuitPrivateInputsBuilder { previous_kernel: PreviousKernelDataBuilder, - nullifier_read_request_reset_hints_builder: NullifierReadRequestResetHintsBuilder, + nullifier_read_request_hints_builder: NullifierReadRequestHintsBuilder, + nullifier_non_existent_read_request_hints_builder: NullifierNonExistentReadRequestHintsBuilder, } impl PublicKernelTailCircuitPrivateInputsBuilder { pub fn new() -> Self { let previous_kernel = PreviousKernelDataBuilder::new(true); + let mut nullifier_non_existent_read_request_hints_builder = NullifierNonExistentReadRequestHintsBuilder::new(); - PublicKernelTailCircuitPrivateInputsBuilder { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder { previous_kernel, - nullifier_read_request_reset_hints_builder: NullifierReadRequestResetHintsBuilder::new(MAX_NULLIFIER_READ_REQUESTS_PER_TX) - } + nullifier_read_request_hints_builder: NullifierReadRequestHintsBuilder::new(MAX_NULLIFIER_READ_REQUESTS_PER_TX), + nullifier_non_existent_read_request_hints_builder + }; + builder.set_nullifiers_for_non_existent_read_request_hints(); + builder + } + + pub fn with_nullifier_tree(&mut self) -> Self { + let nullifier_tree = build_nullifier_tree(); + self.previous_kernel.historical_header.state.partial.nullifier_tree.root = nullifier_tree.get_root(); + self.nullifier_non_existent_read_request_hints_builder.set_nullifier_tree(nullifier_tree); + *self + } + + pub fn add_nullifier(&mut self, unsiloed_nullifier: Field) { + self.previous_kernel.add_nullifier(unsiloed_nullifier); + self.set_nullifiers_for_non_existent_read_request_hints(); } pub fn append_nullifiers(&mut self, num_nullifiers: u64) { self.previous_kernel.append_new_nullifiers_from_public(num_nullifiers); + self.set_nullifiers_for_non_existent_read_request_hints(); } pub fn append_nullifiers_non_revertible(&mut self, num_nullifiers: u64) { self.previous_kernel.append_new_nullifiers_non_revertible_from_public(num_nullifiers); + self.set_nullifiers_for_non_existent_read_request_hints(); + } + + fn set_nullifiers_for_non_existent_read_request_hints(&mut self) { + let previous_kernel_public_inputs = self.previous_kernel.to_public_kernel_data().public_inputs; + let nullifiers = array_concat( + previous_kernel_public_inputs.end_non_revertible.new_nullifiers, + previous_kernel_public_inputs.end.new_nullifiers + ); + self.nullifier_non_existent_read_request_hints_builder.set_nullifiers(nullifiers); } pub fn add_nullifier_pending_read(&mut self, nullifier_index: u64) { let read_request_index = self.previous_kernel.add_read_request_for_pending_nullifier(nullifier_index); - let hint_index = self.nullifier_read_request_reset_hints_builder.pending_read_hints.len(); + let hint_index = self.nullifier_read_request_hints_builder.pending_read_hints.len(); let pending_value_index = nullifier_index + self.previous_kernel.end_non_revertible.new_nullifiers.len(); let hint = PendingReadHint { read_request_index, pending_value_index }; - self.nullifier_read_request_reset_hints_builder.pending_read_hints.push(hint); - self.nullifier_read_request_reset_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index }; + self.nullifier_read_request_hints_builder.pending_read_hints.push(hint); + self.nullifier_read_request_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index }; } pub fn add_nullifier_pending_read_non_revertible(&mut self, nullifier_index_offset_one: u64) { let nullifier_index = nullifier_index_offset_one + 1; // + 1 is for the first nullifier let read_request_index = self.previous_kernel.add_read_request_for_pending_nullifier_non_revertible(nullifier_index); - let hint_index = self.nullifier_read_request_reset_hints_builder.pending_read_hints.len(); + let hint_index = self.nullifier_read_request_hints_builder.pending_read_hints.len(); let hint = PendingReadHint { read_request_index, pending_value_index: nullifier_index }; - self.nullifier_read_request_reset_hints_builder.pending_read_hints.push(hint); - self.nullifier_read_request_reset_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index }; + self.nullifier_read_request_hints_builder.pending_read_hints.push(hint); + self.nullifier_read_request_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index }; + } + + pub fn read_non_existent_nullifier(&mut self, unsiloed_nullifier: Field) { + self.previous_kernel.add_non_existent_read_request_for_nullifier(unsiloed_nullifier); + let siloed_nullifier = silo_nullifier( + self.previous_kernel.storage_contract_address, + unsiloed_nullifier + ); + self.nullifier_non_existent_read_request_hints_builder.add_value_read(siloed_nullifier); } pub fn execute(&mut self) -> PublicKernelCircuitPublicInputs { @@ -138,7 +239,8 @@ mod tests { let kernel = PublicKernelTailCircuitPrivateInputs { previous_kernel, - nullifier_read_request_reset_hints: self.nullifier_read_request_reset_hints_builder.to_hints() + nullifier_read_request_hints: self.nullifier_read_request_hints_builder.to_hints(), + nullifier_non_existent_read_request_hints: self.nullifier_non_existent_read_request_hints_builder.to_hints() }; kernel.public_kernel_tail() @@ -154,9 +256,10 @@ mod tests { } #[test] - fn public_kernel_circuit_tail_succeeds() { + unconstrained fn public_kernel_circuit_tail_succeeds() { let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new(); builder.succeeded(); + // TODO: Check the values in public inputs. } #[test] @@ -194,9 +297,9 @@ mod tests { builder.append_nullifiers(3); builder.add_nullifier_pending_read(1); - let mut hint = builder.nullifier_read_request_reset_hints_builder.pending_read_hints.pop(); + let mut hint = builder.nullifier_read_request_hints_builder.pending_read_hints.pop(); hint.pending_value_index -= 1; - builder.nullifier_read_request_reset_hints_builder.pending_read_hints.push(hint); + builder.nullifier_read_request_hints_builder.pending_read_hints.push(hint); builder.failed(); } @@ -214,4 +317,32 @@ mod tests { builder.failed(); } + + // TODO: Add tests for reading (non-existent) settled values. + + #[test] + unconstrained fn nullifier_non_existent_read_request() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new().with_nullifier_tree(); + + builder.add_nullifier(3); + builder.add_nullifier(1); + builder.add_nullifier(9); + + builder.read_non_existent_nullifier(8); + + builder.succeeded(); + } + + #[test(should_fail_with="Value exists in pending set")] + unconstrained fn nullifier_non_existent_read_request_failed_read_exist() { + let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new().with_nullifier_tree(); + + builder.add_nullifier(3); + builder.add_nullifier(1); + builder.add_nullifier(9); + + builder.read_non_existent_nullifier(1); + + builder.succeeded(); + } } diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/lib.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/lib.nr index 8e41dc16d8d..07a22b9eb44 100644 --- a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/lib.nr +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/lib.nr @@ -1,6 +1,10 @@ +use non_existent_read_request_reset::reset_non_existent_read_requests; +use nullifier_non_existent_read_request_reset::NullifierNonExistentReadRequestHints; +use nullifier_read_request_reset::NullifierReadRequestHints; use read_request_reset::reset_read_requests; -use nullifier_read_request_reset::NullifierReadRequestResetHints; -use nullifier_read_request_reset::NullifierReadRequestResetHintsBuilder; -mod read_request_reset; +mod non_existent_read_request_reset; +mod nullifier_non_existent_read_request_reset; mod nullifier_read_request_reset; +mod read_request_reset; +mod tests; diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/non_existent_read_request_reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/non_existent_read_request_reset.nr new file mode 100644 index 00000000000..4beeee665ae --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/non_existent_read_request_reset.nr @@ -0,0 +1,354 @@ +use dep::types::{ + abis::{side_effect::OrderedValue, read_request::ReadRequestContext}, + merkle_tree::{assert_check_non_membership, IndexedTreeLeafPreimage, MembershipWitness}, + traits::{Empty, is_empty} +}; + +trait NonMembershipHint where LEAF_PREIMAGE: IndexedTreeLeafPreimage { + fn low_leaf_preimage(self) -> LEAF_PREIMAGE; + fn membership_witness(self) -> MembershipWitness; +} + +fn check_no_matching_pending_value( + read_request: ReadRequestContext, + sorted_pending_values: BoundedVec, + next_value_index: u64 +) -> bool where T: OrderedValue { + if next_value_index == sorted_pending_values.len() { + let highest_value = sorted_pending_values.get_unchecked(sorted_pending_values.len() - 1).value(); + highest_value.lt(read_request.value) + } else { + let next_value = sorted_pending_values.get_unchecked(next_value_index).value(); + let is_less_than_next = read_request.value.lt(next_value); + let is_greater_than_prev = if next_value_index == 0 { + true + } else { + let prev_value = sorted_pending_values.get_unchecked(next_value_index - 1).value(); + prev_value.lt(read_request.value) + }; + is_less_than_next & is_greater_than_prev + } +} + +fn check_is_read_before_pending_value( + read_request: ReadRequestContext, + sorted_pending_values: BoundedVec, + next_value_index: u64 +) -> bool where T: OrderedValue { + if next_value_index == sorted_pending_values.len() { + false + } else { + let pending = sorted_pending_values.get_unchecked(next_value_index); + if pending.value() == read_request.value { + assert(read_request.counter < pending.counter(), "Value exists in pending set"); + true + } else { + false + } + } +} + +// Unlike regular read requests, which can be reset at any time between two function executions. +// Non existent read requests can only be verified at the end, after all pending values are present. +// The values in read_requests and in sorted_pending_values should've been siloed before calling this. +pub fn reset_non_existent_read_requests( + siloed_read_requests: [ReadRequestContext; N], + non_membership_hints: [NON_MEMBERSHIP_HINT; N], + tree_root: Field, + sorted_pending_values: BoundedVec, + next_pending_value_indices: [u64; N] +) where + T: OrderedValue, + NON_MEMBERSHIP_HINT: NonMembershipHint, + LEAF_PREIMAGE: IndexedTreeLeafPreimage { + for i in 0..siloed_read_requests.len() { + let read_request = siloed_read_requests[i]; + if !is_empty(read_request) { + // Verify that it's not in the tree. + let hint = non_membership_hints[i]; + assert_check_non_membership( + read_request.value, + hint.low_leaf_preimage(), + hint.membership_witness(), + tree_root + ); + + // Verify that its value is either not in the pending set, or is created after the read. + let next_value_index = next_pending_value_indices[i]; + assert( + next_value_index <= sorted_pending_values.len(), "Next pending value index out of bounds" + ); + let no_matching_value = check_no_matching_pending_value(read_request, sorted_pending_values, next_value_index); + let is_read_before_value = check_is_read_before_pending_value(read_request, sorted_pending_values, next_value_index); + assert(no_matching_value | is_read_before_value, "Invalid next pending value index"); + } + } +} + +mod tests { + use crate::non_existent_read_request_reset::{NonMembershipHint, reset_non_existent_read_requests}; + + use dep::types::{ + address::AztecAddress, abis::{read_request::ReadRequestContext, side_effect::SideEffect}, + merkle_tree::{leaf_preimage::IndexedTreeLeafPreimage, membership::MembershipWitness}, + hash::silo_nullifier, tests::merkle_tree_utils::NonEmptyMerkleTree + }; + + struct TestLeafPreimage { + value: Field, + next_value: Field, + } + + impl IndexedTreeLeafPreimage for TestLeafPreimage { + fn get_key(self) -> Field { + self.value + } + + fn get_next_key(self) -> Field { + self.next_value + } + + fn as_leaf(self) -> Field { + self.value * 100 + } + } + + struct TestNonMembershipHint { + low_leaf_preimage: TestLeafPreimage, + membership_witness: MembershipWitness<3>, + } + + impl NonMembershipHint<3, TestLeafPreimage> for TestNonMembershipHint { + fn low_leaf_preimage(self) -> TestLeafPreimage { + self.low_leaf_preimage + } + + fn membership_witness(self) -> MembershipWitness<3> { + self.membership_witness + } + } + + global sorted_pending_values = BoundedVec { + storage: [ + SideEffect { value: 5, counter: 17 }, + SideEffect { value: 15, counter: 8 }, + SideEffect { value: 25, counter: 11 }, + SideEffect::empty(), + SideEffect::empty(), + ], + len: 3, + }; + + global leaf_preimages = [ + TestLeafPreimage { value: 0, next_value: 10 }, + TestLeafPreimage { value: 20, next_value: 30 }, + TestLeafPreimage { value: 30, next_value: 0 }, + TestLeafPreimage { value: 10, next_value: 20 }, + ]; + + fn build_tree() -> NonEmptyMerkleTree<4, 3, 1, 2> { + NonEmptyMerkleTree::new( + leaf_preimages.map(|leaf_preimage: TestLeafPreimage| leaf_preimage.as_leaf()), + [0; 3], + [0; 1], + [0; 2] + ) + } + + fn get_non_membership_hints(leaf_indices: [Field; N]) -> ([TestNonMembershipHint; N], Field) { + let tree = build_tree(); + let hints = leaf_indices.map( + |leaf_index| TestNonMembershipHint { + low_leaf_preimage: leaf_preimages[leaf_index], + membership_witness: MembershipWitness { leaf_index, sibling_path: tree.get_sibling_path(leaf_index as u64) } + } + ); + let tree_root = tree.get_root(); + (hints, tree_root) + } + + #[test] + fn test_reset_non_existent_read_requests_in_range() { + let read_requests = [ + ReadRequestContext { value: 11, counter: 50, contract_address: AztecAddress::zero() }, + ReadRequestContext { value: 22, counter: 51, contract_address: AztecAddress::zero() }, + ReadRequestContext { value: 6, counter: 52, contract_address: AztecAddress::zero() } + ]; + let (non_membership_hints, root) = get_non_membership_hints([3, 1, 0]); + let next_pending_value_indices = [1, 2, 1]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test] + fn test_reset_non_existent_read_requests_less_than_min() { + let read_requests = [ + ReadRequestContext { value: 3, counter: 50, contract_address: AztecAddress::zero() }, + ReadRequestContext { value: 2, counter: 51, contract_address: AztecAddress::zero() } + ]; + let (non_membership_hints, root) = get_non_membership_hints([0, 0]); + let next_pending_value_indices = [0, 0]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test] + fn test_reset_non_existent_read_requests_greater_than_max() { + let read_requests = [ + ReadRequestContext { value: 35, counter: 50, contract_address: AztecAddress::zero() }, + ReadRequestContext { value: 31, counter: 51, contract_address: AztecAddress::zero() } + ]; + let (non_membership_hints, root) = get_non_membership_hints([2, 2]); + let next_pending_value_indices = [3, 3]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test] + fn test_reset_non_existent_read_requests_read_before_pending_emitted() { + let read_requests = [ + ReadRequestContext { value: 25, counter: 10, contract_address: AztecAddress::zero() }, + ReadRequestContext { value: 5, counter: 11, contract_address: AztecAddress::zero() } + ]; + let (non_membership_hints, root) = get_non_membership_hints([1, 0]); + let next_pending_value_indices = [2, 0]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test(should_fail_with="Cannot check non membership against empty leaf")] + fn test_reset_non_existent_read_requests_empty_leaf_failed() { + let read_requests = [ReadRequestContext { value: 10, counter: 50, contract_address: AztecAddress::zero() }]; + let (non_membership_hints, root) = get_non_membership_hints([0]); + let mut hint = non_membership_hints[0]; + hint.low_leaf_preimage = TestLeafPreimage { value: 0, next_value: 0 }; + let next_pending_value_indices = [1]; + reset_non_existent_read_requests( + read_requests, + [hint], + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test(should_fail_with="Low leaf does not exist")] + fn test_reset_non_existent_read_requests_invalid_preimage_failed() { + let read_requests = [ReadRequestContext { value: 10, counter: 50, contract_address: AztecAddress::zero() }]; + let (non_membership_hints, root) = get_non_membership_hints([3]); + let mut hint = non_membership_hints[0]; + hint.low_leaf_preimage = TestLeafPreimage { value: 9, next_value: 20 }; + let next_pending_value_indices = [1]; + reset_non_existent_read_requests( + read_requests, + [hint], + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test(should_fail_with="Key is not greater than the low leaf")] + fn test_reset_non_existent_read_requests_read_settled_failed() { + let read_requests = [ReadRequestContext { value: 10, counter: 50, contract_address: AztecAddress::zero() }]; + let (non_membership_hints, root) = get_non_membership_hints([3]); + let next_pending_value_indices = [1]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test(should_fail_with="Key is not less than the next leaf")] + fn test_reset_non_existent_read_requests_invalid_non_membership_hint_failed() { + let read_requests = [ReadRequestContext { value: 10, counter: 50, contract_address: AztecAddress::zero() }]; + let (non_membership_hints, root) = get_non_membership_hints([0]); + let next_pending_value_indices = [1]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test(should_fail_with="Value exists in pending set")] + fn test_reset_non_existent_read_requests_read_pending_value_failed() { + let read_requests = [ReadRequestContext { value: 25, counter: 50, contract_address: AztecAddress::zero() }]; + let (non_membership_hints, root) = get_non_membership_hints([1]); + let next_pending_value_indices = [2]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test(should_fail_with="Invalid next pending value index")] + fn test_reset_non_existent_read_requests_wrong_next_pending_index_failed() { + let read_requests = [ReadRequestContext { value: 21, counter: 50, contract_address: AztecAddress::zero() }]; + let (non_membership_hints, root) = get_non_membership_hints([1]); + let next_pending_value_indices = [1]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test(should_fail_with="Invalid next pending value index")] + fn test_reset_non_existent_read_requests_wrong_max_next_pending_index_failed() { + let read_requests = [ReadRequestContext { value: 21, counter: 50, contract_address: AztecAddress::zero() }]; + let (non_membership_hints, root) = get_non_membership_hints([1]); + let next_pending_value_indices = [3]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } + + #[test(should_fail_with="Next pending value index out of bounds")] + fn test_reset_non_existent_read_requests_overflown_index_failed() { + let read_requests = [ReadRequestContext { value: 21, counter: 50, contract_address: AztecAddress::zero() }]; + let (non_membership_hints, root) = get_non_membership_hints([1]); + let next_pending_value_indices = [4]; + reset_non_existent_read_requests( + read_requests, + non_membership_hints, + root, + sorted_pending_values, + next_pending_value_indices + ); + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_non_existent_read_request_reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_non_existent_read_request_reset.nr new file mode 100644 index 00000000000..cc76145b18e --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_non_existent_read_request_reset.nr @@ -0,0 +1,28 @@ +use crate::non_existent_read_request_reset::{NonMembershipHint}; +use dep::types::{ + abis::{nullifier_leaf_preimage::NullifierLeafPreimage, side_effect::SideEffectLinkedToNoteHash}, + merkle_tree::{MembershipWitness}, + constants::{MAX_NEW_NULLIFIERS_PER_TX, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT} +}; + +struct NullifierNonMembershipHint { + low_leaf_preimage: NullifierLeafPreimage, + membership_witness: MembershipWitness, +} + +impl NonMembershipHint for NullifierNonMembershipHint { + fn low_leaf_preimage(self) -> NullifierLeafPreimage { + self.low_leaf_preimage + } + + fn membership_witness(self) -> MembershipWitness { + self.membership_witness + } +} + +struct NullifierNonExistentReadRequestHints { + non_membership_hints: [NullifierNonMembershipHint; MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX], + sorted_pending_values: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX], + sorted_pending_value_index_hints: [u64; MAX_NEW_NULLIFIERS_PER_TX], + next_pending_value_indices: [u64; MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX], +} diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_read_request_reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_read_request_reset.nr index 6524089d74a..6a122e57a2d 100644 --- a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_read_request_reset.nr +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_read_request_reset.nr @@ -2,8 +2,9 @@ use crate::read_request_reset::{PendingReadHint, ReadRequestStatus, ReadValueHint, SettledReadHint}; use dep::std::unsafe; use dep::types::{ - abis::{membership_witness::MembershipWitness, nullifier_leaf_preimage::NullifierLeafPreimage}, - constants::{MAX_NULLIFIER_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT} + abis::{nullifier_leaf_preimage::NullifierLeafPreimage}, + constants::{MAX_NULLIFIER_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT}, + merkle_tree::MembershipWitness }; struct NullifierSettledReadHint { @@ -36,49 +37,22 @@ impl SettledReadHint for Nullifier } } -struct NullifierReadRequestResetHints { +struct NullifierReadRequestHints { read_request_statuses: [ReadRequestStatus; MAX_NULLIFIER_READ_REQUESTS_PER_TX], pending_read_hints: [PendingReadHint; MAX_NULLIFIER_READ_REQUESTS_PER_TX], settled_read_hints: [NullifierSettledReadHint; MAX_NULLIFIER_READ_REQUESTS_PER_TX], } -struct NullifierReadRequestResetHintsBuilder { - read_request_statuses: [ReadRequestStatus; MAX_NULLIFIER_READ_REQUESTS_PER_TX], - pending_read_hints: BoundedVec, - settled_read_hints: BoundedVec, -} - -impl NullifierReadRequestResetHintsBuilder { - pub fn new(read_request_len: u64) -> Self { - NullifierReadRequestResetHintsBuilder { - read_request_statuses: [ReadRequestStatus::empty(); MAX_NULLIFIER_READ_REQUESTS_PER_TX], - pending_read_hints: BoundedVec { storage: [PendingReadHint::nada(read_request_len); MAX_NULLIFIER_READ_REQUESTS_PER_TX], len: 0 }, - settled_read_hints: BoundedVec { - storage: [NullifierSettledReadHint::nada(read_request_len); MAX_NULLIFIER_READ_REQUESTS_PER_TX], - len: 0 - } - } - } - - pub fn to_hints(self) -> NullifierReadRequestResetHints { - NullifierReadRequestResetHints { - read_request_statuses: self.read_request_statuses, - pending_read_hints: self.pending_read_hints.storage, - settled_read_hints: self.settled_read_hints.storage - } - } -} - mod tests { use crate::nullifier_read_request_reset::NullifierSettledReadHint; use crate::read_request_reset::{PendingReadHint, ReadRequestState, ReadRequestStatus, reset_read_requests}; use dep::types::{ address::AztecAddress, abis::{ - membership_witness::MembershipWitness, nullifier_leaf_preimage::NullifierLeafPreimage, - read_request::ReadRequestContext, side_effect::SideEffect + nullifier_leaf_preimage::NullifierLeafPreimage, read_request::ReadRequestContext, + side_effect::SideEffect }, - constants::NULLIFIER_TREE_HEIGHT, hash::silo_nullifier, + constants::NULLIFIER_TREE_HEIGHT, hash::silo_nullifier, merkle_tree::MembershipWitness, tests::merkle_tree_utils::NonEmptyMerkleTree }; diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/read_request_reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/read_request_reset.nr index 2c50bcd46d7..1432df9ef18 100644 --- a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/read_request_reset.nr +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/read_request_reset.nr @@ -1,8 +1,7 @@ // This will be moved to a separate Read Request Reset Circuit. use dep::types::{ - abis::{membership_witness::MembershipWitness, read_request::ReadRequestContext, side_effect::OrderedValue}, - hash::{silo_nullifier, root_from_sibling_path}, merkle_tree::leaf_preimage::LeafPreimage, - traits::{Empty, is_empty} + abis::{read_request::ReadRequestContext, side_effect::OrderedValue}, hash::{silo_nullifier}, + merkle_tree::{assert_check_membership, LeafPreimage, MembershipWitness}, traits::{Empty, is_empty} }; struct ReadRequestStateEnum { @@ -84,7 +83,7 @@ fn validate_pending_read_requests( read_requests: [ReadRequestContext; READ_REQUEST_LEN], hints: [H; NUM_SETTLED_READS], - historical_tree_root: Field + tree_root: Field ) where H: SettledReadHint + ReadValueHint, LEAF_PREIMAGE: LeafPreimage { @@ -99,8 +98,7 @@ fn validate_settled_read_requests BoundedVec where P: OrderedValue, H: SettledReadHint + ReadValueHint, LEAF_PREIMAGE: LeafPreimage { validate_pending_read_requests(read_requests, pending_values, pending_read_hints); - validate_settled_read_requests(read_requests, settled_read_hints, historical_tree_root); + validate_settled_read_requests(read_requests, settled_read_hints, tree_root); propagate_unverified_read_requests( read_requests, @@ -163,9 +161,8 @@ mod tests { }; use dep::std::{hash::pedersen_hash, unsafe}; use dep::types::{ - address::AztecAddress, - abis::{membership_witness::MembershipWitness, read_request::ReadRequestContext, side_effect::SideEffect}, - merkle_tree::leaf_preimage::LeafPreimage, hash::silo_nullifier, + address::AztecAddress, abis::{read_request::ReadRequestContext, side_effect::SideEffect}, + merkle_tree::{LeafPreimage, MembershipWitness}, hash::silo_nullifier, tests::merkle_tree_utils::NonEmptyMerkleTree }; @@ -309,7 +306,7 @@ mod tests { validate_settled_read_requests(read_requests, hints, tree_root); } - #[test(should_fail_with="Tree root mismatch for read request")] + #[test(should_fail_with="membership check failed")] fn test_validate_settled_read_requests_wrong_witness_fails() { let (settled_hints, tree_root) = get_settled_read_hints(); let mut hint = settled_hints[0]; diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests.nr new file mode 100644 index 00000000000..9ecd400c180 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests.nr @@ -0,0 +1,2 @@ +mod nullifier_non_existent_read_request_hints_builder; +mod nullifier_read_request_hints_builder; diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_non_existent_read_request_hints_builder.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_non_existent_read_request_hints_builder.nr new file mode 100644 index 00000000000..d4de21463a7 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_non_existent_read_request_hints_builder.nr @@ -0,0 +1,80 @@ +use crate::nullifier_non_existent_read_request_reset::{NullifierNonMembershipHint, NullifierNonExistentReadRequestHints}; +use dep::types::{ + abis::{nullifier_leaf_preimage::NullifierLeafPreimage, side_effect::SideEffectLinkedToNoteHash}, + constants::{ + MAX_NEW_NULLIFIERS_PER_TX, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT, + NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_SUBTREE_HEIGHT +}, + merkle_tree::MembershipWitness, + tests::{merkle_tree_utils::NonEmptyMerkleTree, sort::sort_get_sorted_hints}, + utils::{arrays::find_index, field::full_field_greater_than} +}; +use dep::std::unsafe; + +struct NullifierNonExistentReadRequestHintsBuilder { + nullifier_tree: NonEmptyMerkleTree, + non_membership_hints: BoundedVec, + read_values: BoundedVec, + pending_nullifiers: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX], +} + +impl NullifierNonExistentReadRequestHintsBuilder { + pub fn new() -> Self { + NullifierNonExistentReadRequestHintsBuilder { + nullifier_tree: unsafe::zeroed(), + non_membership_hints: BoundedVec::new(), + read_values: BoundedVec::new(), + pending_nullifiers: [SideEffectLinkedToNoteHash::empty(); MAX_NEW_NULLIFIERS_PER_TX] + } + } + + pub fn set_nullifier_tree( + &mut self, + tree: NonEmptyMerkleTree + ) { + self.nullifier_tree = tree; + } + + pub fn set_nullifiers( + &mut self, + nullifiers: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX] + ) { + self.pending_nullifiers = nullifiers; + } + + pub fn add_value_read(&mut self, siloed_value: Field) { + self.read_values.push(siloed_value); + + // There are only two pre-existing nullifiers in the tree: [0, 100], generated in public_kernel_tail::tests. + // Assuming the siloed_value is always greater than 100. + let hint = NullifierNonMembershipHint { + low_leaf_preimage: NullifierLeafPreimage { nullifier: 100, next_nullifier: 0, next_index: 0 }, + membership_witness: MembershipWitness { leaf_index: 1, sibling_path: self.nullifier_tree.get_sibling_path(1) } + }; + self.non_membership_hints.push(hint); + } + + pub fn to_hints(self) -> NullifierNonExistentReadRequestHints { + let sorted_result = sort_get_sorted_hints( + self.pending_nullifiers, + |a: SideEffectLinkedToNoteHash, b: SideEffectLinkedToNoteHash| a.value.lt(b.value) + ); + let sorted_pending_values = sorted_result.sorted_array; + let sorted_pending_value_index_hints = sorted_result.sorted_index_hints; + + let mut next_pending_value_indices = [0; MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX]; + for i in 0..MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX { + if i < self.read_values.len() { + let value = self.read_values.get_unchecked(i); + next_pending_value_indices[i] = find_index(sorted_pending_values, |v: SideEffectLinkedToNoteHash| !v.value.lt(value)); + } + } + + NullifierNonExistentReadRequestHints { + non_membership_hints: self.non_membership_hints.storage, + sorted_pending_values, + sorted_pending_value_index_hints, + next_pending_value_indices + } + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_read_request_hints_builder.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_read_request_hints_builder.nr new file mode 100644 index 00000000000..355106e7978 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_read_request_hints_builder.nr @@ -0,0 +1,32 @@ +use crate::{ + nullifier_read_request_reset::{NullifierSettledReadHint, NullifierReadRequestHints}, + read_request_reset::{PendingReadHint, ReadRequestStatus} +}; +use dep::types::constants::MAX_NULLIFIER_READ_REQUESTS_PER_TX; + +struct NullifierReadRequestHintsBuilder { + read_request_statuses: [ReadRequestStatus; MAX_NULLIFIER_READ_REQUESTS_PER_TX], + pending_read_hints: BoundedVec, + settled_read_hints: BoundedVec, +} + +impl NullifierReadRequestHintsBuilder { + pub fn new(read_request_len: u64) -> Self { + NullifierReadRequestHintsBuilder { + read_request_statuses: [ReadRequestStatus::empty(); MAX_NULLIFIER_READ_REQUESTS_PER_TX], + pending_read_hints: BoundedVec { storage: [PendingReadHint::nada(read_request_len); MAX_NULLIFIER_READ_REQUESTS_PER_TX], len: 0 }, + settled_read_hints: BoundedVec { + storage: [NullifierSettledReadHint::nada(read_request_len); MAX_NULLIFIER_READ_REQUESTS_PER_TX], + len: 0 + } + } + } + + pub fn to_hints(self) -> NullifierReadRequestHints { + NullifierReadRequestHints { + read_request_statuses: self.read_request_statuses, + pending_read_hints: self.pending_read_hints.storage, + settled_read_hints: self.settled_read_hints.storage + } + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr index b554d16bc9e..b06afdce361 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr @@ -9,10 +9,7 @@ use crate::{ use dep::types::{ abis::{ append_only_tree_snapshot::AppendOnlyTreeSnapshot, - membership_witness::{ - ArchiveRootMembershipWitness, MembershipWitness, NullifierMembershipWitness, - PublicDataMembershipWitness -}, + membership_witness::{ArchiveRootMembershipWitness, NullifierMembershipWitness, PublicDataMembershipWitness}, nullifier_leaf_preimage::NullifierLeafPreimage, public_data_update_request::PublicDataUpdateRequest, public_data_read::PublicDataRead, kernel_data::RollupKernelData, side_effect::{SideEffect, SideEffectLinkedToNoteHash}, accumulated_data::CombinedAccumulatedData @@ -29,8 +26,10 @@ use dep::types::{ MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, MAX_REVERTIBLE_NULLIFIERS_PER_TX, MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX }, - hash::assert_check_membership, - merkle_tree::{append_only_tree, calculate_empty_tree_root, calculate_subtree, indexed_tree}, + merkle_tree::{ + append_only_tree, assert_check_membership, calculate_empty_tree_root, calculate_subtree_root, + indexed_tree, MembershipWitness +}, mocked::{AggregationObject, Proof}, partial_state_reference::PartialStateReference, public_data_tree_leaf::PublicDataTreeLeaf, public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage, @@ -118,7 +117,7 @@ impl BaseRollupInputs { // TODO(Kev): This should say calculate_commitments_subtree_root // Cpp code says calculate_commitments_subtree, so I'm leaving it as is for now fn calculate_commitments_subtree(self) -> Field { - calculate_subtree(self.kernel_data.public_inputs.end.new_note_hashes.map(|c: SideEffect| c.value)) + calculate_subtree_root(self.kernel_data.public_inputs.end.new_note_hashes.map(|c: SideEffect| c.value)) } fn check_nullifier_tree_non_membership_and_insert_to_tree(self) -> AppendOnlyTreeSnapshot { @@ -137,9 +136,6 @@ impl BaseRollupInputs { } } ), - |a: Field, b: Field| {a == b}, // Nullifier equals - |nullifier: Field| {nullifier == 0}, // Nullifier is zero - |leaf: NullifierLeafPreimage| {leaf.hash()}, // Hash leaf |low_leaf: NullifierLeafPreimage, nullifier: Field| { // Is valid low leaf let is_less_than_nullifier = full_field_less_than(low_leaf.nullifier, nullifier); let is_next_greater_than = full_field_less_than(nullifier, low_leaf.next_nullifier); @@ -169,7 +165,7 @@ impl BaseRollupInputs { } fn create_nullifier_subtree(leaves: [NullifierLeafPreimage; N]) -> Field { - calculate_subtree(leaves.map(|leaf:NullifierLeafPreimage| leaf.hash())) + calculate_subtree_root(leaves.map(|leaf:NullifierLeafPreimage| leaf.hash())) } fn validate_and_process_public_state(self) -> AppendOnlyTreeSnapshot { @@ -265,9 +261,6 @@ fn insert_public_data_update_requests( } } ), - |a: PublicDataTreeLeaf, b: PublicDataTreeLeaf| a.eq(b), // PublicDataTreeLeaf equals - |write: PublicDataTreeLeaf| write.is_empty(), // PublicDataTreeLeaf is_empty - |preimage: PublicDataTreeLeafPreimage| preimage.hash(), // Hash preimage |low_preimage: PublicDataTreeLeafPreimage, write: PublicDataTreeLeaf| { // Is valid low preimage let is_update = low_preimage.slot == write.slot; let is_low_empty = low_preimage.is_empty(); @@ -406,12 +399,11 @@ mod tests { MAX_NEW_L2_TO_L1_MSGS_PER_TX }, contract_class_id::ContractClassId, partial_state_reference::PartialStateReference, - hash::assert_check_membership, merkle_tree::{calculate_empty_tree_root, calculate_subtree}, public_data_tree_leaf::PublicDataTreeLeaf, public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage, tests::{ kernel_data_builder::PreviousKernelDataBuilder, - merkle_tree_utils::{NonEmptyMerkleTree, compute_zero_hashes} + merkle_tree_utils::{NonEmptyMerkleTree, compute_zero_hashes}, sort::sort_high_to_low }, utils::{field::full_field_less_than, uint256::U256} }; @@ -421,28 +413,10 @@ mod tests { value: Field, } - struct SortedTuple { - value: T, - original_index: u64, - } - global MAX_NEW_NULLIFIERS_PER_TEST = 4; global MAX_PUBLIC_DATA_WRITES_PER_TEST = 2; global MAX_PUBLIC_DATA_READS_PER_TEST = 2; - fn sort_high_to_low(values: [T; N], is_less_than: fn(T, T) -> bool) -> [SortedTuple; N] where T: Eq { - let mut sorted_tuples = [SortedTuple { value: values[0], original_index: 0 }; N]; - - for i in 0..N { - sorted_tuples[i] = SortedTuple { - value: values[i], - original_index: i, - }; - } - - sorted_tuples.sort_via(|a: SortedTuple, b: SortedTuple| (b.value == a.value) | is_less_than(b.value, a.value)) - } - fn update_public_data_tree( public_data_tree: &mut NonEmptyMerkleTree, kernel_data: &mut RollupKernelData, diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr index 623014f668c..508e4d9ed44 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr @@ -9,7 +9,7 @@ use dep::types::{ L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH, ARCHIVE_HEIGHT }, header::Header, content_commitment::ContentCommitment, - merkle_tree::{append_only_tree, calculate_subtree, calculate_empty_tree_root}, + merkle_tree::{append_only_tree, calculate_subtree_root, calculate_empty_tree_root}, state_reference::StateReference }; @@ -41,7 +41,7 @@ impl RootRollupInputs { // Check correct l1 to l2 tree given // Compute subtree inserting l1 to l2 messages - let l1_to_l2_subtree_root = calculate_subtree(self.new_l1_to_l2_messages); + let l1_to_l2_subtree_root = calculate_subtree_root(self.new_l1_to_l2_messages); // Insert subtree into the l1 to l2 data tree let empty_l1_to_l2_subtree_root = calculate_empty_tree_root(L1_TO_L2_MSG_SUBTREE_HEIGHT); diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/accumulated_non_revertible_data_builder.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/accumulated_non_revertible_data_builder.nr index f138d7e1f26..3499620d06c 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/accumulated_non_revertible_data_builder.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/accumulated_non_revertible_data_builder.nr @@ -12,7 +12,8 @@ use crate::{ use crate::constants::{ MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX, - MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX + MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX }; struct AccumulatedNonRevertibleDataBuilder { @@ -20,6 +21,7 @@ struct AccumulatedNonRevertibleDataBuilder { new_nullifiers: BoundedVec, public_call_stack: BoundedVec, nullifier_read_requests: BoundedVec, + nullifier_non_existent_read_requests: BoundedVec, public_data_update_requests: BoundedVec, public_data_reads: BoundedVec, } @@ -38,6 +40,7 @@ impl AccumulatedNonRevertibleDataBuilder { new_nullifiers: self.new_nullifiers.storage, public_call_stack: self.public_call_stack.storage, nullifier_read_requests: self.nullifier_read_requests.storage, + nullifier_non_existent_read_requests: self.nullifier_non_existent_read_requests.storage, public_data_update_requests: self.public_data_update_requests.storage, public_data_reads: self.public_data_reads.storage } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_non_revertible_data.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_non_revertible_data.nr index 697c307e656..c8175fbd7c0 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_non_revertible_data.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_non_revertible_data.nr @@ -8,7 +8,8 @@ use crate::{ use crate::constants::{ MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX, - MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX + MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX }; use dep::std::unsafe; @@ -21,6 +22,7 @@ struct PublicAccumulatedNonRevertibleData { new_nullifiers: [SideEffectLinkedToNoteHash; MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX], public_call_stack: [CallRequest; MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX], nullifier_read_requests: [ReadRequestContext; MAX_NULLIFIER_READ_REQUESTS_PER_TX], + nullifier_non_existent_read_requests: [ReadRequestContext; MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX], public_data_update_requests: [PublicDataUpdateRequest; MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], public_data_reads: [PublicDataRead; MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX], } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_revertible_data.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_revertible_data.nr index 954cb1b69a0..2b310a2ae87 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_revertible_data.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_revertible_data.nr @@ -13,6 +13,7 @@ use crate::constants::{ MAX_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_REVERTIBLE_PUBLIC_DATA_READS_PER_TX }; +// TODO - Requests for checking data should not be revertible. struct PublicAccumulatedRevertibleData { note_hash_read_requests: [SideEffect; MAX_NOTE_HASH_READ_REQUESTS_PER_TX], nullifier_read_requests: [ReadRequestContext; MAX_NULLIFIER_READ_REQUESTS_PER_TX], diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/membership_witness.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/membership_witness.nr index 488272527c3..d23cfb0f19a 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/membership_witness.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/membership_witness.nr @@ -3,11 +3,6 @@ use crate::constants::{ ARCHIVE_HEIGHT, PUBLIC_DATA_TREE_HEIGHT }; -struct MembershipWitness { - leaf_index: Field, - sibling_path: [Field; N] -} - // TODO(Kev): Instead of doing `MembershipWitness` we are forced // to do this new struct because the typescript bindings generator // does not have logic to monomorphize these properly. See the file named diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier_leaf_preimage.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier_leaf_preimage.nr index 6eb484e5d66..895196567a0 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier_leaf_preimage.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier_leaf_preimage.nr @@ -1,6 +1,6 @@ global NULLIFIER_LEAF_PREIMAGE_LENGTH: u64 = 3; -use crate::{merkle_tree::leaf_preimage::LeafPreimage, traits::{Empty, Hash}}; +use crate::{merkle_tree::leaf_preimage::{LeafPreimage, IndexedTreeLeafPreimage}, traits::{Empty, Hash}}; struct NullifierLeafPreimage { nullifier : Field, @@ -38,6 +38,20 @@ impl LeafPreimage for NullifierLeafPreimage { } } +impl IndexedTreeLeafPreimage for NullifierLeafPreimage { + fn get_key(self) -> Field { + self.nullifier + } + + fn get_next_key(self) -> Field { + self.next_nullifier + } + + fn as_leaf(self) -> Field { + self.hash() + } +} + impl NullifierLeafPreimage { pub fn is_empty(self) -> bool { (self.nullifier == 0) & (self.next_nullifier == 0) & (self.next_index == 0) diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr index 0b9dabb2db7..3a9181be985 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr @@ -69,7 +69,7 @@ mod tests { let call_stack_item = PublicCallStackItem { contract_address, public_inputs, is_execution_request: true, function_data }; // Value from public_call_stack_item.test.ts "Computes a callstack item request hash" test - let test_data_call_stack_item_request_hash = 0x09cb16dc10b48bb544bd5f4293cfd2dee539bd281aa468c0c69a9352df17a307; + let test_data_call_stack_item_request_hash = 0x1a1194c14f229b72d31669b06e3984d6f0f5edd4d5204ceda0ff30f25e910e83; assert_eq(call_stack_item.hash(), test_data_call_stack_item_request_hash); } @@ -87,7 +87,7 @@ mod tests { let call_stack_item = PublicCallStackItem { contract_address, public_inputs, is_execution_request: false, function_data }; // Value from public_call_stack_item.test.ts "Computes a callstack item hash" test - let test_data_call_stack_item_hash = 0x086b4890110c751f01df5eb163b250f10c90a4f38e73e07e3b5a58685456eaa9; + let test_data_call_stack_item_hash = 0x187836686ed01f12180ef08c419e4ac8514d9c60e6a38b4a56d893fa90c83a5d; assert_eq(call_stack_item.hash(), test_data_call_stack_item_hash); } } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_circuit_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_circuit_public_inputs.nr index b8f44a158ad..e364c3b49d8 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_circuit_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_circuit_public_inputs.nr @@ -6,10 +6,10 @@ use crate::{ address::AztecAddress, constants::{ MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL, MAX_NEW_NOTE_HASHES_PER_CALL, - MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, - MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, - RETURN_VALUES_LENGTH, GENERATOR_INDEX__PUBLIC_CIRCUIT_PUBLIC_INPUTS, - PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH + MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, + MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL, + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH, + GENERATOR_INDEX__PUBLIC_CIRCUIT_PUBLIC_INPUTS, PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH }, contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest}, hash::pedersen_hash, header::Header, messaging::l2_to_l1_message::L2ToL1Message, @@ -23,6 +23,7 @@ struct PublicCircuitPublicInputs{ return_values: [Field; RETURN_VALUES_LENGTH], nullifier_read_requests: [ReadRequest; MAX_NULLIFIER_READ_REQUESTS_PER_CALL], + nullifier_non_existent_read_requests: [ReadRequest; MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL], contract_storage_update_requests: [StorageUpdateRequest; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL], contract_storage_reads: [StorageRead; MAX_PUBLIC_DATA_READS_PER_CALL], @@ -62,6 +63,9 @@ impl Serialize for PublicCircuitPublicInput for i in 0..MAX_NULLIFIER_READ_REQUESTS_PER_CALL { fields.extend_from_array(self.nullifier_read_requests[i].serialize()); } + for i in 0..MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL { + fields.extend_from_array(self.nullifier_non_existent_read_requests[i].serialize()); + } for i in 0..MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL { fields.extend_from_array(self.contract_storage_update_requests[i].serialize()); } @@ -97,6 +101,7 @@ impl Deserialize for PublicCircuitPublicInp args_hash: reader.read(), return_values: reader.read_array([0; RETURN_VALUES_LENGTH]), nullifier_read_requests: reader.read_struct_array(ReadRequest::deserialize, [ReadRequest::empty(); MAX_NULLIFIER_READ_REQUESTS_PER_CALL]), + nullifier_non_existent_read_requests: reader.read_struct_array(ReadRequest::deserialize, [ReadRequest::empty(); MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL]), contract_storage_update_requests: reader.read_struct_array(StorageUpdateRequest::deserialize, [StorageUpdateRequest::empty(); MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL]), contract_storage_reads: reader.read_struct_array(StorageRead::deserialize, [StorageRead::empty(); MAX_PUBLIC_DATA_READS_PER_CALL]), public_call_stack_hashes: reader.read_array([0; MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL]), @@ -135,6 +140,6 @@ fn empty_hash() { let hash = inputs.hash(); // Value from public_circuit_public_inputs.test.ts "computes empty item hash" test - let test_data_empty_hash = 0x153eea640dd0a53eaa029301381962507fb89e348d42d6f3335107644c6541b9; + let test_data_empty_hash = 0x1c9942cee14a4f84b3e606f553b2ab3151c395822ee7ffd51759d5822375d6c9; assert_eq(hash, test_data_empty_hash); } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr index 12f6c0000f6..1c765a0bc7e 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -32,6 +32,7 @@ global MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL: u64 = 16; global MAX_PUBLIC_DATA_READS_PER_CALL: u64 = 16; global MAX_NOTE_HASH_READ_REQUESTS_PER_CALL: u64 = 32; global MAX_NULLIFIER_READ_REQUESTS_PER_CALL: u64 = 2; // Change it to a larger value when there's a seperate reset circuit. +global MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL: u64 = 2; global MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL: u64 = 1; // "PER TRANSACTION" CONSTANTS @@ -60,6 +61,7 @@ global MAX_REVERTIBLE_PUBLIC_DATA_READS_PER_TX: u64 = 16; global MAX_NEW_L2_TO_L1_MSGS_PER_TX: u64 = 2; global MAX_NOTE_HASH_READ_REQUESTS_PER_TX: u64 = 128; global MAX_NULLIFIER_READ_REQUESTS_PER_TX: u64 = 8; // Change it to a larger value when there's a seperate reset circuit. +global MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX: u64 = 8; global MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX: u64 = 4; global NUM_ENCRYPTED_LOGS_HASHES_PER_TX: u64 = 1; global NUM_UNENCRYPTED_LOGS_HASHES_PER_TX: u64 = 1; @@ -167,7 +169,7 @@ global PRIVATE_CALL_STACK_ITEM_LENGTH: u64 = 214; // constant as well PRIVATE_CALL_STACK_ITEM_LENGTH global PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH: u64 = 209; // Change this ONLY if you have changed the PublicCircuitPublicInputs structure. -global PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH: u64 = 196; +global PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH: u64 = 200; global STATE_REFERENCE_LENGTH: u64 = 8; // 2 for snap + 8 for partial global TX_CONTEXT_DATA_LENGTH: u64 = 4; global TX_REQUEST_LENGTH: u64 = 10; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr b/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr index 0afb6ea78a9..7de5fa7d536 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr @@ -14,6 +14,7 @@ use crate::constants::{ GENERATOR_INDEX__NOTE_HASH_NONCE, GENERATOR_INDEX__UNIQUE_NOTE_HASH, GENERATOR_INDEX__FUNCTION_ARGS }; use crate::messaging::l2_to_l1_message::L2ToL1Message; +use crate::merkle_tree::root::root_from_sibling_path; use dep::std::hash::{pedersen_hash_with_separator, sha256}; @@ -61,38 +62,6 @@ pub fn hash_args(args: [Field; N]) -> Field { } } -// Checks that `value` is a member of a merkle tree with root `root` at position `index` -// The witness being the `sibling_path` -pub fn assert_check_membership(value: Field, index: Field, sibling_path: [Field; N], root: Field) { - let calculated_root = root_from_sibling_path(value, index, sibling_path); - assert(calculated_root == root, "membership check failed"); -} - -// Calculate the Merkle tree root from the sibling path and leaf. -// -// The leaf is hashed with its sibling, and then the result is hashed -// with the next sibling etc in the path. The last hash is the root. -// -// TODO(David/Someone): The cpp code is using a uint256, whereas its -// TODO a bit simpler in Noir to just have a bit array. -// TODO: I'd generally like to avoid u256 for algorithms like -// this because it means we never even need to consider cases where -// the index is greater than p. -pub fn root_from_sibling_path(leaf: Field, leaf_index: Field, sibling_path: [Field; N]) -> Field { - let mut node = leaf; - let indices = leaf_index.to_le_bits(N); - - for i in 0..N { - let (hash_left, hash_right) = if indices[i] == 1 { - (sibling_path[i], node) - } else { - (node, sibling_path[i]) - }; - node = merkle_hash(hash_left, hash_right); - } - node -} - // Calculate the function tree root from the sibling path and leaf preimage. // // TODO: The cpp code passes in components of the FunctionLeafPreimage and then @@ -148,7 +117,7 @@ pub fn silo_nullifier(address: AztecAddress, nullifier: Field) -> Field { ) } -fn merkle_hash(left: Field, right: Field) -> Field { +pub fn merkle_hash(left: Field, right: Field) -> Field { pedersen_hash([left, right], 0) } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree.nr index ecd76abb5ff..67b23d3f449 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree.nr @@ -1,110 +1,14 @@ mod append_only_tree; mod indexed_tree; mod leaf_preimage; - -struct MerkleTree { - leaves: [Field; N], - nodes: [Field; N], -} - -impl MerkleTree { - pub fn new(leaves: [Field; N]) -> Self { - let mut nodes = [0; N]; - - // We need one less node than leaves, but we cannot have computed array lengths - let total_nodes = N - 1; - let half_size = N / 2; - - // hash base layer - for i in 0..half_size { - nodes[i] = dep::std::hash::pedersen_hash([leaves[2*i], leaves[2*i+1]]); - } - - // hash the other layers - for i in 0..(total_nodes - half_size) { - nodes[half_size+i] = dep::std::hash::pedersen_hash([nodes[2*i], nodes[2*i+1]]); - } - - MerkleTree { leaves, nodes } - } - - fn get_root(self) -> Field { - self.nodes[N - 2] - } -} - -pub fn calculate_subtree(leaves: [Field; N]) -> Field { - MerkleTree::new(leaves).get_root() -} - -// These values are precomputed and we run tests to ensure that they -// are correct. The values themselves were computed from the cpp code. -// -// Would be good if we could use width since the compute_subtree -// algorithm uses depth. -pub fn calculate_empty_tree_root(depth: u64) -> Field { - if depth == 0 { - 0 - } else if depth == 1 { - 0x27b1d0839a5b23baf12a8d195b18ac288fcf401afb2f70b8a4b529ede5fa9fed - } else if depth == 2 { - 0x21dbfd1d029bf447152fcf89e355c334610d1632436ba170f738107266a71550 - } else if depth == 3 { - 0x0bcd1f91cf7bdd471d0a30c58c4706f3fdab3807a954b8f5b5e3bfec87d001bb - } else if depth == 4 { - 0x06e62084ee7b602fe9abc15632dda3269f56fb0c6e12519a2eb2ec897091919d - } else if depth == 5 { - 0x03c9e2e67178ac638746f068907e6677b4cc7a9592ef234ab6ab518f17efffa0 - } else if depth == 6 { - 0x15d28cad4c0736decea8997cb324cf0a0e0602f4d74472cd977bce2c8dd9923f - } else if depth == 7 { - 0x268ed1e1c94c3a45a14db4108bc306613a1c23fab68e0466a002dfb0a3f8d2ab - } else if depth == 8 { - 0x0cd8d5695bc2dde99dd531671f76f1482f14ddba8eeca7cb9686d4a62359c257 - } else if depth == 9 { - 0x047fbb7eb974155702149e58ea6ad91f4c6e953e693db35e953e250d8ceac9a9 - } else if depth == 10 { - 0x00c5ae2526e665e2c7c698c11a06098b7159f720606d50e7660deb55758b0b02 - } else { - assert(false, "depth should be between 0 and 10"); - 0 - } -} - -#[test] -fn test_merkle_root_interop_test() { - // This is a test to ensure that we match the cpp implementation. - // You can grep for `TEST_F(root_rollup_tests, noir_interop_test)` - // to find the test that matches this. - let root = calculate_subtree([1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]); - assert(0x17e8bb70a11d0c946345950879484d2f4f9fef397ff6adbfdec3baab2d41faab == root); - - let empty_root = calculate_subtree([0; 16]); - assert(0x06e62084ee7b602fe9abc15632dda3269f56fb0c6e12519a2eb2ec897091919d == empty_root); -} - -#[test] -fn test_empty_subroot() { - assert(calculate_empty_tree_root(0) == 0); - - let expected_empty_root_2 = calculate_subtree([0; 2]); - assert(calculate_empty_tree_root(1) == expected_empty_root_2); - - let expected_empty_root_4 = calculate_subtree([0; 4]); - assert(calculate_empty_tree_root(2) == expected_empty_root_4); - - let expected_empty_root_8 = calculate_subtree([0; 8]); - assert(calculate_empty_tree_root(3) == expected_empty_root_8); - - let expected_empty_root_16 = calculate_subtree([0; 16]); - assert(calculate_empty_tree_root(4) == expected_empty_root_16); - - let expected_empty_root_32 = calculate_subtree([0; 32]); - assert(calculate_empty_tree_root(5) == expected_empty_root_32); - - let expected_empty_root_64 = calculate_subtree([0; 64]); - assert(calculate_empty_tree_root(6) == expected_empty_root_64); - - let expected_empty_root_128 = calculate_subtree([0; 128]); - assert(calculate_empty_tree_root(7) == expected_empty_root_128); -} +mod membership; +mod merkle_tree; +mod root; + +use leaf_preimage::{IndexedTreeLeafPreimage, LeafPreimage}; +use membership::{ + assert_check_membership, assert_check_non_membership, check_membership, check_non_membership, + MembershipWitness +}; +use merkle_tree::MerkleTree; +use root::{calculate_empty_tree_root, calculate_subtree_root, root_from_sibling_path}; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/append_only_tree.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/append_only_tree.nr index 18145d3d233..b1faf2c06c7 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/append_only_tree.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/append_only_tree.nr @@ -1,6 +1,6 @@ use crate::{ abis::{append_only_tree_snapshot::AppendOnlyTreeSnapshot}, - hash::{assert_check_membership, root_from_sibling_path} + merkle_tree::{membership::assert_check_membership, root::root_from_sibling_path} }; pub fn insert_subtree_to_snapshot_tree( diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr index 9c291900061..6cfc75baaf6 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr @@ -1,53 +1,12 @@ use crate::{ - abis::{append_only_tree_snapshot::AppendOnlyTreeSnapshot, membership_witness::MembershipWitness}, - hash::{assert_check_membership, root_from_sibling_path}, - merkle_tree::{calculate_subtree, calculate_empty_tree_root} + abis::{append_only_tree_snapshot::AppendOnlyTreeSnapshot}, + merkle_tree::{ + membership::{assert_check_membership, MembershipWitness}, + root::{calculate_subtree_root, calculate_empty_tree_root, root_from_sibling_path} +}, + traits::{Empty, Hash, is_empty}, utils::arrays::check_permutation }; -fn check_permutation( - original_array: [T; N], - sorted_array: [T; N], - indexes: [u64; N], - is_equal: fn(T, T) -> bool -) { - let mut seen_value = [false; N]; - for i in 0..N { - let index = indexes[i]; - let sorted_value = sorted_array[i]; - let original_value = original_array[index]; - assert(is_equal(sorted_value, original_value), "Invalid index"); - assert(!seen_value[index], "Duplicated index"); - seen_value[index] = true; - } -} - -#[test] -fn check_permutation_basic_test() { - let original_array = [1, 2, 3]; - let sorted_array = [3, 1, 2]; - let indexes = [2, 0, 1]; - let is_equal = |a: Field, b: Field| a == b; - check_permutation(original_array, sorted_array, indexes, is_equal); -} - -#[test(should_fail_with = "Duplicated index")] -fn check_permutation_duplicated_index() { - let original_array = [0, 1, 0]; - let sorted_array = [1, 0, 0]; - let indexes = [1, 0, 0]; - let is_equal = |a: Field, b: Field| a == b; - check_permutation(original_array, sorted_array, indexes, is_equal); -} - -#[test(should_fail_with = "Invalid index")] -fn check_permutation_invalid_index() { - let original_array = [0, 1, 2]; - let sorted_array = [1, 0, 0]; - let indexes = [1, 0, 2]; - let is_equal = |a: Field, b: Field| a == b; - check_permutation(original_array, sorted_array, indexes, is_equal); -} - pub fn batch_insert( start_snapshot: AppendOnlyTreeSnapshot, values_to_insert: [Value; SubtreeWidth], @@ -56,22 +15,14 @@ pub fn batch_insert; SubtreeWidth], - is_equal: fn(Value, Value) -> bool, - is_empty_value: fn(Value) -> bool, - hash_leaf: fn(Leaf) -> Field, is_valid_low_leaf: fn(Leaf, Value) -> bool, update_low_leaf: fn(Leaf, Value, u64) -> Leaf, build_insertion_leaf: fn(Value, Leaf) -> Leaf, _subtree_height: [Field; SubtreeHeight], _tree_height: [Field; TreeHeight] -) -> AppendOnlyTreeSnapshot { +) -> AppendOnlyTreeSnapshot where Value: Eq + Empty, Leaf: Hash { // A permutation to the values is provided to make the insertion use only one insertion strategy - check_permutation( - values_to_insert, - sorted_values, - sorted_values_indexes, - is_equal - ); + check_permutation(values_to_insert, sorted_values, sorted_values_indexes); // Now, update the existing leaves with the new leaves let mut current_tree_root = start_snapshot.root; @@ -80,7 +31,7 @@ pub fn batch_insert Field; fn as_leaf(self) -> Field; } + +trait IndexedTreeLeafPreimage { + fn get_key(self) -> Field; + fn get_next_key(self) -> Field; + fn as_leaf(self) -> Field; +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/membership.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/membership.nr new file mode 100644 index 00000000000..6fc8d91d13b --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/membership.nr @@ -0,0 +1,343 @@ +use crate::{merkle_tree::{leaf_preimage::IndexedTreeLeafPreimage, root::root_from_sibling_path}}; + +struct MembershipWitness { + leaf_index: Field, + sibling_path: [Field; N] +} + +pub fn check_membership(leaf: Field, index: Field, sibling_path: [Field; N], root: Field) -> bool { + let calculated_root = root_from_sibling_path(leaf, index, sibling_path); + calculated_root == root +} + +pub fn assert_check_membership(leaf: Field, index: Field, sibling_path: [Field; N], root: Field) { + assert(check_membership(leaf, index, sibling_path, root), "membership check failed"); +} + +struct NonMembershipCheckErrorCodeEnum { + NADA: u64, + IS_EMPTY: u64, + NOT_EXISTS: u64, + NOT_GREATER_THAN_LOW: u64, + NOT_LESS_THAN_NEXT: u64, +} + +global NonMembershipCheckErrorCode = NonMembershipCheckErrorCodeEnum { + NADA: 0, + IS_EMPTY: 1, + NOT_EXISTS: 2, + NOT_GREATER_THAN_LOW: 3, + NOT_LESS_THAN_NEXT: 4, +}; + +fn check_non_membership_internal( + key: Field, + low_leaf_preimage: LEAF_PREIMAGE, + low_leaf_membership_witness: MembershipWitness, + tree_root: Field +) -> u64 where + LEAF_PREIMAGE: IndexedTreeLeafPreimage { + let low_key = low_leaf_preimage.get_key(); + let next_key = low_leaf_preimage.get_next_key(); + let is_empty_leaf = (low_key == 0) & (next_key == 0); + + let low_leaf_exists = check_membership( + low_leaf_preimage.as_leaf(), + low_leaf_membership_witness.leaf_index, + low_leaf_membership_witness.sibling_path, + tree_root + ); + + if is_empty_leaf { + NonMembershipCheckErrorCode.IS_EMPTY + } else if !low_leaf_exists { + NonMembershipCheckErrorCode.NOT_EXISTS + } else if !low_key.lt(key) { + NonMembershipCheckErrorCode.NOT_GREATER_THAN_LOW + } else if !key.lt(next_key) & (next_key != 0) { + NonMembershipCheckErrorCode.NOT_LESS_THAN_NEXT + } else { + NonMembershipCheckErrorCode.NADA + } +} + +pub fn check_non_membership( + key: Field, + low_leaf_preimage: LEAF_PREIMAGE, + low_leaf_membership_witness: MembershipWitness, + tree_root: Field +) -> bool where + LEAF_PREIMAGE: IndexedTreeLeafPreimage { + let error = check_non_membership_internal(key, low_leaf_preimage, low_leaf_membership_witness, tree_root); + error == NonMembershipCheckErrorCode.NADA +} + +pub fn assert_check_non_membership( + key: Field, + low_leaf_preimage: LEAF_PREIMAGE, + low_leaf_membership_witness: MembershipWitness, + tree_root: Field +) where + LEAF_PREIMAGE: IndexedTreeLeafPreimage { + let error = check_non_membership_internal(key, low_leaf_preimage, low_leaf_membership_witness, tree_root); + if error != NonMembershipCheckErrorCode.NADA { + assert( + error != NonMembershipCheckErrorCode.IS_EMPTY, "Cannot check non membership against empty leaf" + ); + assert(error != NonMembershipCheckErrorCode.NOT_EXISTS, "Low leaf does not exist"); + assert( + error != NonMembershipCheckErrorCode.NOT_GREATER_THAN_LOW, "Key is not greater than the low leaf" + ); + assert( + error != NonMembershipCheckErrorCode.NOT_LESS_THAN_NEXT, "Key is not less than the next leaf" + ); + assert(false, "Unknown error"); + } +} + +mod tests { + use crate::{ + merkle_tree::{ + leaf_preimage::{IndexedTreeLeafPreimage, LeafPreimage}, + membership::{ + assert_check_membership, assert_check_non_membership, check_membership, check_non_membership, + MembershipWitness + } + }, + tests::merkle_tree_utils::NonEmptyMerkleTree + }; + use dep::std::hash::pedersen_hash; + + struct TestLeafPreimage { + value: Field, + next_value: Field, + } + + impl LeafPreimage for TestLeafPreimage { + fn get_key(self) -> Field { + self.value + } + + fn as_leaf(self) -> Field { + pedersen_hash([self.value]) + } + } + + impl IndexedTreeLeafPreimage for TestLeafPreimage { + fn get_key(self) -> Field { + self.value + } + + fn get_next_key(self) -> Field { + self.next_value + } + + fn as_leaf(self) -> Field { + pedersen_hash([self.value]) + } + } + + global leaf_preimages = [ + TestLeafPreimage { value: 20, next_value: 30 }, + TestLeafPreimage { value: 40, next_value: 0 }, + TestLeafPreimage { value: 10, next_value: 20 }, + TestLeafPreimage { value: 30, next_value: 40 }, + ]; + + fn build_tree() -> NonEmptyMerkleTree<4, 3, 1, 2> { + NonEmptyMerkleTree::new( + leaf_preimages.map(|leaf_preimage: TestLeafPreimage| leaf_preimage.as_leaf()), + [0; 3], + [0; 1], + [0; 2] + ) + } + + fn check_membership_at_index(leaf_index: Field, leaf: Field) -> bool { + let tree = build_tree(); + let tree_root = tree.get_root(); + + check_membership( + leaf, + leaf_index, + tree.get_sibling_path(leaf_index as u64), + tree_root + ) + } + + fn assert_check_membership_at_index(leaf_index: Field, leaf: Field) { + let tree = build_tree(); + let tree_root = tree.get_root(); + + assert_check_membership( + leaf, + leaf_index, + tree.get_sibling_path(leaf_index as u64), + tree_root + ); + } + + fn check_non_membership_at_index(low_leaf_index: u64, leaf: Field) -> bool { + let tree = build_tree(); + let tree_root = tree.get_root(); + let leaf_preimage = if low_leaf_index < leaf_preimages.len() { + leaf_preimages[low_leaf_index] + } else { + TestLeafPreimage { value: 0, next_value: 0 } + }; + + check_non_membership( + leaf, + leaf_preimage, + MembershipWitness { leaf_index: low_leaf_index as Field, sibling_path: tree.get_sibling_path(low_leaf_index) } , + tree_root + ) + } + + fn assert_check_non_membership_at_index(low_leaf_index: u64, leaf: Field) { + let tree = build_tree(); + let tree_root = tree.get_root(); + let leaf_preimage = if low_leaf_index < leaf_preimages.len() { + leaf_preimages[low_leaf_index] + } else { + TestLeafPreimage { value: 0, next_value: 0 } + }; + + assert_check_non_membership( + leaf, + leaf_preimage, + MembershipWitness { leaf_index: low_leaf_index as Field, sibling_path: tree.get_sibling_path(low_leaf_index) } , + tree_root + ); + } + + #[test] + fn test_check_membership() { + assert_eq(check_membership_at_index(0, leaf_preimages[0].as_leaf()), true); + assert_eq(check_membership_at_index(2, leaf_preimages[2].as_leaf()), true); + } + + #[test] + fn test_assert_check_membership() { + assert_check_membership_at_index(0, leaf_preimages[0].as_leaf()); + assert_check_membership_at_index(2, leaf_preimages[2].as_leaf()); + } + + #[test] + fn test_check_membership_false_wrong_leaf() { + assert_eq(check_membership_at_index(0, leaf_preimages[1].as_leaf()), false); + assert_eq(check_membership_at_index(2, leaf_preimages[0].as_leaf()), false); + } + + #[test(should_fail_with="membership check failed")] + fn test_assert_check_membership_failed_wrong_leaf() { + assert_check_membership_at_index(0, leaf_preimages[1].as_leaf()); + } + + #[test] + fn test_check_membership_false_wrong_root() { + let tree = build_tree(); + let tree_root = 56; + + let res = check_membership( + leaf_preimages[0].as_leaf(), + 0, + tree.get_sibling_path(0), + tree_root + ); + assert_eq(res, false); + } + + #[test(should_fail_with="membership check failed")] + fn test_assert_check_membership_false_wrong_root() { + let tree = build_tree(); + let tree_root = 56; + + assert_check_membership( + leaf_preimages[0].as_leaf(), + 0, + tree.get_sibling_path(0), + tree_root + ); + } + + #[test] + fn test_check_non_membership() { + assert_eq(check_non_membership_at_index(0, 25), true); + } + + #[test] + fn test_assert_check_non_membership() { + assert_check_non_membership_at_index(0, 25); + } + + #[test] + fn test_check_non_membership_greater_than_max() { + assert_eq(check_non_membership_at_index(1, 45), true); + } + + #[test] + fn test_assert_check_non_membership_greater_than_max() { + assert_check_non_membership_at_index(1, 45); + } + + #[test] + fn test_check_non_membership_false_empty_leaf() { + assert_eq(check_non_membership_at_index(4, 25), false); + } + + #[test(should_fail_with="Cannot check non membership against empty leaf")] + fn test_assert_check_non_membership_failed_empty_leaf() { + assert_check_non_membership_at_index(4, 25); + } + + #[test] + fn test_check_non_membership_false_wrong_low_leaf() { + assert_eq(check_non_membership_at_index(3, 25), false); + } + + #[test(should_fail_with="Key is not greater than the low leaf")] + fn test_assert_check_non_membership_failed_wrong_low_leaf() { + assert_check_non_membership_at_index(3, 25); + } + + #[test] + fn test_check_non_membership_false_wrong_next_key() { + assert_eq(check_non_membership_at_index(2, 25), false); + } + + #[test(should_fail_with="Key is not less than the next leaf")] + fn test_assert_check_non_membership_failed_wrong_next_key() { + assert_check_non_membership_at_index(2, 25); + } + + #[test] + fn test_check_non_membership_false_invalid_leaf() { + let tree = build_tree(); + let tree_root = tree.get_root(); + + let fake_leaf = TestLeafPreimage { value: 50, next_value: 60 }; + assert_eq( + check_non_membership( + 55, + fake_leaf, + MembershipWitness { leaf_index: 1, sibling_path: tree.get_sibling_path(1) } , + tree_root + ), false + ); + } + + #[test(should_fail_with="Low leaf does not exist")] + fn test_assert_check_non_membership_failed_invalid_leaf() { + let tree = build_tree(); + let tree_root = tree.get_root(); + + let fake_leaf = TestLeafPreimage { value: 50, next_value: 60 }; + assert_check_non_membership( + 55, + fake_leaf, + MembershipWitness { leaf_index: 1, sibling_path: tree.get_sibling_path(1) } , + tree_root + ); + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/merkle_tree.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/merkle_tree.nr new file mode 100644 index 00000000000..f1cacb956ac --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/merkle_tree.nr @@ -0,0 +1,31 @@ +struct MerkleTree { + leaves: [Field; N], + nodes: [Field; N], +} + +impl MerkleTree { + pub fn new(leaves: [Field; N]) -> Self { + let mut nodes = [0; N]; + + // We need one less node than leaves, but we cannot have computed array lengths + let total_nodes = N - 1; + let half_size = N / 2; + + // hash base layer + for i in 0..half_size { + nodes[i] = dep::std::hash::pedersen_hash([leaves[2*i], leaves[2*i+1]]); + } + + // hash the other layers + for i in 0..(total_nodes - half_size) { + nodes[half_size+i] = dep::std::hash::pedersen_hash([nodes[2*i], nodes[2*i+1]]); + } + + MerkleTree { leaves, nodes } + } + + fn get_root(self) -> Field { + self.nodes[N - 2] + } +} + diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/root.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/root.nr new file mode 100644 index 00000000000..e659261fbdd --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/root.nr @@ -0,0 +1,102 @@ +use crate::{hash::merkle_hash, merkle_tree::merkle_tree::MerkleTree}; + +// Calculate the Merkle tree root from the sibling path and leaf. +// +// The leaf is hashed with its sibling, and then the result is hashed +// with the next sibling etc in the path. The last hash is the root. +// +// TODO(David/Someone): The cpp code is using a uint256, whereas its +// TODO a bit simpler in Noir to just have a bit array. +// TODO: I'd generally like to avoid u256 for algorithms like +// this because it means we never even need to consider cases where +// the index is greater than p. +pub fn root_from_sibling_path(leaf: Field, leaf_index: Field, sibling_path: [Field; N]) -> Field { + let mut node = leaf; + let indices = leaf_index.to_le_bits(N); + + for i in 0..N { + let (hash_left, hash_right) = if indices[i] == 1 { + (sibling_path[i], node) + } else { + (node, sibling_path[i]) + }; + node = merkle_hash(hash_left, hash_right); + } + node +} + +pub fn calculate_subtree_root(leaves: [Field; N]) -> Field { + MerkleTree::new(leaves).get_root() +} + +// These values are precomputed and we run tests to ensure that they +// are correct. The values themselves were computed from the cpp code. +// +// Would be good if we could use width since the compute_subtree +// algorithm uses depth. +pub fn calculate_empty_tree_root(depth: u64) -> Field { + if depth == 0 { + 0 + } else if depth == 1 { + 0x27b1d0839a5b23baf12a8d195b18ac288fcf401afb2f70b8a4b529ede5fa9fed + } else if depth == 2 { + 0x21dbfd1d029bf447152fcf89e355c334610d1632436ba170f738107266a71550 + } else if depth == 3 { + 0x0bcd1f91cf7bdd471d0a30c58c4706f3fdab3807a954b8f5b5e3bfec87d001bb + } else if depth == 4 { + 0x06e62084ee7b602fe9abc15632dda3269f56fb0c6e12519a2eb2ec897091919d + } else if depth == 5 { + 0x03c9e2e67178ac638746f068907e6677b4cc7a9592ef234ab6ab518f17efffa0 + } else if depth == 6 { + 0x15d28cad4c0736decea8997cb324cf0a0e0602f4d74472cd977bce2c8dd9923f + } else if depth == 7 { + 0x268ed1e1c94c3a45a14db4108bc306613a1c23fab68e0466a002dfb0a3f8d2ab + } else if depth == 8 { + 0x0cd8d5695bc2dde99dd531671f76f1482f14ddba8eeca7cb9686d4a62359c257 + } else if depth == 9 { + 0x047fbb7eb974155702149e58ea6ad91f4c6e953e693db35e953e250d8ceac9a9 + } else if depth == 10 { + 0x00c5ae2526e665e2c7c698c11a06098b7159f720606d50e7660deb55758b0b02 + } else { + assert(false, "depth should be between 0 and 10"); + 0 + } +} + +#[test] +fn test_merkle_root_interop_test() { + // This is a test to ensure that we match the cpp implementation. + // You can grep for `TEST_F(root_rollup_tests, noir_interop_test)` + // to find the test that matches this. + let root = calculate_subtree_root([1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]); + assert(0x17e8bb70a11d0c946345950879484d2f4f9fef397ff6adbfdec3baab2d41faab == root); + + let empty_root = calculate_subtree_root([0; 16]); + assert(0x06e62084ee7b602fe9abc15632dda3269f56fb0c6e12519a2eb2ec897091919d == empty_root); +} + +#[test] +fn test_empty_subroot() { + assert(calculate_empty_tree_root(0) == 0); + + let expected_empty_root_2 = calculate_subtree_root([0; 2]); + assert(calculate_empty_tree_root(1) == expected_empty_root_2); + + let expected_empty_root_4 = calculate_subtree_root([0; 4]); + assert(calculate_empty_tree_root(2) == expected_empty_root_4); + + let expected_empty_root_8 = calculate_subtree_root([0; 8]); + assert(calculate_empty_tree_root(3) == expected_empty_root_8); + + let expected_empty_root_16 = calculate_subtree_root([0; 16]); + assert(calculate_empty_tree_root(4) == expected_empty_root_16); + + let expected_empty_root_32 = calculate_subtree_root([0; 32]); + assert(calculate_empty_tree_root(5) == expected_empty_root_32); + + let expected_empty_root_64 = calculate_subtree_root([0; 64]); + assert(calculate_empty_tree_root(6) == expected_empty_root_64); + + let expected_empty_root_128 = calculate_subtree_root([0; 128]); + assert(calculate_empty_tree_root(7) == expected_empty_root_128); +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests.nr index c02eefc2353..77f7f32bc6d 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/tests.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests.nr @@ -6,3 +6,4 @@ mod private_call_data_builder; mod private_circuit_public_inputs_builder; mod public_call_data_builder; mod public_circuit_public_inputs_builder; +mod sort; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/kernel_data_builder.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/kernel_data_builder.nr index 6ab1f79d6f1..50769bbfbb4 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/tests/kernel_data_builder.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/kernel_data_builder.nr @@ -146,6 +146,16 @@ impl PreviousKernelDataBuilder { value_offset + nullifier_index as Field } + pub fn add_nullifier(&mut self, unsiloed_nullifier: Field) { + let value = silo_nullifier(self.storage_contract_address, unsiloed_nullifier); + self.end.new_nullifiers.push(SideEffectLinkedToNoteHash { value, note_hash: 0, counter: self.next_sideffect_counter() }); + } + + pub fn add_nullifier_non_revertible(&mut self, unsiloed_nullifier: Field) { + let value = silo_nullifier(self.storage_contract_address, unsiloed_nullifier); + self.end_non_revertible.new_nullifiers.push(SideEffectLinkedToNoteHash { value, note_hash: 0, counter: self.next_sideffect_counter() }); + } + pub fn append_new_nullifiers_from_private(&mut self, num_extra_nullifier: u64) { // in private kernel, the nullifiers have not yet been partitioned // (that is part of the job of the private kernel tail) @@ -154,13 +164,7 @@ impl PreviousKernelDataBuilder { for i in 0..MAX_NEW_NULLIFIERS_PER_TX { if i < num_extra_nullifier { let mock_value = self.get_mock_nullifier_value(index_offset + i); - self.end.new_nullifiers.push( - SideEffectLinkedToNoteHash { - value: silo_nullifier(self.storage_contract_address, mock_value), - note_hash: 0, - counter: self.next_sideffect_counter() - } - ); + self.add_nullifier(mock_value); } } } @@ -170,13 +174,7 @@ impl PreviousKernelDataBuilder { for i in 0..MAX_NEW_NULLIFIERS_PER_TX { if i < num_extra_nullifier { let mock_value = self.get_mock_nullifier_value(index_offset + i); - self.end.new_nullifiers.push( - SideEffectLinkedToNoteHash { - value: silo_nullifier(self.storage_contract_address, mock_value), - note_hash: 0, - counter: self.next_sideffect_counter() - } - ); + self.add_nullifier(mock_value); } } } @@ -186,13 +184,7 @@ impl PreviousKernelDataBuilder { for i in 0..MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX { if i < num_extra_nullifier { let mock_value = self.get_mock_nullifier_value_non_revertible(index_offset + i); - self.end_non_revertible.new_nullifiers.push( - SideEffectLinkedToNoteHash { - value: silo_nullifier(self.storage_contract_address, mock_value), - note_hash: 0, - counter: self.next_sideffect_counter() - } - ); + self.add_nullifier_non_revertible(mock_value); } } } @@ -221,6 +213,15 @@ impl PreviousKernelDataBuilder { read_request_index } + pub fn add_non_existent_read_request_for_nullifier(&mut self, unsiloed_nullifier: Field) { + let read_request = ReadRequestContext { + value: unsiloed_nullifier, + counter: self.next_sideffect_counter(), + contract_address: self.storage_contract_address + }; + self.end_non_revertible.nullifier_non_existent_read_requests.push(read_request); + } + // snapshot the side effects // this is useful in the private tail circuit to test side effect splitting pub fn capture_min_revertible_side_effect_counter(&mut self) { diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/public_circuit_public_inputs_builder.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/public_circuit_public_inputs_builder.nr index ae74ec6bb73..d4bf5c3d294 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/tests/public_circuit_public_inputs_builder.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/public_circuit_public_inputs_builder.nr @@ -9,9 +9,9 @@ use crate::{ }; use crate::constants::{ MAX_NEW_NOTE_HASHES_PER_CALL, MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL, - MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, - MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, - RETURN_VALUES_LENGTH + MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, + MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL, + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH }; struct PublicCircuitPublicInputsBuilder { @@ -19,6 +19,7 @@ struct PublicCircuitPublicInputsBuilder { args_hash: Field, return_values: BoundedVec, nullifier_read_requests: BoundedVec, + nullifier_non_existent_read_requests: BoundedVec, contract_storage_update_requests: BoundedVec, contract_storage_reads: BoundedVec, public_call_stack_hashes: BoundedVec, @@ -46,6 +47,7 @@ impl PublicCircuitPublicInputsBuilder { args_hash: self.args_hash, return_values: self.return_values.storage, nullifier_read_requests: self.nullifier_read_requests.storage, + nullifier_non_existent_read_requests: self.nullifier_non_existent_read_requests.storage, contract_storage_update_requests: self.contract_storage_update_requests.storage, contract_storage_reads: self.contract_storage_reads.storage, public_call_stack_hashes: self.public_call_stack_hashes.storage, diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/sort.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/sort.nr new file mode 100644 index 00000000000..d067eb1a2d9 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/sort.nr @@ -0,0 +1,87 @@ +use crate::traits::{Empty, is_empty}; + +struct SortedTuple { + value: T, + original_index: u64, +} + +pub fn sort_high_to_low( + values: [T; N], + is_less_than: fn(T, T) -> bool +) -> [SortedTuple; N] where T: Eq { + let mut sorted_tuples = [SortedTuple { value: values[0], original_index: 0 }; N]; + + for i in 0..N { + sorted_tuples[i] = SortedTuple { + value: values[i], + original_index: i, + }; + } + + sorted_tuples.sort_via(|a: SortedTuple, b: SortedTuple| (b.value == a.value) | is_less_than(b.value, a.value)) +} + +struct SortedResult { + sorted_array: [T; N], + sorted_index_hints: [u64; N], +} + +pub fn sort_get_sorted_hints( + values: [T; N], + ordering: fn(T, T) -> bool +) -> SortedResult where T: Eq + Empty { + let mut tuples = [SortedTuple { value: values[0], original_index: 0 }; N]; + for i in 0..N { + tuples[i] = SortedTuple { + value: values[i], + original_index: i, + }; + } + + let sorted_tuples = tuples.sort_via( + |a: SortedTuple, b: SortedTuple| is_empty(b.value) | (!is_empty(a.value) & !is_empty(b.value) & ordering(a.value, b.value)) + ); + + let sorted_array = sorted_tuples.map(|t: SortedTuple| t.value); + let mut sorted_index_hints = [0; N]; + for i in 0..N { + if !is_empty(sorted_tuples[i].value) { + let original_index = sorted_tuples[i].original_index; + sorted_index_hints[original_index] = i; + } + } + + SortedResult { sorted_array, sorted_index_hints } +} + +#[test] +fn sort_get_sorted_hints_asc_non_padded() { + let values = [40, 60, 20, 50]; + let res = sort_get_sorted_hints(values, |a: Field, b: Field| a.lt(b)); + assert_eq(res.sorted_array, [20, 40, 50, 60]); + assert_eq(res.sorted_index_hints, [1, 3, 0, 2]); +} + +#[test] +fn sort_get_sorted_hints_desc_non_padded() { + let values = [40, 20, 60, 50]; + let res = sort_get_sorted_hints(values, |a: Field, b: Field| b.lt(a)); + assert_eq(res.sorted_array, [60, 50, 40, 20]); + assert_eq(res.sorted_index_hints, [2, 3, 0, 1]); +} + +#[test] +fn sort_get_sorted_hints_asc_padded() { + let values = [40, 60, 20, 50, 0, 0]; + let res = sort_get_sorted_hints(values, |a: Field, b: Field| a.lt(b)); + assert_eq(res.sorted_array, [20, 40, 50, 60, 0, 0]); + assert_eq(res.sorted_index_hints, [1, 3, 0, 2, 0, 0]); +} + +#[test] +fn sort_get_sorted_hints_desc_padded() { + let values = [40, 20, 60, 50, 0, 0]; + let res = sort_get_sorted_hints(values, |a: Field, b: Field| b.lt(a)); + assert_eq(res.sorted_array, [60, 50, 40, 20, 0, 0]); + assert_eq(res.sorted_index_hints, [2, 3, 0, 1, 0, 0]); +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr index ec5e186d412..73af12d2969 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr @@ -57,6 +57,16 @@ pub fn array_eq(array: [T; N], expected: [T; S]) -> bool where T: Empty eq } +pub fn find_index(array: [T; N], find: fn[Env](T) -> bool) -> u64 { + let mut index = N; + for i in 0..N { + if (index == N) & find(array[i]) { + index = i; + } + } + index +} + pub fn array_cp(array: [T; N]) -> [T; S] where T: Empty { let mut result: [T; S] = [T::empty(); S]; for i in 0..S { @@ -102,6 +112,45 @@ pub fn array_merge(array1: [T; N], array2: [T; N]) -> [T; N] where T: Empt result } +pub fn check_permutation( + original_array: [T; N], + permuted_array: [T; N], + original_indexes: [u64; N] +) where T: Eq + Empty { + let mut seen_value = [false; N]; + for i in 0..N { + let index = original_indexes[i]; + let original_value = original_array[index]; + assert(permuted_array[i].eq(original_value), "Invalid index"); + assert(!seen_value[index], "Duplicated index"); + seen_value[index] = true; + } +} + +pub fn assert_sorted_array( + original_array: [T; N], + sorted_array: [T; N], + sorted_indexes: [u64; N], + ordering: fn[Env](T, T) -> bool +) where T: Eq + Empty { + let mut seen_empty = false; + for i in 0..N { + let original_value = original_array[i]; + if is_empty(original_value) { + seen_empty = true; + assert(is_empty(sorted_array[i]), "Empty values must not be mixed with sorted values"); + } else { + assert(!seen_empty, "Empty values must be padded to the right"); + + let index = sorted_indexes[i]; + assert(sorted_array[index].eq(original_value), "Invalid index"); + if i != 0 { + assert(ordering(sorted_array[i - 1], sorted_array[i]), "Values not sorted"); + } + } + } +} + #[test] fn smoke_validate_array() { let valid_array = []; @@ -147,3 +196,107 @@ fn test_array_length() { assert_eq(array_length([123, 0, 456]), 1); assert_eq(array_length([0, 123, 0, 456]), 0); } + +#[test] +fn find_index_greater_than_min() { + let values = [10, 20, 30, 40]; + let min = 22; + let index = find_index(values, |v: Field| min.lt(v)); + assert_eq(index, 2); +} + +#[test] +fn find_index_not_found() { + let values = [10, 20, 30, 40]; + let min = 100; + let index = find_index(values, |v: Field| min.lt(v)); + assert_eq(index, 4); +} + +#[test] +fn check_permutation_basic_test() { + let original_array = [1, 2, 3]; + let permuted_array = [3, 1, 2]; + let indexes = [2, 0, 1]; + check_permutation(original_array, permuted_array, indexes); +} + +#[test(should_fail_with = "Duplicated index")] +fn check_permutation_duplicated_index() { + let original_array = [0, 1, 0]; + let permuted_array = [1, 0, 0]; + let indexes = [1, 0, 0]; + check_permutation(original_array, permuted_array, indexes); +} + +#[test(should_fail_with = "Invalid index")] +fn check_permutation_invalid_index() { + let original_array = [0, 1, 2]; + let permuted_array = [1, 0, 0]; + let indexes = [1, 0, 2]; + check_permutation(original_array, permuted_array, indexes); +} + +#[test] +fn assert_sorted_array_asc() { + let original = [30, 20, 90, 50, 0, 0]; + let sorted = [20, 30, 50, 90, 0, 0]; + let indexes = [1, 0, 3, 2, 0, 0]; + assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b)); +} + +#[test] +fn assert_sorted_array_desc() { + let original = [30, 20, 90, 50, 0, 0]; + let sorted = [90, 50, 30, 20, 0, 0]; + let indexes = [2, 3, 0, 1, 0, 0]; + assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| b.lt(a)); +} + +#[test] +fn assert_sorted_array_all_empty() { + let original = [0, 0, 0, 0, 0, 0]; + let sorted = [0, 0, 0, 0, 0, 0]; + let indexes = [0, 0, 0, 0, 0, 0]; + assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b)); +} + +#[test(should_fail_with="Values not sorted")] +fn assert_sorted_array_failed_ordering() { + let original = [30, 20, 90, 50, 0, 0]; + let sorted = [20, 30, 90, 50, 0, 0]; + let indexes = [1, 0, 2, 3, 0, 0]; + assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b)); +} + +#[test(should_fail_with="Values not sorted")] +fn assert_sorted_array_failed_misplaced_sorted() { + let original = [30, 20, 90, 50, 0, 0]; + let sorted = [20, 30, 50, 0, 0, 90]; + let indexes = [1, 0, 5, 2, 0, 0]; + assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b)); +} + +#[test(should_fail_with="Invalid index")] +fn assert_sorted_array_failed_wrong_index() { + let original = [30, 20, 90, 50, 0, 0]; + let sorted = [20, 30, 50, 90, 0, 0]; + let indexes = [1, 1, 2, 3, 0, 0]; + assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b)); +} + +#[test(should_fail_with="Empty values must be padded to the right")] +fn assert_sorted_array_failed_not_padded() { + let original = [30, 20, 90, 0, 50, 0]; + let sorted = [20, 30, 90, 0, 0, 0]; + let indexes = [1, 0, 2, 0, 0, 0]; + assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b)); +} + +#[test(should_fail_with="Empty values must not be mixed with sorted values")] +fn assert_sorted_array_failed_mixed_empty() { + let original = [30, 20, 90, 0, 0, 0]; + let sorted = [20, 30, 90, 0, 0, 10]; + let indexes = [1, 0, 2, 0, 0, 0]; + assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b)); +} diff --git a/yarn-project/aztec/CHANGELOG.md b/yarn-project/aztec/CHANGELOG.md index 9f62a9ce34f..b1309ecb8f8 100644 --- a/yarn-project/aztec/CHANGELOG.md +++ b/yarn-project/aztec/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [0.27.0](https://github.com/AztecProtocol/aztec-packages/compare/aztec-package-v0.26.6...aztec-package-v0.27.0) (2024-03-12) + + +### Features + +* Add api for inclusion proof of outgoing message in block [#4562](https://github.com/AztecProtocol/aztec-packages/issues/4562) ([#4899](https://github.com/AztecProtocol/aztec-packages/issues/4899)) ([26d2643](https://github.com/AztecProtocol/aztec-packages/commit/26d26437022567e2d54052f21b1c937259f26c94)) + + +### Miscellaneous + +* Pin foundry ([#5151](https://github.com/AztecProtocol/aztec-packages/issues/5151)) ([69bd7dd](https://github.com/AztecProtocol/aztec-packages/commit/69bd7dd45af6b197b23c25dc883a1a5485955203)) +* Remove old contract deployment flow ([#4970](https://github.com/AztecProtocol/aztec-packages/issues/4970)) ([6d15947](https://github.com/AztecProtocol/aztec-packages/commit/6d1594736e96cd744ea691a239fcd3a46bdade60)) + ## [0.26.6](https://github.com/AztecProtocol/aztec-packages/compare/aztec-package-v0.26.5...aztec-package-v0.26.6) (2024-03-08) diff --git a/yarn-project/aztec/package.json b/yarn-project/aztec/package.json index bc5e0f419b4..9ecb167db71 100644 --- a/yarn-project/aztec/package.json +++ b/yarn-project/aztec/package.json @@ -1,6 +1,6 @@ { "name": "@aztec/aztec", - "version": "0.26.6", + "version": "0.27.0", "type": "module", "exports": { ".": "./dest/index.js" diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index dbbc59cf255..a0af43b2763 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -11,6 +11,7 @@ export const MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL = 16; export const MAX_PUBLIC_DATA_READS_PER_CALL = 16; export const MAX_NOTE_HASH_READ_REQUESTS_PER_CALL = 32; export const MAX_NULLIFIER_READ_REQUESTS_PER_CALL = 2; +export const MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL = 2; export const MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL = 1; export const MAX_NEW_NOTE_HASHES_PER_TX = 64; export const MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX = 8; @@ -31,6 +32,7 @@ export const MAX_REVERTIBLE_PUBLIC_DATA_READS_PER_TX = 16; export const MAX_NEW_L2_TO_L1_MSGS_PER_TX = 2; export const MAX_NOTE_HASH_READ_REQUESTS_PER_TX = 128; export const MAX_NULLIFIER_READ_REQUESTS_PER_TX = 8; +export const MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX = 8; export const MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX = 4; export const NUM_ENCRYPTED_LOGS_HASHES_PER_TX = 1; export const NUM_UNENCRYPTED_LOGS_HASHES_PER_TX = 1; @@ -98,7 +100,7 @@ export const NULLIFIER_KEY_VALIDATION_REQUEST_CONTEXT_LENGTH = 5; export const PARTIAL_STATE_REFERENCE_LENGTH = 6; export const PRIVATE_CALL_STACK_ITEM_LENGTH = 214; export const PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH = 209; -export const PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 196; +export const PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 200; export const STATE_REFERENCE_LENGTH = 8; export const TX_CONTEXT_DATA_LENGTH = 4; export const TX_REQUEST_LENGTH = 10; diff --git a/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap b/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap index 0a9847e7740..0c8262f95ee 100644 --- a/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap +++ b/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap @@ -9,18 +9,18 @@ exports[`ContractClass creates a contract class from a contract compilation arti "selector": { "value": 2381782501 }, - "bytecode": "0x1f8b08000000000000ffed9d099415c779ef7b860106ddb92c4212629134da1020963b7716606680cbbe0b186058b431ec0804320c2024b1ef3b08c42210c28e9dc4cebe2f4ee22cce1e67b193d88e6327b1133bce7befbc73de39efbc735ece5152d5b73ecf7f8aeaabb9a3aecb7767be3ea7e6567f53dddfaffefd55f556ddfdc320084a82ecd443a5a7837b27fa7fc6fca63ed95415e3ba523e394b8a84b3b448387b140967599170f62c12ce5e45c2d9bb4838cb8b84b34f8c9c9aad34683fc5cdfb80075de3664c1499a61545a069b2c834ed5b049af60b8aa38fea5f249c038a84f3c122e11c58249c0f1509e7c345c2f94891700e2a12ce478b84737091700e2912cea145c239ac48381f2b12cec78b84f38922e1ac2c12ce278b84f3a922e17cba48389f2912ce6763e41c019cc3cdef73e697fe37d2fc8e32bfcf9bdfd1e6778ca96399991fabd238cda65295f5bfb44ad52ad5a8546bfe5769fe57a7d2789526a83451a57a951a546a5469924a93559a62ea3e55a5692a4d5769864a33559aa5d26c95e6a83457a5792acd5769814a0b557a41a5452a2d5669894a4d2a2d5569994acb2d96669556a8b452a5552aad56e945955e52e965955e51e95595d6a8d4a2d25a95d6a9b45ea50d2a6d5469934a9b55daa2d26b2a6d55699b4aafabb45da51d2abda1d2a754daa9d22e955a55da6d69b647a5bd2abda9d23e8bf32d95de56e91d95f6ab7440a5832a1d52e9b04a47543aaad231958eab7442a5932a9d52e9b44a67543aabd23995ceab7441a58b2a5d52e9b24aefaa7445a5ab2abda7d23595aeab7443a59b868582fd7d956ea9745ba50f54baa3d2872add55e9d32a7d46a51f53e9b32a7d4ea51f57e92754fa49953eafd21754fa29957e5aa59f51e96755fa39957e5ea55f50e91755fa25957e59a55f51e95755fa35957e5da5df50e93755faa24abfa5d26fabf43b2a7d49a5df55e9f754fa7d95fe40a52fabf4872afd914a7facd29fa8f4a796e67fa6d29fabf4172a7dc5fc8fae75fda54a7f65f27f6d7effc6fc7ed5fc7ecd5ae66f55fa3bcbf6f72a7dddb27d43a56f9afc3f98df6f99df7f34bfdf36bfdf31bfff647effd9fcfe8bf9fdaef9fd9ef9fd57f3fb6fe6f7fbe6f707e6f7dfcdef0f551a372c9b2f0fdaa64c1053bf53b3a145df2721b18707ed27ad450ff33ffaad34f632334fbfa45d4f33dfd3b2f732f3bdacf5949bf972cb3ec0cc0fb0ec03cdfc40cbfeb0997fd8b20f32f383c09e08e07aa9b16b5b0f632a011bc56129d87a06ed35d1b65eb43ab0f50eda6ba16db41d7b81ad8fb1f506db03c6560eb684b1f521cd54aa30b64c10574ca45af47a9371afd7dc43ea1b3fef3abdde7e9e78fbc7cfbb41af7780075e1d1f0f9a75f587b819686c03c0f690b13d08b6878d6d20d81e31b687c036c8d81e06dba3c6f608d8061bdb20b00d31b647c136d4d80683cd747bc110b03d666c43c1f6b8b10d03db13c6f618d82a8ded71b03d696c4f80ed2963ab041b8d4f79126ccf18db53607bd6d89e061bf5a9cf808d8eeb9e3536dd4ffcbf009631f652b03d47fd30d846501f0cb691d4ff826d14f5bd607b1e7c936d34f42b641b636cd447e9ffd5997c2688ab4da4c336313eeef5aa35ebf54e8c7fbde13db7faa04dd70cf8190f5a35987c8ce37aaad0778949e487ec65909f0365a91ce941fb1962d7fb930926df9063b93a6bb9249499e0a87f2688b7fe132d9e8916b38eff49c0117fcc56a725663b3ce51db3cd50d68e3d3ae6e98a313b1f383cc46c9d9f984da72466b3d71c82c01d7b74dcdb1563761570c41fb3b512b31d9ff28ed99d50d68e3d3af7e98a31bb0938e28fd9f175726cd0e129ef983d0465edd8a3f3dfae18b3bb81c343ccb6483fdbe129ef98bd0065edd8a36b315d31668f0247fc313bd153cc564bcc06d9fb4741e08e3dba2ed81563f63270c41fb3eb5ae4d8a0c353de31fb05286bc71e5da3ee8a31fb21707888595fd767d312b3d9fbe641e08e3dba5fd21563f6674c5edf67f86b739f6118d8fec6d81e03db578ded71b07dcdd89e807ac5df06d6d7481be8f094771bf833286bc772a5c977c536f025e0f010b37512b31d9ef28ed96f40593bf69e32f9ae18b35f010e0f313b4162b6c353de31fb7d286bc71e8d69e88a31fb2d93d7c70bff608d77d3b66f19db7360fb47631b01b66f1bdb48b07dc7d84681ed9f8ced79b0fdb3b18d06dbbf18db18b07dd7d8c682ed7bc6360e6cff6a6c29b0fd9bb15581edfbc69606db0f8cad1a6cff6e6c3560fba1b1d51a9bbe2740e353be6c6ce5e02f13c4b76d1350379a4aacf90ce4abfcf2a492c083beaae3f755adeb9e0e3a5ef76ae0a9f150f704f8e8084f0df0d4c6cf53e565df93ca6ee3b4a569027ca5a15e1ef651617f5617b4d794e6c95f126cd856c73b1827c4cf982e015fb46e9a9f008c64ab0546ea4ba9fde8beb95f491baf87b614ee9fd15f0638c85f1994f9e3a16d65071ab60af83ff6a3b596cd535c867141be68dd344ffe2aa03eb585674c7794b1c662f4d54794802f5ab7edbb02f229bffae4e4a963e2bbde936fbb4f23cdeb0be07b82e5bbdaf28d7d274db9f66d784cebe17ca2cad3b172d80f369a75d1790af9c1e38749a0415c7542df749e427ec85e06f9ba92b6b2548ef4a07e98d8751ba66d89ecf672e3ade59250a6de51ff4c106ffdedf3a6068b59ef6f9e877da187f6d0ee5c95d64df3d5a05d438476f5a01d95790eb4f370ec99f31caf0678a8ffc6fe2c55609e1403df780c4bdb0dcfa5f038c0d7f64a598c34efda5e1380b1c6c1e8e198309d6b7f580d8c649b083c694f9a456dd73413df3ece2b4bc0071d9bdbe736655066528fb6b24d255ed9d278ec4c5347cf79e3df4ee914b68f8ef0786e43559ee23185d76f3e0ae28d35bb5fb2fb9ba86b3cbefaf2b4c543f3e44f98855998855998855998855998855998855998855998855998855998855998f9336b1eba9f80e3b6a85c2d13467bfc9bafebfce1fbcaccbaf01ed0f7bc8e074ba7f0de1f8d831869d5b90cca9494b6b1fd00c683d96389a2b6a58fb1b1b9b625f9c3f16085b887586df1d8be2b1cfae07d69d4ccd71828bbcfa0791c9f46faa580c7577bacb2786cdfd867a4996a16d5cffa1ad3171567aef18495b1f94eaff3755f57bf1b4abf5fd1de7fd55a9a86ef6db46ce13df31e6df5f6719f37dffbceb8dfa07c9cf771715c06fa8abf9f4db71b9f501ab4df5760acfb1aab45ed8ac646d45bbecba04cbfd2b66dd300ffcf04f7ee1bb00cad9be647c2b20dd6bafbf9ab6fce7128f88c93fdac15d5ad0eb8a9cc43b0effe0393f7b40f4ce7fb8c04ee93e3dfbf64c78ba4f3e0c1fd9d8f63164ffbd114c663dce345ec7190aee365fb793b9fcfba45ed8b733d9f27ccc22cccc22cccc22cccc22cccc29cfa6493300bb3300bb3300bb3300bb3300bb330c7ccac79ec7be878efb79a096381c6d884f733e81d6b785fec73a56d7e7ddf03a47b4ea3ac3ae333caffa7b48dedf3268f631ff0bd52ae6de9ebde5ad4b6247fdcc63e5438f4d19a3538346bf4c468f71934df088ca45f03f0f86a8f51efee70f51929a69a45f5b3bee23e2ace5cbe2b63f39d1d2fe263fc1a8d17b1fb13aa03eecf6a2c1bde078f7a5f589565bbdf7d806b5c1be5f19d0bf1c74fbadd3d607bac20be5f95ca7cc5684be324e2ef07d2299f6d85da288d09493bea4a65be0afbb9bf35791c2b540debfa9ee3ff34e51a93807deae4f8eb1c6edf29665db47d273b7c67803526df55e8bbc45a37d9cb20ffddd27b19480fd29ad8751bc1f758452d57632d978432931cf5cfc45cffc9160f6e633de9d8f93ac4d9f7e058cf579f342942a391a01195c1635e5fe36aed3ed21e338d63227b5b65f0f894cafc0fe8a3a2c6a4bbc654fa3a5e881acf89c70bae631abb8ef6d8f1ee3e1ef03fa1bf6880ff67824f3e1ef03f21865cc7a5b4fe51b07ee2ea1d44ef5ba8cc7f59fb515f6debe3de7be53af7235e1c6f4865cae0fd5475269fcfb9dffd3a8f779dfbe1725175c77e21ee7d23c623b2e0fb22a94c5fa335c5634304f704c7b203229625adec771fe279291e23c4ffdec86c7f33d9aa0bb5a94950172a3308eae2e798297bfce9eb1d9974ac437d5095a3ae546618b4b5c74d3e01db099f291ae3f83f4db98e3f493f5de7a9f1d739dcbed3ccba68fb4e75f89e0eac31f9ae42df74fc497ec85e06f9d13ddaca5239d283b426f60ae044767bb97a6bb92494c938ea9f89b9fe532d9ea916b38e9d2721cec6c0b316befaea4ce0d6681468f4237f6073bdd7d8b58ff1f53c5ad43ea60a18ed7e13f793857c56cebe7eeb3a3ea1323fdab7233ff4b3094759fbba342d17e7f8747c1ea8dd71618f7bfd6662d63319b4d7336971f8f4ddcff2ddaf80be0758be0714d0b7682e9a73d29cd3377f70df520a8c3ece1bf11a794718f17d04b45c0f60f4f5cc663a0f467ce735eeef88d1c77bc23bfb4d213cd6e9098c3e9e37cef77a752d30e23b0688d1c733daf9bea71a9fdba6e57a03a38fef1ae137943ac2e8fad65139fc7af8ae515567bf0382df3aea038c3ebe0d9208da7fcfe4e318f19b7cb4dc03c0e8e33e5222687f5dede318f19b12b45cc23363ae7dbbe7b13fe97caf4114625c41d4b106faf670fd3f8d63133aa245a35f9e9cc73ee8dbc3f5af508bc6a0e35ae0fd390ff746c3763c290f1ebc8748cb3d088c533c314ece83710a30d272038131e389714a1e8c1960a4e51e02460fd72143c64c1e8c78bd8eec0f03e3344f8c53f3609c068cb4dc23c0e8e39a6202fc7684713a30d2728380718627c6e97930ce00465aee51609ce98971461e8c338191961b0c8cb33c31cecc83711630d272438071b627c6597930ce06465a6e2830cef1c4383b0fc639c048cb0d03c6b99e18e7e4c138171869b9c780719e27c6b97930ce03465aee71609cef89715e1e8cf38191967b02181778629c9f07e30260a4e52a8b80f1c922607caa08189f2e02c6678a80f1d922602c2f02c6e7817161fc8ce179ea823c181702cf0bf1f3849a2dcc83e705bf3ce1fb09173a7c2d8adf573adfba2f029ec5f1f384db62511e3cc49084e55ef0cb98ee2ca3e659123f4fa8d9e23c789680668b1d9a79604c779651f334c5cf136ab6240f9e26d06c8943330f8ce9ce326a9ea5f1f3849a35e5c1b314346b7268e68131dd5946cdb32c7e9e50b3a579f02c03cd963a34f3c098ee2ca3e6591e3f4fa8d9b23c78968366cb1c9a79604c779651f334c7cf136ab63c0f9e66d06cb943330f8ce9ce326a9e15f1f3849a35e7c1b302346b7668c6891179e27e7776b3c3d74a0675270664ec53048c0f140123de4ff7d17fe5ba9fdeec579f7467f5f1b5bd72dd4f47dfab3c69b132e8b816abfcf2e4bc9f8ebe577bd26255d0712d5603cf8b1eb448808f8ef0104312967bb00818071601e34345c0f87011303e52048c838a80f1d122601c5c048c438a80716811300e2b02c6c78a80f1f122607ca20818577866cc75fef26217f71d75aed2d57d479d977475df12e712e7ddc1b7c4b9c47977f02d712e71de1d7c4b9c4b9c7707df12e712e7ddc1b7c4b9c47977f02d712e71cec9f74b1e7c27c0074db9aef1134312965b218c5d9a11792ae3e34961ddd1d7cb0ceafeb283a7c453ddd1d72b0cea4e0cc5c6f8521130ae280246d1313b06b1338c9ae7554f3cafe4c1f32af0acf1c4f36a1e3c6b80a7257e9e30a6d6e4c1430c49586e451130be54048ca2a3e8c8895174ec3e3a0aa3300aa330de0fc662e8c3653f933d7759d30946cdb3367e9e50b3963c78d68266b4dc0b7e19d39d65d43cebe2e709355b9b07cf3ad06cad43330f8ce9ce326a9ef5f1f3849aadcb83673d68b6cea19907c674671935cf86f87942cdd6e7c1b301345befd0cc0363bab38c9a6763fc3ca1661bf2e0d9089a6d7068e68131dd5946cdb3297e9e50b38d79f06c02cd363a34f3c098ee2ca3e6d91c3f4fa8d9a63c783683669b1c9a79604c779651f36c899f27d46c731e3c5b40b3cd0ecd3c30a63bcba8795e8b9f27d46c4b1e3caf81665b1c9a71655c51048c2f1501a3671dd39d65d43c5b3df1bc9607cf56e0d9e689676b1e3cdb80e7f5f879c298da96070f312461b91545c0f85211308a8ea2232746d1b1fbe8288cc2288cf931be5c048cb2ad85912ba387f3ab9ccfa76cebe2bea39e4fe9eabea39e4fe9eabe25ce25cebb836f897389f3eee05be25ce2bc3bf896389738ef0ebe25ce25cebb836f897389f3eee05be25ce2bc3bf896389738ef0ebe25ce25cebb836f897389f3eee05be25ce2bc3bf896389738ef0ebe25ce25cebb836f897389f3eee05be25ce2bc3bf896389738ef0ebe25ce25cebb836f897389f3eee05be25ce29c93efedf1fb4ee7fb0ceb76e079dd83169eea99d2ebdd61d6f5518cfa69addeb0b4da66699584323b40bf373ce857027e69dd344ffef2651ece80d993ef745fb58e3e507ff2f192a587f6ff294f758feaeb3fd5c57d47f5f55ddd77545fdfd57d4b9c4b9c7707df12e712e7ddc1b7c4b9c43917df982f0bda8edbe9fd4a7a1d3b4dbea799a7f22fc37254a6a677f6b75f206dc8876f6943b2afe80ebe25ce25cebb836f897389f3eee05be29c5f9c633ccc2b004f60f10439785e65c6338319cf64663ca398f1a499f16c65c63381194f33339ee5cc789a98f1f460c6b38819cf4c663c5398f14c64c653cd8c6725339e8dcc78c631e359c78c670d339ef9cc786631e3c930e3a967c6339c194f0d339e55cc789632e359cc8c6701339eb1cc786633e319c18c672a339e428cf7cd87a78119cf73cc786a99f16c62c6b39e194f05339e24339e16663ca399f1cc61c6f322339e69cc781a99f1d431e34931e3d9c28c6724339ebecc78fa31e359c68c6709339e85cc78e632e3798519cf74663c9398f1ac66c6339e194f15339ed798f16c66c6b381194f7f663c0398f18c61c6b396194f09039e4470ef3b4512f0ffed602bb596d58f61550c6bfbff2e632f85655a4dbe8763ddbbc046cf76b53a96459d76415d32269ffa6453a813facac03cf9ab008e56263c6b99f18c61c63380194f7f663c1b98f16c66c6f31a339e2a663ce399f1ac66c6338919cf74663caf30e399cb8c6721339e25cc789631e3e9c78ca72f339e91cc78b630e34931e3a963c6d3c88c671a339e1799f1cc61c6339a194f0b339e24339e0a663ceb99f16c62c653cb8ce739663c0dcc78b633e399ca8c6704339ed9cc78c632e359c08c6731339ea5cc785631e3a961c6339c194f3d339e0c339e59cc78e633e359c38c671d339e71cc783632e359c98ca79a19cf44663c5398f1cc64c6b388194f0f663c4dcc789633e36966c6338119cf56663c69663ca398f14c66c6338319cfabcc78e639783c7c1f2ee4a1f1a1b46e9adfcec4b787ed107e176fb7a73aed31ebea65d64bfce4af0ccaa4fb647ff5f8065c96b8ecf1bc786d790f68b4d3535da2de39bcb38bfb8e7ae77057f73dc0f23da09bf896389738e7e47b4ffcbed3f82c084d25d67c06f2b87ff1f10c8da77ab6dbb7c7fdcddbbd96563b2dad92506637e8b7d7837eaee3059a277ff9320f67c08c715119c41b176fc65fa71f7d4b97747dd3d217ebb5cf93a651fb907d5ddc77d43ea4abfb8eda877475df12e712e7ddc1b7c4b9c47977f02d712e71cec9f75b261fe379630a7de86bbf743ef016f87dc7e44b62f4abd7f5b659177dfb9738de011e2a7305ae454b9b97361f976fd9b7499c7707df9ce3dcced33d447cafb9af7bbc51b15888fbcbf7d377542c7675df51b1d8d57d4b9c4b9c73f2bd3f7edfe13dc4ed41fb29d73dc4fdc0f3b6072d3cd5333c773a60d569bb55a72494c173b9031eea59027e69dd347f00b643b1316b1e7ab702be0f91cabdc884916c6ffbe509dbd78b41fb2957fb3a003c1eda4195a77a86edeba055a7171dba53198cd5831eeae96a3b347f10b643b1316b1e7a1718b126a0dc2b4c18c9b6df2f4fd8be5e09da4fb9dad741e0f1d1ff78aa67d8be0e59757ac5a13b95c1583de4a19eaeb643f387603b1c2a3266cd4363c7893501e55e65c248b6037e796a1250679a72b5af43c0e3a3fff154cfb07d1db6eaf4aa43772a83b17ad8433d5d6d87e6c99f300b7314b3e65963f2c49a80726b983092eda0579e9a5402ea4c53ae7eec30f01c8a9d27db8f79d03decc78e58755a13dcab3b95c1583de2a19eaeb643f347603be4c3bca708994567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da39845e7ce316b9e16935f637e1350ae850923d90ef9e5099fdf6909da4f25d67c06f24780e7b0077d3cd5331cf77ed4aa538b43772a83edeba8877abada0ecd1f85ed900ff39e2264169d3bc7ac79d69a3cb126a0dc5a268c643bec9727ecc7d606eda75cfdd851e0f1d1cf7baa67d88f1db3eab4d6a13b95c1f675cc433d5d6d87e68fc17610666176316b9e75264fac0928b78e0923d98e78e54987cf21ae0bda4fb9fab163c0e3a39ff7a47bd88f1db7eab4cea13b95c1583deea19eaeb643f3c7613be4c3bca708994567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974ee3e3a6b9ef5264fac0928b79e0923d98e7ae5a90eef3bac0fda4fb9ee3b1c071e1ff7653ce91ede773861d569bd43772a83edeb84877abada0ecd9f80edd0d599f71421b3c4466198253684398a59624398a398253684398a59624398a398253684398a59624398a398253684398a59624398a398253684398a59624398a398253684398a59624398a398253684398a59624398a39839c486e6d960f2c49a80721b983092ed985f9ef0bd071b82f653ae713b2780e7b8077d3cd5331cb773d2aad30687ee5406dbd7490ff574b51d9a3f09db419885d9c5ac79369a3cb126a0dc46268c643bee9727ecc73606eda75cfdd849e0f1d1cf7baa67d88f9db2eab4d1a13b95c1583de5a19eaeb643f3a7603b08b330bb9835cf269327d60494dbc484916c27fcf284fdd8a6a0fd94ab1f3b053c3efa794ff50cfbb1d3569d363974a73218aba73dd4d3d57668fe346c0761166617b3e6d96cf2c49a80729b993092eda45f9e7402ea4c53ae7eec34f0f8e8e73dd533ecc7ce5875daecd09dca60ac9ef1504f57dba1f933b01d8a8d59f36c3179624d40b92d4c18c976ca2f4fd8beb604eda75cedeb0cf0f8e87f3cd5336c5f67ad3a6d71e84e653056cf7aa8a7abedd0fc59d80ec5c6ac795e3379624d40b9d7983092edb45f9eb07dbd16b49f72b5afb3c0e3a3fff154cfb07d9db3eaf49a43772a83b17ace433d5d6d87e6cfc176283666cdb3d5e4893501e5b6326124db19cf3c09a8334db9dad739e0f1d1ff78aa67d8bece5b75daead09dcaec837a9ef7504f57dba1f9f3c043d33ce0f1159781c51338f4a16906339ec9cc784631e34933e399c08ca79919cf72663c4dcc787a30e359c48c6726339e29cc782632e3a966c6b39219cf38663cf399f1ec65c6338b194f86194f3d339ee1cc786a98f1ac62c6b39419cf62663c0b98f18c65c6339b19cf08663c5399f13430e3a965c653c18c27c98c6734339e39cc78a631e36964c653c78c27c58c6724339ebecc78fa31e359c68c6709339e85cc78f631e399cb8c673a339e49cc785633e319cf8ca78a194f7f663c0398f18c61c653c2802711dc3b762501ffdf0bb6f326bf156c174cfe2cd84a1d3ee8dee379b095993cada3b74a8386ddbb6ed4c9d7b812f4958179f257011c1798f08c61c63380194f7f663c55cc78c633e359cd8c6712339ee9cc78e632e3d9c78c6721339e25cc789631e3e9c78ca72f339e91cc7852cc78ea98f13432e399c68c670e339ed1cc7892cc782a98f1d432e36960c6339519cf08663cb399f18c65c6b38019cf62663c4b99f1ac62c653c38c6738339e7a663c19663cb398f1ec65c6339f19cf38663c2b99f15433e399c88c670a339e99cc781631e3e9c18ca78919cf72663ccdcc782630e34933e319c58c6732339e19cc78e63978f67ae249066d5306e6f732f0ade7eb41173d25e0fff81ce13e4f8c7b2d469adf078cc8eb5bb37e164f3f4bb3fbe95bd79fae5df635bfb8bdf0391e0edbab5f01341b60f10cb034bb9fbeb516746f89c6d8e0f6c2e730386c2f1c87e8a17fae49583c7a2ab1e633903fef591f4ff54ce138c58f625cafd6eaa2a5d55e4bab24943907fa5df4a05f4970ef7baf689efc09b33047316b1eba77e11a3fbb800923d9f039914bf1f3d4242c1e3de5ea1f2f79d6c7533dc37eec72e0d6fd12e84e6530562f7ba86709f8a575d3fc6587efca205e2ddeed8016ef3a78de2db016e42f5fe67345c8cc4167cdb3d0e4893501e516326124db45e0b9123b4f3a95b078f494ab7fbce2591f3ff5ccf6095703b7ee5740772a83edebaa877a96805f5a37cd5f85ed20ccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccbc99350f8d6126d604945bc484916cef02cf7bb1f364ef3b208f9e72dd7778cfb33e7eea99bdef702d70ebfe1ee84e653056af79a86709f8a575d3fc35d80ec22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc2cc9b59f32c3679624d40b9c54c18c9761578aec7cf5393b078f494ebbec375cffa78aa6778dfe146e0d6fd3ae84e6530566f78a86709f8a575d3fc0dd80ec22ccc2e66cd43ef9027d604945bc284916cd780e766ec3cd9fba7c8a3a75cfdd84dcffaf8a967b61f7b3f70eb7e1374a73218abef7ba86709f8a575d3fcfbb01df261be5c84cca2b3e81cc52c3a8bce51cca2b3e81cc52c3a8bce51cca2b3e81cc52c3a8bce51cca2b3e81cc52c3a8bce51cca2b3e81cc52c3a771f9d350f7d5b81581350ae890923d96e00cfadd879aa53098b474fb9ee3bdcf2ac8f9f7a66ef3bdc0edcbadf02dda90cb6afdb1eea59027e69dd347f1bb6435767be5c84cc121b856196d810e62866890d618e6296d810e62866890d618e6296d810e62866890d618e6296d810e62866890d618e6296d810e62866890d618e6296d810e62866890d618e6296d810e62866890d618e62e6101b9a67a9c9136b02ca2d65c248b6f781e783f8796a12168f9e728ddbf9c0b33e9eea198edbb913b875ff0074a732d8beee78a86709f8a575d3fc1dd80ec22ccc2e66cdb3cce4893501e596316124db6de0f9307e9e74c2e2d153ae7eec43cffa78aa67d88fdd0ddcba7f08ba53198cd5bb1eea59027e69dd347f17b6c3dd2263d63ccb4d9e5813506e391346b2dd011e0f7117f2242d1e9aff90816f3ddf6cf215e617b7573330def5c498cff64a1640b37e164f3f4bb3fbe95bd77fa5c9f735bfb8bd5602e35d4f8cf96caf7e05d06c80c533c0d2ec7efad65aac32f9fee617b7d72a60bceb89319fed35c02f4f3a61f1e829d7f1c65de0f94cfc3ce179dcdd3c783e033c9f8e9fa7ca533d537abd3f06ec71ad576bf5594babbb96564928830c9ff5a05f09f8a575d33cf91366618e62c6bef0aef94d40b90f993092edd3c0e3a3dfd0751f6dd645ebefa9d23707b4f9f5717d0daf2df432eb250ef2570665fef79036b66f1bb60af83f6d375d9fdb96cdd3336f55aeebbc344ffe2a02f7b9beaf6ba851d71e6e03cf6d8766ef3b34bbe989d17ea695e66f02a3eb3aafafe745a3ae3be3f3a21f5a3a72d30c9f03be053cbe8e8ba3e2cc779bcbf7f8f29683e7a3f87852181be8cb47ac62dfd691babbda4e8c75afc27b58e8cb431b08f75363ccba68fd7a5ff05f03bc6a5e83fd03eda7c658752e83327f0efba91e0f66f3aefd546970ef75e14a63a732f4ff8f8cdd5e4765707faf2d5540bedefc628c924df6bfedef61d65b3a72d30cdb35ded7f4701c98f39a18f9c36dd8e0d0ac818166d8160a116751d77c30ce1a2c1db9698671c6e15a1e6ec34687668d0c34e3d89f355a3a72d3ccd59fa18e0b1cdc0b187073ec5316583a72d3ccd5a7a08e0b1ddc0b1970736cd70b2d1db969f671ed7a91837b11036e8eed7a91a52337cd3eae5d2f76702f66c0cdb15d2fb674e4a6d9c7b5eb250eee250cb839b6eb25968edc34fbb876dde4e06e62c0cdb15d37593a72d3ece3daf55207f75206dc1cdbf5524b476e9a7d5cbb5ee6e05ec6809b63bb5e66e9c84db38f6bd7cb1ddccb1970736cd7aeb1b59c342bf435bb7cee41343b346b66a019c73ea5d9d2919b669cef41ac7468b69281661cfbb395968edc34e37c0f629543b3550c34e3d89fadb274e4a659547fc6ed99aff8f5c97e7bb7b363767cc494a73848791a67138ec1b6bf69608f39c26f1a781e8b9373cc1cf91366611666611666611666611666611666611666611666611666611666611666feccf8be3abcbf42e53e60c24836bc27e5e33abfaefb58b32e5abf7ede73e5409f7ed3edbeb14bcfa2da1c6550e6dce036b6970c1b3eff8ff764dfb76cf2fc7f9bbf0a873e5ab31b0ecdae7b62bc6131d2fc756024fdf09bd6373cf1d87d98ed1bfb8cf7996a9604db4de0f135c6242ace5ce35b2a63f39d5ee7a7eda453fafd747d82b6fec46e23a8a98f38ccf71dcdd82e7cdd57f650cfb0dfbf6ed5e99655a72494190ef5bc9f6d8ba652e0f1d51f05164fe0d087a679cc786630e399cc8c6702339e6798f1a499f13cce8c6714339e66663ccb99f13cca8ca78919cf83cc787a30e359c48ca70f339e99cc78a630e399c88ce759663cd5cc789e60c6f33c339e95cc780633e319c88c671c339e0798f19431e399cf8c6716339e0c339e7a663cc399f1d430e3a964c6b38a19cf10663c0f31e359ca8c27c18ce70e339ec5cc781630e319cb8ca727339ed9cc784630e399ca8ca78119cf73cc786a99f13cc98c6728339e8799f15430e34932e3e9c58c6734339e39cc78a631e36964c653c78ce729663c29663cc398f1f81eb7972fcf23cc784632e359c68ca72f339e7ecc789630e3e9cd8c6721339eb9cc78a633e399c48c6735339ef1cc789e66c653c58ce731663c8398f18c61c6d39f19cf00663ce5cc784a18f024827b9f15c2e7d36e818d9e69b90db652c7fa681c2c95d7c7554f0cbb77dda58e75df7030a04ed7a02e19934f7db2a9dd733c2566bd344ffe2a80e306139e72663c0398f1f467c6338619cf20663c8f31e3a962c6f334339ef1cc785633e399c48c673a339eb9cc781632e3e9cd8c6709339e7ecc78fa32e359c68c6724339e4798f1dc64c6338c194f8a19cf53cc78ea98f13432e399c68c670e339ed1cc787a31e34932e3a960c6f330339ea1cc789e64c653cb8ce739663c0dcc78a632e319c18c6736339e9ecc78c632e359c08c6731339e3bcc7812cc789632e3798819cf10663cab98f15432e3a961c6339c194f3d339e0c339e59cc78e633e32963c6f300339e71cc780632e319cc8c6725339ee799f13cc18ca79a19cfb3cc782632e399c28c6726339e3ecc781631e3e9c18ce741663c4dcc781e65c6b39c194f33339e51cc781e67c69366c6f30c339e09cc782633e399c18c671e339e528b07ffafaf35d078aeeb60a3ff7fcedc2cef67ea71ddf217433d5251df9ef1a5194e19872651df9ee1c0338319cf64663c1398f13cc38c27cd8ce771663ca398f13433e359ce8ce751663c4dcc781e64c6d38319cf22663c7d98f1cc64c6338519cf44663ccf32e3a966c6f304339ee799f1ac64c6339819cf40663ce398f13cc08ca78c19cf7c663cb398f16498f1d433e3a961c653c98c6715339e21cc781e62c6b394194f8219cf1d663c8b99f12c60c63396194f4f663cb399f18c60c63395194f03339ee798f1d432e3799219cf50663c0f33e3a960c69364c6d38b19cf68663c7398f14c63c6d3c88ca78e19cf53cc7852cc788631e3b9c98ce711663c2399f12c63c6d397194f3f663c4b98f1f466c6b39019cf5c663cd399f14c62c6b39a19cf78663c4f33e3a962c6f318339e41cc78c630e3e9cf8c6700339e72663c250c78a2bef542ffef01b6f74cfe0ed8ae9afc4db05d31f9eb607bd7612b75b090bff7c046e34aae828deeb55c011b5dbf225ffa78eed961f7b2963aead4c3c17ad551a7f71ccbe276a4653241bcdb117d65609efce1b768de63c253ce8c6700339efecc78c630e319c48ce731663c55cc789e66c6339e19cf6a663c9398f14c67c6339719cf42663cbd99f12c61c6d38f194f5f663ccb98f18c64c6f308339e9bcc788631e34931e3798a194f1d339e46663cd398f1cc61c6339a194f2f663c49663c15cc781e66c6339419cf93cc786a99f13cc78ca78119cf54663c2398f1cc66c6d39319cf58663c0b98f12c66c67387194f8219cf52663c0f31e319c28c6715339e4a663c35cc78ea99f16498f1cc62c6339f194f19339e0798f18c63c6339019cf60663c2b99f13ccf8ce709663cd5cc789e65c6339119cf14663c3399f1f461c6b388194f0f663c0f32e36962c6f328339ee5cc789a99f18c62c6f338339e34339e6798f14c60c6339919cf0c663cf398f1945a3c788d3c0536ca57818df269b051be1a6c94af011be56bc146f93ab0517e3cd8283f016c949f0836cad335377c9ea610efed245fb46e9abf068c343e0db709e51b80fbb265d3dc973c715fb6b869fe1230521d2e838df28dc07dd1b269ee0b9eb82f5adc347f0118a90e17c146f94960a3fc64b0517e0ad8a6803fb2517e2ad8283f0d6c949f0e36cacf001be567828df2b3c046f9d960a3fc1cb0517e2ed8283f0f6c949f0f36ca2f30bf7a1b9fb76c7a1b9f33f94c10ef36265fb46e9a3f078cb4bdcf838df20b81fbac65d3dc673c719fb5b869fe0c30521dce828df28b80fbb465d3dca73c719fb6b869fe1430521d4e838df28b81fba465d3dc273c719fb4b869fe0430521d4e828df24b80fbb865d3dcc73c711fb7b869fe1830521d8e838df24dc07dd4b269ee239eb88f5adc347f0418a90e47c146f9a5c07dd8b269ee439eb80f5bdc347f0818a90e87c146f965c07dd0b269ee039eb80f5adc347f00180f99fc41b0511edfb3b1da13e34d8bf1a6e5bb02f2788cb6dfb269c6773c31eeb71869fe1d60244df703cf7e4f3c072c1edb77127459cd54b324d80e008fafb670cde2b966f9c66d88c7d4f596cde776adb718697e3f30da6da11cf271f3acb6786cdf49d0e50053cd5c6dc1677f76c9e2b964f9c66d88e7400d96cde7766db01869be1e18edb6500ef9b879a2fa33f297045d5633d5ccd516ca812d6e9e0b16cf054b0b7c0e08cfc3565b369fdb35aaff680046d2aa106d332acec85f12746964aa59126c85d86f9eb378ce59be711be279f302cbe673bb2eb018691efb5cbb2d94433e6e9e468bc7f69d045d0e30d5ccd516ca812d6e9e3316cf194b8b0ac8e3758e0396cde7768dea3f160023695588b6191567e42f09ba2c64aa59126c1867bef69ba72c9e53966fdc86785d6a9165f3b95d17598c348f7daedd16ca211f37cf428bc7f69d045d1a996ae66a0be5c01637cf098be784a54505e4f13a62a365f3b95da3fa8f45c0485a15a26d46c519f94b822e8b996a86e72a1867bef69bc72c9e63966fdc8678dd778965f3b95d97588c348f7daedd16ca211f37cf628bc7f69d045d1632d5ccd516ca812d6e9e2316cf114b0b1ce786d7e9175a369fdb35aaff58028ca45521da66549c91bf24e8d2c454333c57c138f3b5df3c64f11cb27ce336c4fb2a4b2d9bcfedbad462a479ec73edb6500ef9b8799a2c1edb77127459cc5433575ba800b6e5c0bdd8b2f9d436aa0d2f05c6264b5b9fed236a5b2f061d4997654c35c3f305d7fea319b8975b369fda2eb7b8975bdabae2b11cf271f32cb3786cdf49d0a589a966ae78c431d62b81bbc9b2f9d436aa1d2d07c66596b63e8fafa2b67513e848ba3433d50c8f9b9739745c05dc2b2d9b4f6d575adc2b2d6d5df1580ef9b8799a2d1edb77127459c65433573c96035bdc3c51f7215615c077d4b5e942f88eba5e5908df51f7180be13beaba46217c479deb16c277d475c342f83e64f93e5440df51e3c00ae13b6a6c50217c478d17e9eaed5bfaf3c2f7e7f7b36fe9aefdf921cbf7a102fa963e35ba4f5d16bfef74027cd05462cd67208fd7dd9a3d68e1a99e293c37fa28c6f5bace31edf3203cc7c473b5fb755e5e8ccc181725f1f94ee1f508fc961a5d1f48818dae0f55818dae0fa6c146d787abc146f7266ac046f7c56ac146f764e7818dc603cc071b8d45b900361a07751e6c3406ef1cd868fce759b0d1d8e33360a371efa7c1f6b6c99f02db5b267f126cfb4cfe04d8de34f9e360db6bf2c7c0b6c7e48f826db7c91f015babc91f06db2e933f04b69d268fd7723e65f2785dea0d93c7eb7b3b4cbe0e6cdb4d7e3cd85e37f90960db66f213c1b6d5e4f11b7daf99fc55b06d3179fc1edf6693bf0cb64d267f096c1b4dfe22d83698fc24b0ad37f9c9605b67f253c0b6d6e433606b31f9a9605b63f2d3c0f6aac94f07db2b263f036c2f9bfc4cb0bd64f2b3c0f6a2c9cf06db0a939f03b6174c7e2ed83e34f98360bb6bf278cdaad4e4f1fa34bd2f06efe9d03bebf0fe14bdc717ef27d3bbe9f1be3c7daf07c7d1949b3c8e47a2f7f8e0f8417a371d8ec34c983c8e9bae30791c7f9e34797c5e84be4583cf2ad1f772f683adbfc9bf0336fa8edfdb607bd0e4df021bbd2f6e1fd81e32f937c1f6b0c9ef051b7d7b650fd8e87b74bbc146ef886905db6093df05b62126bf136cf4fef74f816d98c9bf0136faeedc0eb03d6ef2dbc146ef377b1d6c9526bf0d6c4f9afc56b03d65f2af818dbee7b6056cf4ce8ecd60a3f7866d02db7093df08367a7ffa06b08d30f9f560a3eff0ac031bbddb652dd89e37f916b08d36f935601b63f2af828dde35fd0ad8e87d872f838df6c52f818df6c52f828df6c52bc046fbe217c046fbe20fc146fbe2bbe657b73fdd2e6f99f94c10df718ff6773b683fe53af62606e489f35836093ce8eb66ec754f87c7cdef9b75959af5521cdc04dfd763f79d3d66bf61d6d5d3acf7bae5bb0ccabc3fb86ddb5c83ff67a00eb41c96a175d3fc5858f69ab5ee7ea6be373cd5f7bac544dc378089cadc19dc5676a1e92ccb619918d9c2f34f8ab50034c429037962f0a3553a3c1f783f0f9e1bc0137f3bc99e0ffb88096c5b719f0fdbd751ec584b4299eba09faff7d5dcb078689efc09b3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300b337f66cd43f713883501e56e3161241bdeebf2719d1fefa5e23daf71705fc7f7fdbd5e66bde3ac3a9741997786b4b1559b7c05fc9fb65bd4b6f4709f30e7b6247ff8ae7bbc17e4615be61c1f7b03786e3834bbe6d0ec3d4f8c769f41f3f82d06d2ef1af0f86a8fd72d1edb37f61937986a16d5cffa8afba83873f9ae8ccd777a9d9fb116e9941ee3a4c772d8fd09d501f767b72d1bde07af80653f30bf09d0e703d0e77ef601f6b804eca76e0363fcf1936e770f98fa7e1afb40fe700cc40aa32d8d8188bf1f48a77cb6156aa3f6d80aac2b957909f673af983c8e03ba05eb7addf17f9a728d49c03ef54afc750eb7efbb665db47daf387c5f06d6987c57a1ef1293c80fd9cb20bf6d485b59fbfb2fa435b1e3376a90dd5eeeb6b55c12ca5c75d43f13c45bff2b16cf158b59c74e0bc4d9eb70ace7ab4fba1aa1d158d088cae031af873145ce3e9238c89f2e43dbbfb755068f4fa9cc6ee8a3745da89fa77ae23826dc07f83a5eb865d58fe6f178c1754c63d751c7c7ca816dbcdd7dacdf71e82faec1ff33c1271feb771c62c8755c4aeb1f07eb27aede41f4be85ca9cb6f6a33ece27715bda7ae258422a731efaa12f77e2bcee7e9da3479dd7f9f8a61d9e6bd0946bff8efb181ffd8ba77aa65cfbae6b569d92506638d4d3c3718c73bf45f357c0b7afef18920f3a86ba6e695106653eb4fa8e281df1bac48d82d425ed3c1e1ce7a80b95f99cd54fbdeb81c9e776c3e32cbdde9b8eba52992f40fff7d3703c4fdb09fbd22f3afe4f53aefe80f4d375be187f9dc3ed4bcf19d2f6bde8f07d1e5863f2ddee9d0f74bc4f7ec85e06f9df84fd3795233d486bfc46223d3787ecf672d7ade59250e692a3fe9920defa477dff91fce9d8f93988b32fc2f1beaf7ef3528446e340232a83d756eceb2e51fb7d1f63e973edf76f02a3dd6fe2b18b4fb6f72d36fb7ab9eb7890cad0b2783cf8a7d0cf261c65edfb003ecec53ec9f31d1e74ae419da99f223f63c06eeb4cff739d337ccdda9ff9be1741dcf6b9009e8f10b7bd9fc1f3916f58e7b4f6f9225eeba467e43c1d93d7e07534dac7d8c7e4781ded3bd6f110d989eb8ea32ea401952d85ffd3ff3eb2cae275b75ccbd979d7f9f8875639971f2fcff655a55236bf7dfe85d7bdf1fbdab6aed40e30e6685d765ba165f17cf07f597d925d56f749ff35a04d1fda8ef88ce66dcbe6e79e41b6cdddb6ea6f5fa7d58cf6b5205fd751f2ed4b7d5f67f3d447a7f0da45796ceb9db8d675cde596a55532b8f77a0697fb6cf169915eefbae7e8d2a290633ba2b470dd238e4f8bba75ae7ecea5c57b0e1e5fd777a3b478cfe13b462d36baae89b8b4b8eae0f175be1da5c55587eff8b418efbc5ee5d2e28a83c7d779579416e42f5fe6f71830975bf9787cd784fdfdbb1dd0e25d074ffcd7a4726bf1aec3777c5a54d569df973ba0c565078faffb98515a5c76f88e4f8b0913b5ef4b1dd0e29283e75281b5b8e4f01d635c6cd0be2f76408b8b0e1e0fd71a736a71d1e13bc6e3c3b08d5ce88016171c3c170aacc50587ef18b568d1becf77408bf30e1e5fd741a3b438eff01d9f166b6bb5ef731dd0e29c83e75c81b538e7f01d9f162d13b4efb31dd0e2ac83e76c81b538ebf01de339541817673aa0c51907cf99026b71c6e13b3e2dd687c75aa73ba0c56907cfe9026b71dae13b3e2d52e13ef55407b438e5e03955602d4e397cc71817e1f9e4c90e6871d2c173b2c05a9c74f88e713f12c6c5890e6871c2c173a2c05a9c70f88e4f8b8de1f5a7e31dd0e2b883e77881b538eef01de33597302e8e75408b630e9e6305d6e298c3777c5a5487fbd4a31dd0e2a883e76881b538eaf01d9f161bc27b62473aa0c51107cf91026b71c4e13bc6e3ceb0bf38dc012d0e3b780e17588bc30edf311e7786d72f0e75408b430e9e4305d6e290c3778c7d6778dc79b0035a1c74f01c2cb016071dbe633cee0cb538d0012d0e38787c7de33e4a8b030edf311e7786fb91fd1dd062bf83677f81b5d8eff01d635c847de73b1dd0e21d07cf3b05d6e21d87ef18af6b857de7db1dd0e26d07cfdb05d6e26d87ef18cf47c26b7c6f75408bb71c3c6f15588bb71cbe63bc57141e83efeb8016fb1c3cfb0aacc53ef0edeb791df24163b1465b5a944199db43b3bf34162b4a475a871e3373db51973763af4b765cd9de88babc0975a1329f81ba94833d4e264f750d63668f59178d4dffc051572af31343dbca7edee413b04deec0ba7eddf17f9a4aacf90ce4493f5de7d6f8eb1cc62a7ddb81b66fabc3f74e608dc97715fa2e3189fc90bd0cf2bf36b4ad2c95233d486b62d76d84be6d81ecf672fbace5925066b7a3fe9920defab75a3cad1673f8dc03c419c5919fbe2bcbb43b42a3d1a01195c1317b1f78e2b1c7101207f9d36568fbf7b6cad0b26550e677a18fc271a554cf4470efb8495dbf3d9eea47be68dd344ffe9260bb098c761d757c7c13c67ed2b73de8bb1fda46dff1c0ef6dd559365dd7f19eea4abe68dd343f1e18e9bb227585674c7794b1d662d43c133d689600bf34e5da5f4c049e091e783cd533dc0fd55b751a6fd5290965f0d9c67a0ff52c01bfb46e9aaf07df3eb6396a41fbe41196166550e63bd6f163948eb40e1dbf758ebaf8d2b1d6e2a975f89ee459475a37f589930ae0bbc1f25d63f9d66d1b634c4fb9da760330377a60d6eb9d1cff7ac3b64ddfc3a378263f3550a70c6810579dd07789b56eb29741feff0ebd9781f4a07d27b1eb7684b113b5dc446bb9249499e4a87f26e6fa4fb678701beb491f37fc071c577a680f610c4cb23868be06b49b1ca1dd24d08ecae0beb7ce93768d164fa3e55bf3d0f1553dd8e83885f813f0ff7401b8ed7eafdec14d36fc96a1eb38ab367ec69cc759b5c048b646e069f0a499bdad4758fae031416fab0c2d5b06651e301f40a467b5ecb2baddf52b69ab177d2333c6ef49857d7a2f0f7ae1f73b03d027b034a48918ca83b66f7cc6c9f340d0f60dcf5dad3b76b66cdad0b4217bdb93d0ca2c4cfc2d7154a3146c98efe1b00541fb4f9596818d3e55da136ca5962cf889542a4f9f4ef42117ea41eb2eb338cb81254edff899579a72854e6fe0f111ca3a74e853af267456ecdcd2ba01e3a3a7c5d999d8d1ffeb91a35cd4ba280eca3cd41d9968dd344ffeb43e49937fa365ddd6a93b37ed7e7dc3f6d65d086b372ecc975822d8bfae653048b031d17a7a5ae2f4895f9c1a6cbc365f00fe0260090c4f79fc3c61a0d2b781d7b56cdbb678f7da6d5bd6cddabd7d5deb961ddb51d1de9672516adb4d5e4faeae0ecbdae57b396cf6845f62ee0d36fa127339d8c87f1fb011c703f03f7b4b786913c361fd14c6fa7f65a6c2bd4c8528046937a4fb13dd66f46b53f5ee5f7ffa595f99d39b4fbf2d4e7fba597faaf99120fb29e64783eca796f5d3ddfa485c1f39e84f25eb4f23eb4f215706d94f1d3f15643f65fc4cd0f6a9623d7d1978f5a789f5a186fef4b0fed4f0f341f652e19820fb5a35fd1a0b7dc8aa4f85f5619e3eadd187e0fab04b1f6ee9cb05fad2913e8cd18788faf0471fb2e843707de83dc5683d35c87eaa5c7f9a7c4690fdf4b8fed4b8feb4f89c20fbe9f0792acd0fb29fd55e18643f1fac3fc5ad3ff9ad3fddad3f11ae3ff5ad3f1fae3f0dde1c643f3bbc32c87e6e7e7590fd34b1fe64b1fe94b1fec4b1fef4f19a20fba9e4b541f6d3caeb83eca798f5279af5a79b3707d94f3deb4f406f0db29f8c7e3dc87e625a7f7a5a7f925a7faa5a5f66d7b717f465777d495b5f72d5b73df4ed1e7d3958dfa6d4b76df56d6c7d5b5f0f73d0c33e0e05d961414782ecb0313d8c4e0f2bd4c32cf5b0d353417658b21ea6ad87adeb61fcfab106fd98877eec453f06a41f8bd28f89e9c7e6f4a38ffa5142fd68ac7e54583f3aad2ff5eac7cef5ed3f7d89575feed69781f525e9bb2a7d5aa5cfa8f4632a7d56a5cfa9f4e32afd844a3fa9d2e755fa824a3fa5d24fabf4332afdac4a3fa7d2cfabf40b2afda24abfa4d22fabf42b2afdaa4abfa6d2afabf41b2afda64a5f54e9b754fa6d957e47a52fa9f4bb2afd9e4abfafd21f04d9f8fb4395fe48a53f56e94f54fa5395fe4ca53f57e92f54fa8a4a7fa9d25fa9f4d72afd8d4a5f55e96b2afdad4a7fa7d2dfabf47595bea1d23755fa0795bea5d23faaf46d95bea3d23fa9f4cf2afd8b4adf55e97b2afdab4affa6d2f755fa814affaed20f83b6cf8d6367d1d7f4304f9af996d6d60dafbfd15ad9baa3f2f5dddb5ab7bcb16d5fe5de2dad9b2b77ecd9b073e3b61d7b71e1bf300b0f36f35377ee6cd957b965fbfa0d6f56eed8dd5ab96363e5da1dbbb7af6fb773fc0fb3d0b07b3db6ac5f1fedecff7f12d28f3ae93469fa3efae2fcdcdc75ebdfa313823cd29985ea7b74ae42d5668f43a75d4bb3c77895bbb6ed68ad4c556e577fd5ce74c7de0debc756e2ff76299177b556ee6a6dd9d95ab971e78ed72babc6e27aabfa74a212eff6f10373d350742a549a8774a226ad433ab7398e0df904a4a73ae9f44e676af8d9ce2cf4279d24fc6ab42cbb76af6dddd9b2ae357ae1af7f9285bfdd996afecf4e56f3d6d04e38fb746716fad2d0ce117ebb33cefa0cebb8b3e0bf0114e4d33b24420500", + "bytecode": "0x1f8b08000000000000ffed9d0774154796f75b4280f0d3b3088e045b3860c004e9492449c023679325030e801019e3210c382272c618b0c946e0999d4db339cfe69c739cd9303bb333bb3b3b73ce37bb5ffecef1f9aafad51dfd55543ff4e4aec77dd2ed734aaffaaabaefaffe7dab3a55777f270882a22033f550e9e9e0ee89fe9f36bf959f6eaa8a715d953e398b0a84b3b840387b140867498170f62c10ce5e05c2d9bb40384b0b84b34f8c9c9aad38683fc5cdfb80075de3664c1498a66505a069b2c0347db000342d0f0aa38fea5b209cfd0a84b37f81700e2810ce870a84f3e102e17ca440381f2d10cec70a84f3f102e11c58209c830a84737081700e2910ce270a84f3c902e1ac2810cea105c2f95481703e5d209ccf1408e7b331728e00ce61e6f739f33bdcfc529991e6f779f33bcafc8e36752c31f363541aabd954aab2fe9752a95aa51a95c659ff1bafd2049526aa34c9fcafc2fcaf56a53a95ea559aacd21495a61a1da6a9345da5192acd5469964ab3559aa3d25c95e6a9345fa5052a2d5469914a2fa8b458a5252a2d5569994acb555aa1d24a951a546a54e9459556592cab555aa3d24b2abdacd22b2abdaad25a95d6a9b45ea5269536a8d4acd2469536a9b459a52d2a6d55699b4adb55daa1d26b2aed54e975953ea3d22e9576abb447a5bd2a7d56a57d2aed57e90d4bb337557a4ba5b7557ac7e27c57a5032ab5a87450a5432a1d56e9884a47553aa6d271954ea87452a5532a9d56e98c4a67553aa7d27b2a9d57e97d952ea87451a54b2a7da0d2872a5d56e98a4a5755baa6d275956ea874d3b05043f848a55b2ab5aa745ba53b2a7dacd2e754fabc4adfa7d21754fa7e957e40a51f54e98754fa6195bea8d28fa8f4a32afd984a3faed24fa8f4932afd944a3fadd2cfa8f4b32afd9c4a3fafd22fa8f425957e51a55f52e99755fa15957e55a55f53e9d755fa0d957e53a5df52e9b755fa1d957e57a5df53e9f755fa0395fe50a53fb234ff6395fe44a53f55e9cfccffe83ad89fabf41726ff97e6f7afccef5f9bdfbfb196f95b95feceb27d59a5af58b6bf57e91f4cfe1fcdef3f99dfaf9adf7f36bf5f33bf5f37bfff627ebf617ebf697effd5fcfe9bf9fd77f3fb2df3fb1fe6f7dbe6f73b2add1c9cc997066d533a88a94faa696ed6f75048ec6141fb496bd1c3fc8f7e2b8cbdc4ccd32f69d7d3ccf7b4ecbdcc7c2f6b3da566bed4b2f733f3fd2cfb00333fc0b23f6ce61fb6ec8f9af947c19e08e05aaab16b5b0f632a021bc56131d87a06ed35d1b65eb43ab0f50eda6ba16db41d7b81ad8fb1f506db03c6560ab684b1f521cd542a33b67410574c54aed3eb4dc6bd5e737fe9c1f8799bf47acb3df1f68d9fb759afb79f075e1d1ffdcdbafa42dc0c30b67e607bc8d8fa83ed61631b00b6478ced21b03d6a6c0f83ed31637b046c8f1bdba3601b686c8f816d90b13d0e36d3ed0503c136c4d80681ed09631b0cb6278d6d08d82a8ced09b00d35b627c1f694b155808dc6ae0c05db33c6f614d89e35b6a7c1467dea3360a363be678d4df7133d8b601963a73e2a5c86fa61b00da73e186c23a8ff05db48ea7bc1f63cf826db28e857c836dad8a88fd2ff9b68f2e920ae36910adbc4a4b8d7abd6acd75b17ff7ac3fb71f5419bae69f03309b49a6cf2318ef9a942df4526911fb297407e2e94a572a407ed67885def4f6a4d7e7296e5265acb25a14cada3fee920defad7593c7516734fc8fb89d9ea94c46c87a79c63b601cadab147c73c5d316617008787981d2f31dbe129e798dd0065edd8a3e3deae18b3ab80c343ccaef313b3a94a89d9ccf5af2070c71e9dfb74c598dd041cf1c7ec3889d98e4f39c76c0b94b5638fce7fbb62ccee058ef86376c23a3936e8f09473cc9e83b276ecd1b598ae18b38781c343cc364b3fdbe129e798bd0165edd8a3eb825d3166df078ef8637692a798ad96980d32f73283c01d7b748dba2bc6ec2de0883f669be4fa6cc7a79c63f6e7a0ac1d7b74bfa42bc6ec178123fe98dde0ebfa6c4a623633862308dcb147f7eeba62cc7ec9e4f5bdb1bf34f7c69e00db5f19db9360fb6b187b40b6bf31b6a1502f0f6d60a2b4810e4f39b781bf85b2762c3f65f25db10dfc11707888d97512b31d9e728ed96f42593bf6684c43578cd9af008787986d9298edf09473ccfe77286bc71e8dafe98a314be343f5f1c23f9ae385e160fb27631b01b6af1adb48b0fdb3b13d0fb6af19db28b07dddd84683ed5f8c6d0cd8be616c63c1f64d63ab04dbbf1a5b15d8fecdd85260fb7763ab06dbb78cad066cff616ce3c0f66d631b0fb6ef18db0463d3f7b1684cd5ef1a5b2970a583f8b66d0234a0a9c89a4f43beca2f4f651278d05775fcbeaa75dd5341c7eb5e0d3c351eea9e001f1de1a9019e71f1f384e349c7c7bfde701ba72c4d13e02b05f59ae0a15e45e08bd64df3e42f09366cd3131c8cb1ef9fd57eb4087cd1ba697e2230920dfb18ea73a9fde8be7948511baf87b614ee9fd15f1a38c85f09941937b8adec50c35606ffc7fe769c65f31497615c902f5a37cd93bf32a8cfb8fc33a63aca586331faea238ac017addbf65d06f94abffa64e519cfc477ad27df769f469ad7e6c1f744cb77b5e51bfb4e9ab2eddb2602b387f394aafb719e82c70f1cce536616b595b5cf37a81fcef53c6582b51cc7f39449b02ff4d01ec218a8b53868be1ab4ab8bd0ae16b4a332cf8176befaf149160fcd8f031eeabfb13fabcc334f2503df780c4bdb0dcfa5f038c0d7f6aab41869deb5bd2602a3eb58c5c3f94cd663951a60241b5ec74979d22c6abba698f8f6102b617f443ee8d89cda2ff92b81320b7ab4955d0b7da58f18c178a4a9a3e7bcf16fa754780e5e93030f6e3b0fe755559ee2b112afdf7c12c41b6b76bf54636915758dc7575f9eb278689efc09b3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300b337f66bc9f84e3b6a8dc38268cf6f8375fd7f9c377ec9975e13da0ef7a1d0f96aac4b130340e628455e71228535edcc6f63f603c983d96286a5bfa181b9b6d5b923f1c0f968f7b88d5168fedbbcca10fde9746cd7c8d81b2fb0c9ac7f169a45f25f0f86a8f55168fed1bfb8c1453cda2fa595f630ba2e2cc359eb02236dfa9265ff775f5fbccf43b41edfdd7384bd3f05da3964df7492ff468abb78ffbbcb9de77c6fd06e5e3bc8f9b041ef4157f3f9b6a373ea13868bfafc058f735568bda158d8da8b57c97409921c56ddba60efe9f0eeede3760195a37cd8f8065ebac7597fbab6fd67128b5c04df95e56ddc6033795790af6dd7f66f29ef681a95c9f91c07d72fcfb97cc7891540e3cb8bff371cce2693f5a89f118f778117b1ca4eb7899cae018520fe377b3ee8bc99f300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330f367d63cf63d74bcf75bcd84314f636cc2fb19f42e36bc2ff693c56d7e7ddf03a47b4e23ad3ae333ca013ca3fcb3860dc73ee07ba55cdbd2d7bdb5a86d49feb88d7d2873e8a335ab736856ef89d1ee3368be1e1849bf3ae0f1d51ea3dedde1ea332a996a16d5cffa18bb902dce5ce3142a62f39d192fe2e3be2e8d17b1f75ff67b8d709c04d9f03e78d4fbc2aa2cdbfdee035ce3da288fef5c88bfdf4cb5bb076c8f15247f384ee22b465b1a27117f3f90aaf4b98fa0364a6342528eba5299af16b795fd9ac9e358a16a58d7771dffa729db9804ec53a7c45fe770fb4e35eba2ed3bc5e13b0dac31f9ae42df45d6bac95e02f9ff567c3703e9415a13bb6e23f81eaba8e56aace5925066b2a3fee998eb3fc5e2c16dac271d3bdf8038fb2e1cebf9ea93264768340234a2329edf03eb1ce76b8f55c67eb4b7558696c5f75bfe1fe8a3a2c6a4bbf601be8e17a2f60178bc60ef173a3276bcbb8f07ec63ce437c8c07a4759707edc7e405d6fa47c2fa89ab7710bd6ff9deb938acdfe798f57bbdf7ca75ee47bc38de90caf48773bf99269fcbb9dffd3a8f779dfbe1725175c77e21ee7d23c623b2e0fb22a9cc602b1eeb22b8273a967d326259d2ca7ef7219e97e23142fcef8dccf43753acba509b9a0c75a132cf5aed26fe63a6ccf1a7af7764d2b10ef541558eba529991d0d646997c02b613ee17ea1cffa729dbf127e9a7eb3c2dfe3a87db77ba59176ddf690edf33803526df55e89b8e3fc90fd94b205f0bfb132a477a90d6c45e069cc86e2f576b2d9784326947fdd331d77f9ac533cd62d6b13316e2ac0e9eb5f0d557a703b7462341a3eff9039bebbdc6ae7d8cafe7d1a2f63155c068f79bb89fcce7b372f6f55bd7f10995c163682a330ffad984a3ac7d5d9a968b737c3a3e0f84c785f83c90af73a464d05ecfa4c5e1d377b9e5bb3c8fbefb59befbe5d1b7682e9a73d29cd3377f70df520c8c3ece1bf1fa484718f17d04b45c0f60f4f5cc662a07c66a60c4fd1d317a78bf725567bf2984c73a3d81d1c7f3c6b95eafc6f79be33b0688d1c733dab9bea71a9fdba6e57a03a38fef1ae137943ac2e8fad65129fc7af8ae515567bf0382df3aea038c3ebe0d9208da7fcfe45e8c938091967b00187ddc474a04edafabdd8b11ef57d27209cf8cd9f6ed9ec7fea472bd06918f710551c71ae8dbc3f5ff148e4de88816f57e79b21efba06f0fd7bf422dea838e6b81f7e73cdc1b0ddbf1e41c78f01e222dd71f18a77a629c9203e35460a4e5060063da13e3d41c18d3c048cb3d048c1eae43868ce91c18f17a1dd91f06c6e99e18a7e5c0381d1869b94780d1c735c504f8ed08e30c60a4e51e05c6999e1867e4c038131869b9c780719627c6993930ce02465aee71609ced8971560e8cb38191961b088c733c31cece81710e30d272838071ae27c6393930ce05465a6e3030cef3c4383707c679c048cb0d01c6f99e18e7e5c0381f1869b92780718127c6f939302e00465aee49605ce88971410e8c0b819196ab2800c6a105c0f85401303e5d008ccf1400e3b305c0585a008ccf03e3a2f819c3f3d48539302e029e17e2e709355b9403cf0b7e79c2f7132e72f85a1cbfaf54ae755f0c3c4be2e709b7c5e21c78882109cbbde09731d55946cdb3347e9e50b32539f02c05cd963834f3c098ea2ca3e659163f4fa8d9d21c789681664b1d9a79604c759651f32c8f9f27d46c590e3ccb41b3650ecd3c30a63acba87956c4cf136ab63c079e15a0d97287661e18539d65d43c2be3e709355b9103cf4ad06c8543330f8ca9ce326a9e86f87942cd56e6c0d3009aad7468e68131d55946cdd3183f4fa859430e3c8da0598343330f8ca9ce326a9e17e3e709356bcc81e745d0acd1a1192746e489fb7de38d0e5fab18d49d1890b14f01303e50008c3806c147ff956d0c42a35f7d529dd5c7d7f6ca3606017daff6a4c5aaa0e35aacf6cb93750c02fa5ee3498bd541c7b558033c2f79d022013e3ac2430c4958ae7f01300e2800c6870a80f1e102607ca400181f2d00c6c70a80f1f102601c58008c830a80717001300e2900c6270a80f1c902607cd13363b6f39797bab8efa87395aeee3beabca4abfb96389738ef0ebe25ce25cebb836f897389f3eee05be25ce2bc3bf896389738ef0ebe25ce25cebb836f897389734ebe5ff6e03b013e68ca768d9f1892b0dc8bc2d8a51991a7223e9e4aac3bfa7a8541dd5f71f01479aa3bfa7a9541dd89a1d0185f2e00c6170b805174cc8c41ec0ca3e659eb89e7d51c78d602cf3a4f3c6b73e059073cebe3e709636a5d0e3cc49084e55e2c00c6970b805174141d39318a8edd47476114466114c6fbc158087db8ec6732e72e9d61d43c4df1f3849aadcf81a70934a3e55ef0cb98ea2ca3e6d9103f4fa859530e3c1b40b32687661e18539d65d43ccdf1f3849a6dc881a71934dbe0d0cc0363aab38c9a6763fc3ca166cd39f06c04cd9a1d9a79604c759651f36c8a9f27d46c630e3c9b40b38d0ecd3c30a63acba87936c7cf136ab629079ecda0d92687661e18539d65d43c5be2e70935db9c03cf16d06cb343330f8ca9ce326a9eadf1f3849a6dc981672b68b6c5a19907c654671935cfb6f87942cdb6e6c0b30d34dbead08c2be38b05c0f87201307ad631d55946cdb3dd13cfb61c78b603cf0e4f3cdb73e0d9013cafc5cf13c6d48e1c78882109cbbd58008c2f1700a3e8283a7262141dbb8f8ec2288cc2981be32b05c028db5a18b9327a38bfcafa7cca8e2eee3beaf994aeee3beaf994aeee5be25ce2bc3bf896389738ef0ebe25ce25cebb836f897389f3eee05be25ce2bc3bf896389738ef0ebe25ce25cebb836f897389f3eee05be25ce2bc3bf896389738ef0ebe25ce25cebb836f897389f3eee05be25ce2bc3bf896389738ef0ebe25ce25cebb836f897389f3eee05be25ce2bc3bf896389738ef0ebe25ce25ce39f9de19bfef54aecfb0ee041e1fcfd47aaa67a55eefeb665d9fc4a89fd6ea3396563b2cad9250e675d0ef331ef42b02bfb46e9a277fb9320f63c0ecc977ea41b58e3e507ff2f1b2a587f6bfcb53dda3fafa5d5ddc77545fdfd57d47f5f55dddb7c4b9c47977f02d712e71de1d7c4b9c4b9c73f18df992a0edb89ddeafa4d7b1dbe47b9a792aff0a2c4765e6f5cefc9607d2867cf8963624fb8aeee05be25ce2bc3bf896389738ef0ebe25cef9c539c6c3bc3cf004164f9085271ff70f72e119c38c6706339ec9cc782630e3a962c6339219cf66663ccdcc78d633e3e9c18c673e339e99cc785e62c6338519cf44663c29663c5b99f1ac64c6b38c19cf68663c8b99f1bcca8c6716339ea9cc78d630e399c48ca79a19cf36663c5b98f10c63c6b391194f13339e05cc78d632e399cd8c27cd8ca79619cf70663c35cc78b633e3798e194f23339e06663ccb99f12c61c653c68c27c98c6721339e39cc78a631e3a963c6338e19cf2a663c9b98f16c60c6b38e19cf08663c0f32e32967c6339619cf5c663cd399f1d433e319cf8ca79219cf6a663c2b98f12c65c6d397194f3f663c8b98f18c62c653c4802711dcfd8e9304fc7f27d88aad65f563616f0e6efbff1e632f8665f69a7c0fc7baf7808d9e35dbeb581675da0375499b7ce5a79b429dd0571ae6c95f1970ec65c2338a19cf22663cfd98f1f465c6b39419cf0a663cab99f15432e319cf8ca79e19cf74663c7399f18c65c653ce8ce741663c2398f1ac63c6b38119cf26663cab98f18c63c653c78c671a339e39cc781632e34932e32963c6b38419cf72663c0dcc781a99f13cc78c673b339e1a663cc399f1d432e34933e399cd8c672d339e05cc789a98f16c64c6338c19cf16663cdb98f15433e399c48c670d339ea9cc786631e3799519cf62663ca399f12c63c6b39219cf56663c29663c1399f14c61c6f312339e99cc78e633e3e9c18c673d339e66663c9b99f18c64c653c58c6702339ec9cc786630e319c38c6727339e790e1e0fdfcf0b7968bc2aad9be67732f1ed613b84df0dfcaca73aed33ebea65d64bfce4af04ca2cea93f9d5e3757059e2b2c71763acee038d767baa4bd43b99777771df51ef64eeeabea3dec9dcd57d4b9c4b9c73f2bd2f7edf297c3685a6226b3e0d79dcbff878a6c7533ddbeddbe3fe26f07e4babdd96564928f359d06fbf07fd5cc70b344ffe72651ec68019e3a22288372ede88bf4edffbd630e9fa86a52fd6eb4d4f9a46ed43deece2bea3f6215ddd77d43ea4abfb96389738ef0ebe25ce25cebb836f897389734ebedf32f918cf1b2bd187bef64be7036f81df774cbe2846bf7a5d6f9b75d1b79189e31de0a1323f02d7a2a5cd4b9b8fcbb7ecdb24cebb836fce716ee7e91e22beb7dbd73ddea858ccc7fde5fbe93b2a16bbbaefa858eceabe25ce25ce39f97e377edfe13dc49d41fb29db3dc47781e76d0f5a78aa6778ee74c0aad34eab4e492883e772073cd4b308fcd2ba69fe006c874263d63c34961ddfcf48e55e62c248b6b7fdf284edeba5a0fd94ad7d1d001e0feda0ca533dc3f6d562d5e92587ee540663b5c5433d5d6d87e65b603bb41418b3e6a167e7883501e55e65c248b677fdf284edebd5a0fd94ad7db5008f8ffec7533dc3f675d0aad3ab0edda90cc6ea410ff574b51d9a277f85c8ac79d69a3cb126a0dc5a268c643be097a7260175a6295bfb3a083c2d1ef4f154cfb07d1db2eab436b85b772a83b17ac8433d5d6d87e60fc17610666176316b9e7526bfd6fc26a0dc3a268c646bf1ca535399803ad394ad1f3b043c3efa794fba87fdd861ab4eeb1cba53198cd5c31eeae96a3b347f18b6432eccfb0a905974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974ee1cb3e6596ff2c49a8072eb993092eda05f9ef0f99df541fba9c89a4f43fe30f01cf2a08fa77a86e3de8f58755aefd09dca60fb3ae2a19eaeb643f347603be4c2bcaf009945e7ce316b9e269327d604946b62c248b6437e79c27eac29683f65ebc78e008f8f7ede533dc37eeca855a72687ee5406dbd7510ff574b51d9a3f0adb419885d9c5ac7936983cb126a0dc06268c643bec9527153e87b821683f65ebc78e028f8f7ede93ee613f76ccaad30687ee540663f598877abada0ecd1f83ed900bf3be0264169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d1b9fbe8ac799a4d9e581350ae990923d98e78e5a90eef3b3407eda76cf71d8e018f8ffb329e740fef3b1cb7ead4ecd09dca60fb3aeea19eaeb643f3c7613b7475e67d05c82cb1911f66890d618e6296d810e62866890d618e6296d810e62866890d618e6296d810e62866890d618e6296d810e62866890d618e6296d810e62866890d618e6296d810e62866890d618e6296d810e628660eb1a179369a3cb126a0dc46268c643bea97277cefc1c6a0fd946ddcce71e039e6411f4ff50cc7ed9cb0eab4d1a13b95c1f675c2433d5d6d87e64fc07610666176316b9e4d264fac0928b7890923d98ef9e509fbb14d41fb295b3f7602787cf4f39eea19f66327ad3a6d72e84e6530564f7aa8a7abedd0fc49d80ec22ccc2e66cdb3d9e4893501e536336124db71bf3c613fb639683f65ebc74e028f8f7ede533dc37eec9455a7cd0edda90cc6ea290ff574b51d9a3f05db419885d9c5ac79b6983cb126a0dc16268c643be1972795803ad394ad1f3b053c3efa794ff50cfbb1d3569db63874a73218aba73dd4d3d57668fe346c874263d63c5b4d9e5813506e2b1346b29df4cb13b6afad41fb295bfb3a0d3c3efa1f4ff50cdbd719ab4e5b1dba53198cd5331eeae96a3b347f06b643a1316b9e6d264fac0928b78d0923d94ef9e509dbd7b6a0fd94ad7d9d011e1ffd8fa77a86edebac55a76d0edda90cc6ea590ff574b51d9a3f0bdba1d09835cf769327d60494dbce84916ca73df324a0ce34656b5f6781c747ffe3a99e61fb3a67d569bb43772af326d4f39c877abada0ecd9f031e9ae6018fafb80c2c9ec0a10f4d6398f1cc60c6339919cf04663c55cc784632e3e9c18c673e339e99cc78a630e399c88c27c58c6725339e65cc784633e3799319cf62663cb398f14c65c6b38619cf24663cd5cc788631e359c08c6736339e34339e5a663cc399f1d430e36964c6d3c08c6739339e25cc78ca98f12499f12c64c6338719cf34663c75cc78c631e359c58c6704339e0799f19433e319cb8c673f339eb9cc78a633e3a967c6339e194f25339ed5cc785630e359ca8ca72f339e7ecc781631e319c58ca788014f22b87b2c4d02febf1f6c34e6633bd8de33f933602b76f8a07bc5e7c05662f2b48ede2a1d1d7cf7ba51275fe35cd0571ae6c95f1970bcc7846714339e45cc78fa31e3e9cb8c6729339e15cc785633e3a964c6339e194f3d339ee9cc78e632e3d9cf8c672c339e72663c0f32e319c18c6715339e71cc78ea98f14c63c6338719cf42663c49663c65cc789630e359ce8ca781194f23339e1a663cc399f1d432e34933e399cd8c6701339e61cc78aa99f14c62c6b38619cf54663cb398f12c66c6f326339ed1cc789631e359c98c27c58c6722339e29cc786632e399cf8ca707339e91cc78aa98f14c60c6339919cf0c663c6398f1cc73f0ecf7c4633fe745f3fb19f8d6f3b5a08b9e12f07f7c0eec4d4f8cfb2d469a7f131891d7b766e5164fb9a5d9fdf4adeb4ff72a1e34bfb8bdf0b9070edbab3c0f9af5b378fa599add4fdf5a0bbaf74f636c707be138680edb0bc7697ae89f6b12168f9e8aacf934e4cf79d6c7533d2b71dce42731ae576b75ded26abfa55512ca9c05fdce7bd0af08fcd2ba699efc09b33047316b1eba97eb1acfbb900923d9f03d2aefc7cf5393b078f494ad7f7cdfb33e9eea19f6631702b7eeef83ee540663f582877a16815f5a37cd5f70f8ae08e2d5e26207b4b8e8e0b998672dc85faecc670b909983ce9a87c6ae136b02ca2d62c248b6f3c073297e9e9a84c5a3a76cfde325cffa78aa67d8277c10b875bf04ba53196c5f1f78a86711f8a575d3fc07b01d7261be5080cca273e79835cf629327d604945bcc84916c1781e7c3d8795295098b474fd9fab10f3debe3a79e997eec72e0d6fd43d09dca60fbbaeca19e45e097d64df397613b08b3300bb3300bb3300bb3300bb3300bb3300bb330eb32c22cccc22cccc22cccc2cc9759f3d0b3e7c49a80724b983092ed03e0b9123b4fe6be03f2e829db7d872b9ef5f153cfcc7d87ab815bf72ba03b95c158bdeaa19e45e097d64df357613b08b3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300b336f66cd43dfb020d604945bca84916c9781e75afc3c35098b474fd9ee3b5cf3ac8fa77a86f71dae076eddaf81ee540663f5ba877a16815f5a37cd5f87ed20ccc2ec62d63cf42e44624d40b9654c18c97615786ec4ce93b97f8a3c7acad68fddf0ac8f9f7a66fab19b815bf71ba03b95c158bde9a19e45e097d64df337613be4c27ca100994567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974ee3e3a6b1efa4627b126a0dc72268c64bb0e3c1fc5ce535d99b078f494edbec3479ef5f153cfcc7d875b815bf78f40772a83edeb96877a16815f5a37cddf82edd0d5992f1420b3c4467e98253684398a59624398a398253684398a59624398a398253684398a59624398a398253684398a59624398a398253684398a59624398a398253684398a59624398a398253684398a59624398a39839c486e65961f2c49a80722b983092ed26f0b4c6cf5393b078f4946ddc4eab677d3cd5331cb7733b70ebde0aba53196c5fb73dd4b308fcd2ba699efc09b33047316b9e95264fac0928b7920923d96e01cf9df87952098b474fd9fab13b9ef5f154cfb01ffb3870eb7e0774a73218ab1f7ba86711f8a575d3fcc7b01d0a8d59f334987cabf94d40b906268c64bb0d3c1ee22ee4495a3c347f87816f3ddf68f265e617b757233072d85ec93c68566ef1945b9add4fdfbafeab4cfe41f38bdb6b153072d85ee579d0ac9fc5d3cfd2ec7efad65aac36f9bee617b7d76a60e4b0bdfaf9e549252c1e3d653bdef818783e1f3f4f781ef7710e3c9f079ecfc5cf53e5a99e957abddf07ec71ad576bf5054bab8f2dad92500619bee041bf22f04beba679f227ccc21cc58c7d21b126a0dc1d268c64fb1cf0f8e83774dd479975d1fa7baaf450ff36bfadf1fbadc16b0bbdcc7a8983fc9540999707b5b13d66d8cae0ffb4dd747d6e59364fcfbc55b9aef3d23cf92b0bdce7fabeaea1b65a3cb6ef32873e5ab39b0ecd6e7862b49f69a5f91bc0e8baceebeb79d1a8ebcef8bce81d4b476e9ae173c01f018fafe3e2568ba7d5a1858f3697ebf1e5470e9e4fe2e3a9c4d8405f3e6215fbb68ed4ddd57662ac7b15dec3425f1eda40b89f1a6dd645ebd7fb82dafe5e35afc1fe81f653a3ad3a97409949b09f9a92653f551cdc7d5db8c2d8a90cfdff1363b7d75111dcdf6b4b6590af35bf18a36493fd6ffb7b98b5968edc34c376dd0a3cad9e78a2ae89913fdc86750ecdea1868866d211f7176c7e2b17d27419756a69a619ce17183affeec5ed7f2701bd63b34ab67a019c7feacded2919b66aefe0c755ce8e05ec8809b639fb2d0d2919b66ae3e05755ce4e05ec4809b63bb5e64e9c84db37bb5ebc50eeec50cb839b6ebc5968edc34bb57bb5ee2e05ec2809b63bb5e62e9c84db37bb5eba50eeea50cb839b6eba5968edc34bb57bb5ee6e05ec6809b63bb5e66e9c84db37bb5ebe50eeee50cb839b6ebe5968edc34bb57bb5ee1e05ec1809b63bb5e61e9c84db37bb5eb950eee950cb839b6eb95968edc34bb57bb6e70703730e0e6d8ae1b2c1db96996effb0051f79bc81f6ec34687668d0c34e3d8a7345a3a72d32cdff701ee35061bb7e12a8766ab1868c6b13f5b65e9c84db37cf76751f79bc81f6ec3d50ecd5633d08c637fb6dad2919b6651fd596bfc3c398fb76ff5aa4fe63bcbad39f0e098291f31e5290e2a3d8da90ac7dbdbdfaf68b5b44a06777fd3c2e7d8c3a8f191e44f98855998855998855998855998855998855998855998855998855998855998f933e3f3ccade617df857a870923d9f09e948febfcbaee63ccba68fdfad9de5b03dafcc67fdf225589f75ce9b9e331569d4ba0ccff7dbc8ded63c386f71769bb456dcbfbf56e057c1f05de0bba9ff73b3f726876c3a1d9754f8c769f41f3d781d17effc3fd7ef7c41d4b476e9a45f5b3773cf144c5d91d87ef8ad87ca79a7cddd7d5ef22ec13dcbdffbae3d0d4471ce6faee0b6c17ad1e787cbde742d7e9ba55a75b569d92506618d4f37eb62d9ae6018faffe28b07802873e341533e399c18c670c339ec9cc784a99f14c60c653c58c6724339e6798f13cc18ce731663cfd99f1f461c6d38319cf7c663c3399f14c61c63391194f8a19cfb3cc785632e3799219cf32663c8f33e359cc8c6700339e0798f18c66c653c28ce779663cb398f14c65c6b38619cf24663cd5cc78f2710f34179e61cc782a98f10c64c6f310339e05cc7812cc787a32e399cd8c27cd8ca79619cf70663c35cc781a99f13cc78ca78119cf50663ccb99f10c62c6b38419cfc3cc781632e32963c69364c6d38b19cf1c663cd398f1d431e319c78c6715339ea798f10c66c6f308339e11cc781e64c653ce8c672c339edecc78e632e399ce8ca79e19cf78663c95cc785633e3799a19cf0a663c4398f1dc66c6b39419cf22663c8f32e3e9cb8ca71f339e51cc788a18f02482bb9f5dc2e7e56e818d9eb169055bb1637d342e97caebe3ceb383ef5e77b163dd371c0ca8d335a84bdae42b3fddd4eeb9a222b35e9a277f65c0718309cf28663cfd98f1f465c6f328339e45cc789632e3b9cd8c6708339e15cc789e66c6b39a194f25339ef1cc78ea99f14c67c63397194f6f663c6399f19433e3799019cf08663c8f30e319cc8ce729663cab98f18c63c653c78c671a339e39cc787a31e34932e32963c6b39019cfc3cc789630e319c48c6739339ea1cc781a98f13cc78ca791194f0d339ee1cc786a99f1a499f1cc66c6d393194f8219cf02663c0f31e319c88ca78219cf30663c3799f15433e399c48c670d339ea9cc786631e3799e194f09339ed1cc781e60c6338019cf62663c8f33e359c68ce749663c2b99f13ccb8c27c58c6722339e29cc786632e399cf8ca707339e3ecc78fa33e3798c19cf13cc789e61c63392194f15339e09cc784a99f14c66c6338619cf0c663cc5cc78e6593cf87f7dad81ae2f5e071bfdff3fcdcdfb72538feb96bf18ea5119f56d1e5f9ae194766812f56d1e0e3c3398f18c61c63399194f29339e09cc78aa98f18c64c6f30c339e2798f13cc68ca73f339e3ecc787a30e399cf8c6726339e29cc782632e34931e3799619cf4a663c4f32e359c68ce771663c8b99f10c60c6f300339ed1cc784a98f13ccf8c6716339ea9cc78d630e399c48ca79a19cf4d663c15cc780632e3798819cf02663c09663c3d99f1cc66c69366c653cb8c6738339e1a663c8dcc789e63c6d3c08c6728339ee5cc780631e359c28ce761663c0b99f19431e34932e3e9c58c670e339e69cc78ea98f18c63c6b38a19cf53cc780633e3798419cf08663c0f32e32967c63396194f6f663c7399f14c67c653cf8c673c339e4a663cab99f13ccd8c6705339e21cc786e33e359ca8c6711339e4799f1f465c6d38f19cf28663c450c78a2be8543ffef01b6ab267f1b6c574cfe26d82e9bfc75b07de8b0153b58c8df55b0d138972b60a37b7597c146d78bc8973edebd38f86ed662479d7a3858af38ea74d5b12c6e475a261dc4bb1dd1571ae6c91f7eabe72a139e51cc78fa31e3e9cb8ce751663c8b98f12c65c6739b19cf10663c2b98f13ccd8c6735339e4a663ce399f1d433e399ce8c672e339edecc78c632e32967c6f320339e11cc781e61c6339819cf53cc785631e319c78ca78e19cf34663c7398f1f462c69364c653c68c6721339e8799f12c61c6338819cf72663c4399f13430e3798e194f23339e1a663cc399f1d432e34933e399cd8ca727339e04339e05cc781e62c63390194f05339e9bcc78aa99f14c62c6b38619cf54663cb398f13ccf8ca78419cf68663c0f30e319c08c6731339ec799f12c63c6f324339e95cc789e65c69362c6339119cf14663c3399f1cc67c6d383194f1f663cfd99f13cc68ce709663ccf30e319c98ca78a19cf04663ca5cc782633e319c38c6706339e62663cf32c1ebca6580936ca57818df229b051be1a6c94af011be5c7818df2e3c146f90960a3fc44b0517e12d8284ff78cf0f99e7cbc178e7cd1ba69fe1a30de3679dc2694af03ee0f2c9be6bee489fb038b9be62f0123d5e103b051be1eb82f5a36cd7dc113f7458b9be62f0023d5e122d8283f196c949f0236ca4f05db54f04736ca4f031be5a7838df233c046f99960a3fc2cb0517e36d8283f076c949f0b36cacf031be5e7838df20bc046f985e6576fe3f72d9bdec6e74d3e1dc4bb8dc917ad9be6cf03236deff7c146f945c0fd9e65d3dce73c71bf6771d3fc3960a43abc0736ca2f06eeb3964d739ff1c47dd6e2a6f933c04875380b36ca2f01eed3964d739ff2c47ddae2a6f953c04875380d36ca2f05ee93964d739ff0c47dd2e2a6f913c04875380936ca2f03eee3964d731ff3c47ddce2a6f963c04875380e36ca2f07eea3964d731ff1c47dd4e2a6f923c04875380a36caaf00eec3964d731ff2c47dd8e2a6f943c04875380c36caaf04ee83964d73b778e23e6871d37c0b30521d0e828df2f83efa359e186f5b8cb72ddf781f178f2d0f5836cdf8ae27c6031623cdbf0b8c2d267f00780e78e269b1786cdf49d0650d53cd92606b019e5a4f3cd72c9e6b961638de0dcf05d658369fdb758dc548f30780b1c5e4f3d1365b2c1edb771274a965aa59126c2dc053e789e792c573c9d2029f73c173b75acbe673bbd65a8c765f8171867d85afb6d962f1d8be93a04b1d53cdb0cf6d019e7a4f3c172c9e0b96166590c7f3c73acbe673bbd6598c765f8171867d85afb6d962f1d8be93a04b3d53cdb0cf6d019e164f3ce72d9ef3966fdc8678bebfd0b2f9dcae0b2d469ac73ed76e0ba5908f9ba7dee2b17d27419716a69ab9da82cffeec9cc573cef28ddb10afcf2cb26c3eb7eb228b91e61702638bc9e3365ce489a7c5e2b17d2741977aa69ab9da4229b0c5cd73c6e239636981e3f6f07a5abd65f3b95da3fa8f45c0d862f2f9689b2d168fed3b09ba2c66aa191e43b6004f8b279e5316cf29cb376e43bcfeb9c4b2f9dcae4b2c469ac73ed76e0ba5908f9b67b1c563fb4e822e2d4c3573b5059ffdd9098be784e51bb7215eaf5e6ad97c6ed7a51623cd2f01c61693c76db8d4134f8bc563fb4e822e8b996ae66a0b3efbb36316cf31cb376e43bcbfb0ccb2f9dcaecb2c469a5f0a8c765b28857cdc3c51fd19f94b822e2d4c3573b58552608b9be788c573c4d2a20cf2783fa8c5b2f9dcae2d1623cd2f0346d2aa05785a3cf144c519f94b822ecb996a96045b3ef69b872c9e43966fdc8678ff6e8565f3b95d57588c34df028c765b28857cdc3ccb2d1edb77127459cc5433575b401d1b807ba565f3a9ed4a8b9be65700e362878e2b3df144f5292b41c7064b476e9ab9e211756c04ee06cbe653db068bbbc1d2d6158fa5908f9b27aa5d37808e8d968edc3473c56319b0ad02eec596cda7b651eda80118975bdafadcef456debc5a023e9d2c85433bcae89db9ad8560377a365f3a96da3c56db719dcd6d8661a3df1446deb46d0917459c55433ec7bf07cc8d7f947d4fdae7cf88eba07920fdf51d7c5f3e13bea5a693e7c47dd47cf87efa86b2af9f07daff36c9fbea3ae59e6c377d4795f3e7cdfb67cdfcea3efa8f16ff9f01d3526aaabb76fd99774af7dc9fdecd7baebbe44fa739efdf9eaf87da71241fb731a3d1559f369c8e3f9cb2a0f5a78aa67259e137e12e37a5de7d6cb2dadf0dc1acf517d9dffadb678689efc152233c645517cbe2bf13a0c7edf91ae8b54828dae8b55818dae8ba6c0d662f2d560a37b323560a3fb81e3c046f7a2e7838dc6412c001b8dc1b900361afff53ed868ece179b0d1b8d7f7c04663aecf818dc6fb9f051b3d6b72066cef98fc69b0bd6df2a7c0f696c99f04db9b267f026c6f98fc71b0ed37f96360db67f247c1f659933f02b6bd267f186c7b4cfe10d8769b3c5e7bdf65f2782de733268fd7f25e37f9f160db69f213c0f69ac94f04db0e939f04b6ed267f1b6cdb4cfe0ad8b69a3c7e5f748bc97f00b6cd267f096c9b4cfe22d8369afc64b0359bfc14b06d30f9a9606b32f934d8d69bfc34b0ad33f9e9605b6bf233c0f6aac9cf04db2b263f0b6c2f9bfc6cb0bd64f273c0f6a2c9cf05db0b263f0f6c1f9bfc41b07dcee4f13a68b1c9e3f57b7adf14def3a2775ee23d4f7aaf34de6fa76f7fb4808dbeaf85e38c4a4d1ec7b8d17ba5707c25bd4b12c7a9264c1ec79597993c3ed340dffdc06743e8db51f85c52b9c9e33371f4cdcf0360a3ef92be0b367af7d43b601b60f26f838ddee9fc16d8e83b176f828dbeddf406d8e87b9ffbc146ef98da07b6c74dfeb3601b68f27bc136c8e4f7808dbed1b41b6cf49dc85d60a377497d066cf47ec4d7c15661f23bc136d4e45f03db5326bf036cf4fdc5ed60a377466d031bbd77702bd88699fc16b0d1f70e36836db8c96f021b7dc76b23d8e8dd50cd607bdee437806d94c937816db4c9af071bbd9b671dd8c69afc5ab0d13efb55b0d13efb15b0d13efb65b0d13efb25b0d13efb45b0d13efb05b051dfff31d8a8efa7fe43b753dd7e6f99f97410df7194f6d71ab49fb21dcb1303f2c4796c9c041ef47533f6baa7c2e3f08fccba8acd7a295e6e82efebb1fbce9c03dc30ebea69d67bddf25d02654a07b66d9b6bf0ff34d48196c332b46e9a1f03cb5eb3d65d6eea7bc3537daf5b4cc47d0398a84cd9c0b6b25f32f952582646b6f07c96622d000d714a439e18fc68950acf2f3eca81e706f0c4df4e32e7d73e6202db56dce7d7f675193bd69250e63ae8e7ebbd58372c1e9a277fc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc2cc9f59f3d0fd04624d40b95b4c18c986f7ba7c5ce7c77bae78cfebe6c036bfbeefeff532eb1d6bd5b904ca7c6b601bdb6d932f83ffd3768bda961eee1366dd96dfbb5706f5c17b411eb665c873d3e2b17d9739f4d19a5d736876d513a3dd67d03c7ef385f4bb063cbedae3758bc7f68d7dc60da69a45f5b33ec625648b33d718848ad87ca79a7cddd7d563a1f4580e7bff75cbd214c740e03df32f419f44cbdc31bf09d0e70ee8733ffb007b5c02f653adc0187fbf996a770f98fafe3196a63806e2b78cb6340622fe7e2055e9731f416dd41e5b8175a532bf0ffbb93f34791c07740bd6f515c7ff69ca362601fbd4cbf1d739dcbe1f9a75d1f6bdecf0fd01b0c6e4bb0a7d1799447ec85e02f92f0f6c2b6b7f678ab42676fc1616b2dbcbb55acb25a1cc1547fdd341bcf5bf6cf15cb69875ecfc09c4d957e058cf579f742542a331a01195c1635e5ffbaf8f2c1ee2207fd88ff6b6cad0b22550e6ebd047e9ba503fdf6a7ea3f601be8e17a2f60178bc60ef17128e3aeaf8b835a08db7bb8ff5fb2fe82faec1ffd3c1a71febf75f1043aee3525aff58583f71f50ea2f72d54e67f59fb511fe793b82d6d3d712c2195f97fd00fa50665f2b99cd7ddaf73f4a8f33a1fdfcec4730d9ab2eddf711fe3a37ff154cf4ad7beeb9a55a724941906f5f4701ce3dc6fd1fc65f0edeb7ba9e4838ea1ae5b5a944099a4693bd47744e988d7256ee4a52e29e7f1e058475da8cc00a84b29d8e364f2b9ddf0384baff7a6a3ae54e6b1416d65079a7c02b613f6a5231cffa7295b7f40fae93a5f8cbfcee1f6a5e71669fb5e74f87e1f5863f2ddeefd1574bc4f7ec85e02f9e183dacadadfee24adf15bacf47c1db2dbcb5db7964b42994b8efaa78378eb1ff59d59f2a7636708c419c591cf7ef35284466341232a83f70aeceb2d51fb7d1f63e9b3edf76f02a3dd6fe2b18b4f36fb9cc5be5eee3a1ea43278ce426526403f9b7094f57f1f20736c83f5a27a04565d03abae9e8e5f6b8a2c3dd3e06734d8293e4967fa1f1ee3529969d6feccf7756ce2a67ae0fdbaab16f7354b57bc16351bb875dcdbe716789ceef99cb6068fc5ece3a1abc04e65165ac743f671dc6d475decfd747170f7b9e02756597c3629db7276fe236b19bc66e0ba268b7ddf1560499b7ce5a799aa2a2b91a9d8a185ebbea9ab4fa2768031675f47196dad07afa3acb1fa24bbacee936afbb7e943db11afb5dbd7df3d3d9b16b6b95b56fd6d8d3463abc9e336f4710fa33b3e9b561adb7a27ad2fc4f10ca5563e1edfa90db8afc8a6c555078faf6b9a515a5c75f88e4f8bf14daefd874b8b2b0e1e5fe798515a5c71f88e518b8dae6b0b2e2d2e3b787c9d6b446971d9e13b3e2d26b4bbae914d8b0f1d3cf15fd3c8ae05de13cb85f90a03e6522b1f8fef9af5ae7b772e2d3e70f0f8ba7717a5c5070edff1695135de75ceeed2e29283e7529eb5b8e4f01d9f161327b9aea9b8b4b8e8e0f1707d2dab16171dbe638c8b66bcbe964d8b0b0e9e0b79d6e282c3778cc787e3b35d33442dde77f0f8baf617a5c5fb0edf316ab14efb3edf012dce3b78cee7598bf30edff169b17e9cf6fd5e07b478cfc1f35e9eb578cfe13b3e2dd64dd4becf75408b730e9e7379d6e29cc3778ce750615c9ced8016671d3c67f3acc55987eff8b4d8101e6b9de98016671c3c67f2acc51987eff8b4a80cf7a9a73ba0c56907cfe93c6b71dae13bc6b808cf274f75408b530e9e5379d6e294c3778cfb91302e4e76408b930e9e9379d6e2a4c3777c5a6c0caf3f9de88016271c3c27f2acc50987ef18afb9847171bc035a1c77f01ccfb316c71dbee3d3a23adca71eeb8016c71c3cc7f2acc53187eff8b4680eef891ded8016471d3c47f3acc55187ef188f3bc3fee24807b438e2e03992672d8e387cc778dc195ebf38dc012d0e3b780ee7598bc30edf31f69de171e7a10e6871c8c17328cf5a1c72f88ef1b833d4e26007b438e8e03998672d0e3a7cc778dc19ee475a3aa0458b83a725cf5ab4387cc7181761df79a0035a1c70f01cc8b316071cbe63bcae15f69def76408b771d3cefe6598b771dbe633c1f09aff1bdd3012dde71f0bc93672dde71f88ef15e51780cfe7607b478dbc1f3769eb5781b7cfb1867825ad058ac5196162550e681c1995f1a8b15a523ad039fa1c5babc157b5d32e3cade8ca8cb5b50172ad317ea520af638993cd5358c19fa96028d4d6f75d495ca3c3cb8adeca3269f806d721bd635ccf17f9ab28d4122fd749df7c55fe73056e91b10b47df7397cef05d6987c57a16f1a9b4e7ec85e02f96707b795a572a407694decba8dd0373090dd5eee6d6bb92494d9efa87f3a88b7fefb2c9e7d1673f8dc03c419c5919fbe2bc3b43f42a351a01195c1317bad9e78ec3184c441fe7419dafebdad32b46c099419037d148e2ba57a2682bbc74deafabde1a97ee48bd64df3e42f09b61bc068d751c7c74330f693be0142df07d136fade077ebf6bbc65d3759de0a9aee48bd64df3138091be3f323eff8ca98e328eb31835cf240f9ae1375568cab6bf98043c133df078aa67b81faab5ea34c1aa5312cae0b38db51eea59047e69dd345f0bbe7d6c73d482f6c9c32d2d4aa0cc42ebf8314a475a878edff18ebaf8d2719cc533cee17bb2671d69ddd4274ece83ef3acb778de55bb76d8c313d656bdb75c05cef8159af774afceb0ddbf654b32e8a67f25303754a830671d5097d1759eb267b09e49b06dfcd407ad0be93d8753bc2d8895a6e92b55c12ca4c76d43f1d73fda7583cb88df5a48f1b5e84e34a0fed218c81c91607cdd780765322b49b0cda5119dcf75679d2aedee2a1f92ae0a1e3ab5ab0d1710a7ee392fe9fca03b7ddefd53ab8c986df3cac72308e8f9f313cceaab218697e3c3092ad1e78ea3c69666febe1963e784cd0db2a43cb964099fdb05f4e38ca86cf1d17b5d58bbea5f9498cf5d2ebede5412ffcce6700fa04968634114369d0f62dd038791e08dabef5b97bcfce5deb36352f6bcedcf624b4120b137f8b1cd528061be67b386c41d0fe93a62560a34f9af6045bb1250b7e4a95cad3a7137dc8857ad0ba4b2cce526089d3377e0e96a66ca1d31b787c84b20e1dfa24ac099dc65d5bf634637cf4b4383b133bfa7f3db2948b5a17c5418987ba2313ad9be6c99fd6873e8ffbfabaa66dd3766ddabba3f9b53dbb11d66e5c982fb244b07f5dcb60906063a2f5f4b4c4e913bf3835d8786dbe00fc05c012189ed2f879c240a56f0837addbbe7dc9def5dbb734cddefb5ad39e2d3b5f43457b5bca45a96d37793db9ba3a2c6b97efe5b0d9137eb1b937d8e88bcda56023ff7dc0461c0fc0ffec2de1a54d0c83f55318ebff95980af73215a210a4dd90ee4f749bd1af4dd5bb7ffd8968fd4968bdf9f4dbe2f4279ef5279df5279cf5279bf5279af52799f5db24f413defac8614890f984f29341e615b14383cc2790f5278f9f09da3e69aca7df055efd09637da8a13f51ac3f49fc7c90b954383ac8bc564dbfc6421fb2ea432b7d98a74f6bf421b83e2dd7875efa7281be74a40f63f421a23efcd1872cfa105c1f7a4f355a4f0b329f34d79f309f19643e51ae3f49ae3f413e37c87c627cbe4a0b82cca7b6170599cf07ebcf87ebcf76ebcf79ebcf7cebcfcfebcf82ebcf85ebcf88ebcfcceb4f0feb4fd0af0e329fda7e29c87cb6f89520f399e3b541e6b3c8eb83cc6794370499cf2eebcf31ebcf346f0e329f75d69f7bde16643e0fbd23c87c4e5a7f665a7f7e5a7f965a7fae5a7fc65a7fde5a5f72d7b71af425787d795b5f7ed5b740f4ad1f7d6958dfb2d4b770f52ded962033e4e150901912a38708e921537a08991e527722c80cb9d44350f5905c3d44590fd9d643d8f5907efd88837ee4433f02a31f09d28f48e947c6f42374fa3148fd58a17e4c563f36ac1fa3d6977df523e8fa56a0bedcdb1a642e09df09329f5ffe9c4a9f57e9fb54fa824adfafd20fa8f4832afd904a3facd21755fa11957e54a51f53e9c755fa09957e52a59f52e9a755fa19957e56a59f53e9e755fa0595bea4d22faaf44b2afdb24abfa2d2afaaf46b2afdba4abfa1d26faaf45b2afdb64abf1364e2f1f754fa7d95fe40a53f54e98f54fa6395fe44a53f55e9cf54fa7395fe42a5bf54e9af54fa6b95fe46a5bf55e9ef54fab24a5f51e9ef55fa0795fe51a57f52e9ab2afdb34a5f53e9eb2afd8b4adf50e99b2afdab4affa6d2bfabf42d95fe43a56fabf49da0ed33e5d8790c363dce5033bf6ecf9ee61dafefa9d8b3b362c7deed7bb6bcbefd8d8a7d5bf66caed8f9d9e65d1bb7efdc870b7fd92cfcb8999fb66bd7ba372ab6bcb6a1797fc5cebd7b2a766eac58bf73ef6b1bdaed2cffb75968f0dd1ed76dd810edacb4c7a7202debd139a783cc72f4a5fa79d9ebf6448f4e08f24c67169adbc90acd357b203a0d5b9e39e6abd8bd7de79e8aca8ad7d45fb573ddb9af79c3980afcdf6e25f2ee3d15bbf7acdbb5a762e3ae9d3b2aaac6e07a17f6e94425bed8c70f4c6ff35ea24e85ca6f0eec444dbe36b0739be33f3f0de9ffeca4d3b2419da861ffce2c347e50e708d3832265d9bd77fd9e5deb9af6442f3cebd32cbca033d55cddc96af619dc0967e59d5968f4e0ce112ee88cb37d39380bfe3f4095c818d4550500", "isInternal": false }, { "selector": { "value": 2603445359 }, - "bytecode": "0x1f8b08000000000000ffed9d07741c459ac75bb22ccb1e8d6559ce5198e024dba351b66c2ce7c89a643060c0191b6ccbd822c76513bb2cbbb039b26c860536e71cd8c8eec2b21936c212eebd7befeedd7bc7ddbd77c75d554f7da7ff14d58d46f427576bbe7eeff3547faae9ef57fffebabaa7babafd7c100415416119a16c4ef0d285fede633e73af6c694e705b394ece8a947056a68473444a38ab52c23932259cd529e11c9512ce9a94708e4e9053b35506c54bd2bc6318744d9a3193324d6b53a06936659a8e4d81a675413afaa87129e1ac4f09e7f8947036a48473424a3827a68473524a3827a784734a4a38a7a684735a4a38a7a78473464a3867a68473564a3867a784b331259c27a584734e4a384f4e09e7290972ce03ce53cde769e673aef9a43af3cde702f3b9d07c3699365699f545ca162b5ba2f9acbfe91b0d79652dca5acddf1acddfda94b52beb50d6a9ac4bd95265ddca96295baeec74652b4cfb572a5ba56cb5b235cad62a5ba76cbdb20dca362adba46cb3b22dcace50f62a655b959da9ec2c65672b3b47d9b916cb3665e7293b5fd976651728bb50d945ca7628bb58d925ca2e55b653d92e65bb95ed51b657d93e65fb955da6ec80b283ca2e577685b243ca0e2b3ba2ac57d95165572a3ba6ecb8b23e4bb3ab945dadec1a65d75a9cd729bb5ed90dca6e547693b29b95dda2ec5665af56769bb2d7287badb2d7297bbdb23728bb5dd91b95bd49d91dcadeacec4e656f51f656657729bb5bd9db94bd5dd93b94bd53d9bb94bd5bd97b0c0b25fb7b95bd4fd9fb957d40d90795dda3ec43caee55f661651f51f651651f53f671659f50f64965f729bb5fd9a7943da0ec41650f29fbb4b2cf28fbacb2cf29fbbcb22f28fba2b22f29fbb2b2af28fbaab2af29fbbab26f28fba6b26f29fbb6b2ef28fbaeb2ef29fbbeb21f287b58d90f95fd48d98f95fdc4d2fca7ca7ea6ec11653f377fa331a45f28fba5293f6a3e1f339fbf329f8f5bdff9b5b2df58bedf2afb9de5fbbdb23f98f21fcde713e6f349f3f927f3f967f3f917f3f957f3f937f3f977f3f994f97cda7cfec37c3e633e9f359fcf99cfe795d54f28946b82fea52748a8df69ddd7a5ef3f90d8a706c58bd66284f91b7d361a7f9559a74fd26ea4591f69f9abcd7ab5b59d1ab35e63f9ebcd7abde56f30eb0d967fa2599f68f9279bf5c9e0cf04300e69fcda37c2b82ac047795809be9141b126da574d9b03dfa8a0580beda3fd580dbed1c6370a7c638caf067c19e31b4d9a29ab35be9e20a99cc8edd2dbcd26bd5d736f666cf2bc7bf476eb9878c725cfbb4f6fb79e8157e7c778b3ad7190370dc6570f3ed3ad04e3c137d1f81ac037c9f826806fb2f14d04df14e39b04bea9c637197cd38c6f0af8a61bdf54f0cd30be69e09b697cd3c137cbf866806fb6f1cd045fa3f1cd02df49c6371b7c738caf117c271bdf49e03bc5f8e6808ffacf93c147d770a7189fee135e08e03bc65f09bed3a8cf05df5cea6fc1378ffa5af0cda77e167c0b2036f916421f42be26e3a3fe48ffaddd947b82a4f23fbf576fb723e9edaa2debed7625bfddf0bed5d2a05fd71e88d3015a759b728273639a317685318a43fe2a28af87ba548ff4a0730ab1eb7347a72977c77cafddfa5e16ea743adadf1324dbfe2e8ba7cb62d6f9bf0c3892cfd99666c9d9012f25e7ec36a86be71e5ddf0cc79cdd041c0c39dbc693b3f99ce46c617c2108dcb947d7b8c33167b70347f239db26393bf0a5e49cbd12eadab947bf738663ceee078ee473b6a34dae0d06bc949cb3b7405d3bf7e8b7ee70ccd93ee060c8d92ee96707bc949cb377425d3bf768dc6538e6ec6dc0917cce7631e56c8be46c50b8571404eedca331c0e198b3770147f239bba74bae0d06bc949cb3f7435d3bf7683c7a38e6ec3dc0c190b35ce3b379c9d9c23df22070e71edd1b198e39fba029ebfb0c8f9afb0c33c0f798f1cd04dfaf8c6f16f81e37bed9d0aee48f81bd2d720c0c7829f918f829d4b573b9d19487e331f06de060c8d936c9d9012f25e7ecefa1ae9d7b734c7938e6eccf818321673b246707bc949cb3cf405d3bf768fec270ccd9274c595f2ffcd15c2f9c06be278c6f2ef89e34be79e0fb93f1cd07df9f8d6f01f8fe627c0bc1f757e36b02dfdf8c6f11f8fe6e7c8bc1f794f12d01dfd3c69703df3f8caf197ccf185f1e7ccf1a5f0bf89e33be56f03d6f7c6dc6a7ef09d0fc94878daf06dad41324b76fc3392941f15261adf740b9899727970d8ae75553acc5c9c76ad16d5f140cbced8b81670943db331063203c4b8027973c4f38f7a239f9ed86fb7891a56906622d8276e519da5501b168dbb44ef1b2e0c3fe20ef606c499e315f01b168dbb4de028ce4c3fe89fa573a7e74df3cb6a29f97e1580acfcf18af0738285e15d479a8a1bfee78c3560b7fa73ea016cad87fe72c1f53ae36e3fc7eda36ad370323b53137f48cf981322eb118b9fa8d0a8845dbb663d73af4d19ab538346b65626cb11869bd151849bf96a167cc0f94d1ee1798faa4e681f649a44b6ee8351bd07ecd820fafe15a1d8c6dc93386fbb5d562a4f53660245f1e78b8ce8751c7ab2fb139ae43309fe99c45e71f8a5705754e1dd15f77299c4f19fad07ca9d7a6d8a727bf9ff2393c9f0d848779df3533e5630efbce17836473cd3ee673965678cc635fced54f46f5e5144f98855998855998855998855998855998855998855998855998855998855998fd678eba2fe79acf702219c9d70c3c1ce3fce17b7cccb6f01ed023705f27f9fb16f91cdeaba7798cf3ac3657419d7faae8677bd4314f02ef9d2fb67c4cf394c27d89f3947a609de2e1bc0d9c37c530f724e45968f1d8b16b1dfaf8388fc4a7391a51f3b47cd22c0b3e9c0fb78889272acf1639623726163bbf87e7d8c9e7f43b53f47bc7a83fb18f119cfbb6c0f2e93e69de88fe7673e440a9f79df1bc41e524efe362be61ace4e7dbe48be6275406c5e70abca66098a35274bf9ae646b459b1aba0ce7f54f4ef9b76f87b4ff0d239425887b64debf3e0bbedd6b6ebf8da1bdb1fb6013795abadb6350137d5f96f38773f505928335dcfe471fe6c00bc81d5265a707e46f2d77b85f922f912785a818763ee1ad3756d0ef331e9f922ed9656aeeb65aad306fa25febc90393eecdf3cb44ef184599885599885599885599885599885599885599885599885599885599885d97f667cff05b1e233d2cd9e300ed11c9bf07e06bd8f08ef8bdd59d91f97fb1e20dd739a6fb5199f517ea2b29fed6e53c67704b89e77c77dc9756f2d6a5f52bcdae0a5cfe933edcb01bffba1d5a159bb433386f7a835bbfa0c5aef0046d2af1d78b88ec7368bc78e8d7d46aba79a45f5b35cf357a2f2cc354fa131b1d885f9221cf77569be887dfe6ab634c57912e4c3fbe0f86e159c8fe5dbfb5fecb90bd84fe11cade4fbcd7cd13d607bae20c5c379125f32dad23c89e4fb817c8ef31c41c728cd09c93bda4a75be0ee7b96f9a32ce156a866d3de2f83b2d717312b04f1dea775a626c1fde69f9b3cafebaf6bb2949eb52df6999b3bee7e33b2dbf0379f6085ceb71f5499d815ba379a011d5c16b5eaef3973d67db9eab8cfde828ab0e7db70aeafc1afaa852de15c575bd10750ec0eb05fbbc3090b9e3e53e1ff069e82fdae1ef3dc12b9f0ff834e490ebba94b63f1fb64f5ca382e8730bd579ce3a8f32ccd71bd07baf5cbffd8817e71b529d7f86be6a9699971bf53bc6f59c05d7ef86a8e73e281e5e5395d276ec17923e37623e220bc5c37c7cc1cac7f608eec58eeffe57c477492b9a5f8ebf936dfdb40e9df09d9e447428f4375d565be898ea84b6509dffb58e9be4af990ad79fc9b7b5f87a88faa016475bffffda08de05576dca19d84fd8574e74fc9d96b8eb4fd24fb77959f26d0ef7ef72b32ddabfcb1cb14f07d6846237636cbafea438e4af82f28411fd75a91ee9415a13bb3e46e81a0ed9edefb559dfcb429d6e47fb7b8264dbbfcce2596631ebdc190d7936119eb5e0eaabbb23349a0f1a511d7cd6d1fefd1f758e3991ef25b5fb4d3c4f0e259b3d7eebba3ea13a780d4d754e36cc754667bbae3d2e4de79024e7a7e3f3400b212e3e0fb49049cf6c50ac67d6e2e08c5d67c5ae1bc2d8f556ecfa218c2d9a8be63e69eed3ff8581ff6745650a1847a480b12a058c2353c0589d02c6512960ac4901e3e814308e49016306184fe4b99d419fbc67ffdf53ecb506c666789f43a8455330702d16f1f2c45efb606c8677a894fcff0d30ff5f58cd83fdbfb0b2f0bdf129606c4801e38414304e4c01e3a414304e4e01e39414304e4d01e3b414304e4f01e38c1430ce4c01e3ac1430ce4e0163630a184f4a01e39c14309e9c02c65352c0786a0a186b52c0b88097313f5846cdc3f11ecd4c503c8febe57898dfeb19be9fd0f50e518eff77b6d4b633bfd3b779b0efc0c3f90bbcff8fe22b7b4f1fc7fc8452dfd317f77f4f3331e607cbc8354f1be7280f84079ff1743d3bc2c0981f2c23d7f31df8bce140785cff0f2eeff33605cd06c3c8351fabd4f982f81c5e9b433306c6fc6019b9e6f4e3f38603e1713d1798e365cc0f96916bee6b06620c84a71334eb7068c6c0981f2c23d3b35ba1669d25f0e0334e9d0ecd7c62449ea4df9ddde988b5d483b61303328e4e01e3981430e2fd74aeb9fb51f7d33b79f5c90f561faefd15773f1d63333c53196ab13418b816ddbc3cb1f7d33136c3b315a1163897fee5b458063ccb19b4c804c5cf1bbc1c0f3164e17be353c0d89002c60929609c9802c64929609c9c02c62929609c9a02c66929609c9e02c61929609c9902c65929609c9d0246e6e770637fbf2c1be6b1a37eab0cf7d851bf4b867b6cc973c9f372882d792e795e0eb125cf25cfcb21b6e4b9e47939c4963c973c2f87d892e792e7e5105bf25cf2dca7d86918e317c6e1c7883c8dc9f1e4b0ed188be39da1a5b6fd74074f0553db31d60a0fda4e0c69635c9e02c6ae14308a8e8539888361ac017fd23c2b4ae0e9019e954c3c3d25f0ac049e55c9f38439b5b2041e62c8c2f7ba52c0b83c058ca2a3e8e813a3e8583e3a0aa3300aa3309e08c634f4e1729e29fc76190ca3e6599d3c4fa8d9aa1278568366f4bd1c2f637eb08c9a674df23ca166ab4be059039aad7668c6c0981f2ca3e6599b3c4fa8d99a1278d682666b1c9a3130e607cba879d625cf136ab6b6049e75a0d95a87660c8cf9c1326a9ef5c9f3849aad2b81673d68b6cea11903637eb08c9a6743f23ca166eb4be0d9009aad7768c6c0981f2ca3e6d9983c4fa8d98612783682661b1c9a3130e607cba8793625cf136ab6b1049e4da0d94687660c8cf9c1326a9ecdc9f3849a6d2a81673368b6c9a199af8c5d29605c9e0246661df38365d43c5b98783697c0b30578ce60e2d95202cf19c0f3aae479c29c3aa3041e62c8c2f7ba52c0b83c058ca2a3e8e813a3e8583e3a0aa3300a63698ca7a78051f6b530facac8f0fb2af6f994338679eca8e753867beca8e753867b6cc973c9f372882d792e795e0eb125cf25cfcb21b6e4b9e47939c4963c973c2f87d892e792e7e5105bf25cf2bc1c624b9e4b9e97436cc973c9f372882d792e795e0eb125cf25cfcb21b6e4b9e47939c4963c973c2f87d892e792e7e5105bf25cf2bc1c624b9e4b9e97436cc973c9f372882d792e795e0eb125cf25cf7d8abd35f9d8f9529f61dd0a3c1ccfd432b533a7b77ba6d9d68b09eaa7b53acbd2ea0c4bab2cd43913f43b8b41bf0a884bdba6758a572af3691e3033c5ce8f55db180deda718cb2d3d74fcb399da1ed5d79f3dcc6347f5f5c33d76545f3fdc634b9e4b9e97436cc973c9f372882d792e79ee4b6c2c5705fdd7edf47e25bd8d734c79a459a7faa7c3f7a8ce8c5185cfba408e218ed8720cc9b9a21c624b9e4b9e97436cc973c9f372882d79ee679e9f9b7cecf0de18febed04bdcbdb17381e71c062d98da99d36dda66b5e96cab4d59a883efacddc6d0ce0a884bdba6f56db01fd2c6ac79969932b166a0de324f18c9770e2f4f787c2d0b8a97b8e36b1bf0301c07cd4ced0c8faff3ac362d73e84e753057cf6368a7ebd8a1f5f3603fa48d59f3ac306562cd40bd159e3092ef5c5e9ef0f85a11142f71c7d779c0c3d1ff30b5333cbeceb7dab4c2a13bd5c15c3d9fa19dae6387d6cf87fd9036e62cc423d64c50bc4f7d6024df365e9ed64c507cfce825eef83a1f7838fa1fa67686c7d776ab4d3d0edda90ee6ea768676ba8e1d5adf0efb419885d9c5ac7956c2dff592817a2b3d6124df79ac3cadb90cb49996b87e6c3bf070f4f34cba87fdd805569b563a74a73a98ab1730b4d375ecd0fa05b01f4a615e9e4266d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d17970cc9a679529136b06eaadf284917ce7f3f284cfefac0a8a970a6bbd07ca1700cf76067d98da19ce7bbfd06ad32a87ee54078faf0b19dae93a7668fd42d80fa5302f4f21b3e83c3866cdb3da94893503f5567bc248beedbc3c613fb63a285ee2fab10b8187a39f676a67d88f5d64b569b54377aa83c7d7450ced741d3bb47e11ec0761166617b3e65963cac49a817a6b3c6124df05ac3cf9f039c43541f112d78f5d043c1cfd3c93ee613fb6c36ad31a87ee5407737507433b5dc70eadef80fd500af3f214328bcea27314b3e82c3a47318bcea27314b3e82c3a47318bcea27314b3e82c3a47318bcea27314b3e82c3a47318bcea27314b3e85c3e3a6b9eb5a64cac19a8b7d61346f25dc8cad312de77581b142f71f71d76000fc77d1926ddc3fb0e175b6d5aebd09deae0f17531433b5dc70ead5f0cfb61b8332f4f21b3e4c6d0304b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e486304731fb901b9a679d29136b06eaadf384917c17f1f284ef3d5817142f71f3762e069e1d0cfa30b5339cb77389d5a6750edda90e1e5f9730b4d375ecd0fa25b01f8459985dcc9a67bd29136b06eaadf784917c3b7879c27e6c7d50bcc4f56397000f473fcfd4ceb01fbbd46ad37a87ee540773f5528676ba8e1d5abf14f683300bb38b59f36c306562cd40bd0d9e3092ef625e9eb01fdb10142f71fdd8a5c0c3d1cf33b533ecc7765a6ddae0d09dea60aeee6468a7ebd8a1f59db01f760ab3303b9835cf465326d60cd4dbe80923f92ee1e5c967a0cdb4c4f5633b8187a39f676a67d88fedb2dab4d1a13bd5c15cddc5d04ed7b143eb142f8dcc9a679329136b06ea6df284917c97f2f284c7d7a6a078893bbe7601cf4e067d98da191e5fbbad366d72e84e7530577733b4d375ecd0fa6ed80f6963d63c9b4d995833506fb3278ce4dbc9cb131e5f9b83e225eef8da0d3c1cfd0f533bc3e36b8fd5a6cd0edda90ee6ea1e8676ba8e1d5adf03fb216dcc9a678b29136b06ea6df184917cd84fd152098c5b9818038b31b0f4419e6ecf78da3ce3c97bc6b3d8339ead9ef1ccf28ca7c9339e299ef18cf78c67b4673c233ce399ed19cf7ccf78a67ac6b3c8339e06cf78c678c653e5194fbb673c2d9ef12cf18c67ae673cd33ce399e019cf02cf78329ef18cf48c67ba673c133de3a9f58c27eb194fb5673c9d9ef17478c6d3ea194fb3673c333ce399e419cf58cf78ea3ce319e519cf52cf78667ac6b3d0339ec99ef18cf38ca7de339e1acf78e679c653e1014f2678e97dfa0cfc7d2bf82aadefeafefcdf1afaff4ef7452be13b7b4d798463db7bc0b7db94f73abe8b3a71ddebc5583db04ef16a8163af273cf33ce3a9f18ca7de339e719ef14cf68c67a1673c333de359ea19cf28cf78ea3ce319eb19cf24cf786678c6d3ec194fab673c1d9ef1747ac653ed194fd6339e5acf78267ac633dd339e919ef1643ce359e019cf04cf78a679c633d7339e259ef1b478c6d3ee194f95673c633ce369f08c6791673c533de399ef19cf6ccf784678c633da339ef19ef14cf18ca7c9339e599ef16cf58c67b1673c79cf78da3ce3e9f68ca7d2c1b395896777d0bff4c0fa564f6233ec879cdeee3ea636ed37dbaa36db257e8a5705755e300311fa7e037e97b8b698b2abafdb0f1aed666a4bd6e2a1f5ddc33c769d15bbae4c62d75bb1ebcb24b6e4b9e47939c4963c973c2f87d892e792e73ec67e31b9d86df27ea081f3c8fb81e2797c7b3f90bc8f279e47dec713cf23efe389e791f7f1c4f3c8fb78e279e47d3cf13c559ef1c8fb78e279e47d3cf13cf23e9e781e791f4f3c8fbc8f279e47dec713cf23efe389e791f7f1c4f3c8fb78e2797c7b1fb3bc1f289e47de0f14cf23ef078ae791f703c5f3c8fb81e279e4fd40f13cf33ce3a9f080e7e5de0f84eff5a1b98dbbc147f327e3de239481edec071f8dc7d136f4f9e17f1a5eca5009dfb9ccc1b5cf118fe25ce6f8ee50e88eb17a609de2d502c7659ef0ccf38ca7c6339e7acf78c679c633d9339e859ef1ccf48c67a9673ca33ce3a9f38c67ac673c933ce399e119cf56cf789a3de369f58ca7c3339e4ecf78aa3de3c97ac653eb19cf44cf78a67bc633d2339e8c673c0b3ce399e019cf34cf78e67ac6b3c4339e16cf78da3de3a9f28c678c673c0d9ef12cf28c67aa673cf33de399ed19cf08cf78467bc633de339e299ef13479c633cb339ec59ef1e43de369f38ca7db339e4a070fd73b7fa29e8f1e8af70dbd5c6cbdbe1074d14b06fe3e14f354b65a8cb4be05189197781632f1443dd7bdd083d8bafd746ea07b1619f87b133072e5d4428b91d65d3985f3e49a9878a29e476ff220b6d6827e4bd03de50cfc1d9f73e3caa9268b91d65d3955cfcbd39a8136d3123797068f398e7dc8d4ce1c1e7f09be0321a7b5da6269b5c8d22a0b7586629e73547f40f1845998a398350f5dcb132b9ecf86e23d0d0361749d5f1978c2fe717150bcc4f58f5b8087e3fcc1d4ceb01f3b60b569b14377aa83b97a80a19dae6387d60f38623706c96a7170005a1c74f01c1c622d285ea9cc5b53c8ec83ce9a87eef5102bcedf5de20923f916b2f2e4731968332d71fde341e0e1387f30e91ef609975b6d5ae2d09deae0f17539433b5dc70ead5f0efb4198855998855998855998855998855998855998855998855998855998855998fd66d63cf44c2bb166a05eb3278ce4dbc2ca53b8efd01c142f71f71d2e071e8efb324cba87f71daeb0dad4ecd09dea60ae5ec1d04ed7b143eb57c07e1066611666611666611666611666611666611666611666611666611666611666bf99350fbd4b815833502fef0923f90ef2f284cf83e583e225eebec315c0c3715f86a99de17d8743569bf20edda90ee6ea218676ba8e1d5a3f04fb419885d9c5ac79e89d78c49a817a2d9e3092ef72569ec2fdd396a07889ebc70e010f473fcfa47bd88f1db6dad4e2d09dea60ae1e6668a7ebd8a1f5c3b01f4a613e904266d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169dcb4767cd43fff71fb166a05eab278ce4bb8295a725bcefd01a142f71f71d0e030fc77d1926ddc3fb0e47ac36b53a74a73a787c1d6168a7ebd8a1f523b01f863bf38114324b6e0c0db3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b30fb9a179da4c99583350afcd1346f21de2e509df7bd016142f71f3768e00cf61067d98da19cedbe9b5dad4e6d09deae0f1d5cbd04ed7b143ebbdb01f8459985dcc9aa7dd94893503f5da3d6124df615e9e7c06da4c4b5c3fd60b3c1cfd3c533bc37eeca8d5a67687ee540773f528433b5dc70ead1f85fd903666cdd361cac49a817a1d9e30920fcfcb1d4c3c598b27ebd0e244c5d6eb9da65c6b3e33f0f74e60e4ea0f3b2c465ac71c475ee2e964e2a9b378ea1c5a9ca8d8bafd4b4d79acf9ccc0df970223574e755a8cb4eecaa93ae059cac4536ff1d43bb43851b1b516dda63cce7c66e0efddc0c895534b2d465a77e5543df07433f144f549dd43103beaf81a8ad851b93214b145f368cd198ebb707ca03b285ee2aeabf1dcc2d15731b533e73a7f775b6dc2f3375ea39ea8f393300b731433d3756e6bc68a4dfa04160f2dbdcc5a0ce5efec4eab4d69f89d1dc77c2085cca2f3e09875ec2b138f5d78df28c6267d028b87962b99b5e06967a13f3816b835a67859a883797a8ca19d151097b64debc7603f08b3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3dfcc3af6f1c46317c6ef3136e913583cb41c67d682a79d85f1fbbec0ad31c5cb421ddce77d0cedac80b8b46d5aef83fd20ccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22ccc7e33ebd857251f3b7c1e0763933e81c543cb55cc5a30b5331cbfbf3a706b4cf1b25007f7f9d50cedac80b8b46d5abf1af683300bb38b59c7be26f1d885fb79189bf4092c1e5aae61d682a79d85fee0dac0ad31c5cb421ddce7d732b4b302e2d2b669fd5ad80fa5301f4821b3e82c3a47318bcea27314b3e82c3a47318bcea27314b3e82c3a47318bcea27314b3e82c3a47318bcea27314b3e82c3a47318bcee5a3b38e7d5de2b15bc2f17b8c4dfa04160f2dd7316bc1d3cec2f8fdf5815b638a97853a98a7d733b4b302e2d2b669fd7ad80fc39df9400a992537868659724398a398253784398a59724398a398253784398a59724398a398253784398a59724398a398253784398a59724398a398253784398a59724398a398253784398a59724398a398253784398ad987dcd0b16f483e76f83c3bc6267d028b87961b98b5606a6738ffe5c6c0ad31c5cb421dccd31b19da59017169dbb47e23ec0761166617b38e7d53f2b1f3192b36e913583cb4dcc4ac05533bc3fee0e6c0ad31c5cb421ddce73733b4b302e2d2b669fd66d80f6963c6fd57915cec70de26c5a8349fda778b298f00dfada65c05be579bf248f0dd66cad5e07b8d298f02df6ba16de47b9d292f00dfeb4db91b7c6f30e5a5e0bbdd943bc1f74653ee00df9b4cb9177c7798f251f0bdd994af04df9da67c0c7c6f31e5e3e07bab29f781ef2e53be0a7c779bf2d5e07b9b295f03beb79bf2b5e07b87295f07be779af2f5e07b9729df00be779bf28de07b8f296f05df7b4d7934f8de67ca63c0f77e28d3e7074cb9167c1f34e52cf8ee31e5b1e0fb9029d781ef5e531e07be0f9b723df83e62cae3c1f751536e00dfc74c7902f83e6eca13c1f709539e04be4f9af264f0dd67ca53c077bf294f05dfa74c791af81e30e5e9e07bd0946780ef21539e09be4f9bf22cf07dc6946783efb3a68cfbf773a67c13f8a81fb8197cd40fdc023eea076e051ff503af061ff503b7818ffa81d7808ffa81d7828ff2ee75e0a3bc7b3df828efde003ecabbdbc14779f746f051debd097c947777808ff2eecde0a3bcbb137c94776f011fe5dd5bc147797717f828efee061fe5dddbc04779f776f051debd037c9477ef041fe5ddbbc04779f76ef051debd077c9477ef051fe5ddfbc0477987fd42a3297f007c2799f207c137c794ef01dfc9a6fc21f09d62caf782ef5453fe30f84e33e58f806fae297f147cf34cf963e09b6fca1f071f9d9b3e01be85a6fc49f03599f27de05b64caf7836fb1297f0a7c4b4cf901f0e54cf941f0359bf243e0cb9bf2a7c1d762ca9f015fab297f167c6da64cfd823efe6aa12dd4ce5ae05ee4e0215f0df0f404c95e33512cda36ade78191746c1e7ac6fc40197316a3e66965d00c738396b8df24adc0d3c2c0c3d4cef037499bd5a6bcd5a62cd4390ddad9c6d0ce0a884bdba6f53688cdb1cf518b6ab3ddb996165550e76173a2d2e7bf381d691b3a7f9b1d6de1d23167f1e41cb13b9975a46d539fd83904b1dbadd84bacd8d89fd312776cb703730703b3de6e57f2db0d8f6dfacd47f94c7196409bba4183a4da84b12b8c511cf25741f9c986feba548ff4a07327b1ebe388f625b2dbdf6bb5be97853a9d8ef6f704c9b6bfcbe2e9b298f5f5ff630dfd1c0cc74398039d1607ad2f01edba22b4eb04eda80e9e7b9b98b4ebb07868bd0978e8faaa0d7c749d42fc789db66808b8ed7eafcdc14dbe76606c72303627cf185e6735598cb4de0c8ce4eb009e7626cdec7d3dd7d207af09465975e8bb5550e75fe1bc9c71d4d5c7ddd88afe76d1eff5178364fbf46a06bd702c21007d024b435a88a126e81f6f4892674cd03f9e70bcaff7d8aecbf69dbd6fd7de0a40abb230f1b3c2d18c4af0617984c31704c5c32638dc4ac32638dc5a69c982c335545fff8cd3cda2a1897d870ff66d3bb2efc89e63d71deddbb7774bef65483dd2a247d2a8162029fa68a909fa07787a82646fb4545bb1e292a7063e4725cfd3ccd4cef0a437da6a53b5d5a62cd419097f1bcdd0ce0a884bdba6f5d18ed8097644a1166306a0c51807cf9821d60207c9c987472afd1d6f8c545a6dc1231adb64e779a20da280a7c2f62b0c9cfe9b3ed8479ac68c0afa7736f59efa8a56ef043dbaaacf5a7af4548f96ea2e489fccf4e8a71eedd4a39b7a34538f5eead14a3d3aa94723f5e8a31e6dd4a38b7a34b131288c16ead1413d1a780a703d0cacfad7bc3e3bead13c3d7aa747ebf455953efbeb2b117de5adaf12f52f477d75a07fd1ead10d7da6d55731fa0cadcfaafa2a515f1deaab797d75bb4cd97265a72b5b61b45ea96c95b2d5cad6285bab6c9db2f5ca3628dba86c93b2cdcab6283b43d9ab82c208fc99cace5276b6b273949dab6c9bb2f3949daf6cbbb20b945da8ec22653b945dacec1265972adba96c97b2ddcaf628dbab6c9fb2fdca2e0b0ab36d0e2abb5cd915ca0e293bacec4850b8f3a5ef74e93b5bfa4e96be73a5ef54e93b53fa4e94bef3a4ef34e93b4bfa4e92be73a4ef14e9bb04faae80be0b706b5018e5d7a3fa7a145f8fdaeb517a3d2aaf47e16f0f0aa3ec7a54fd8ea0306aae47c9f5a8b81e05d7a3de7a945b8f6aeb516c3d6aad47a9f5a8b41e85d6a3ce7a94598f2aeb51643d6aac4789f5a8b01e05be37288cf2ea515d3d8aab476df528ad1e95d5a3b0f7058551563daaaa4751f5a8a91e25d5a3a27a14548f7aea51cecf2bfb82b22f2afb92b22f2bfb8ab2af2afb9ab2af2bfb86b26f2afb96b26f2bfb8eb2ef2afb9eb2ef2bfb4150c8c11f2afb91b21f2bfb89b29f2afb99b24794fd5cd92f94fd52d9a3ca1e53f62b658f2bfbb5b2df28fbadb2df29fbbdb23f28fba3b227943da9ec4fcafeacec2fcafeaaec6fcafeaeec29654f2bfb87b267943dabec3965cf07fd773bb0a378c1acd0c8fbaebebe7d878ff635f6f5361ebeea50dfc1a387ae6bbce660df81c6deabf71ddb7fa8f71afcf2174dd744b715561e3bb6ebbac68347f6eebbb6b1f7aabec6defd8dbb7baf3ab2f7387ee971f3a5192f8db86befdee8604f55be02d2670719f4dfcdf7e886cdc6f8b6fde760047971305f9a3362700d9a6ece2ccbccfa39852bdac6e3877afb1a738d47d4bfbb0ea9efecdbbbb811ff765c897cbcaff178dfae637d8dfb8ff51e6e6c5e5c245466108df841c320bef42f0d036f79f07f1d300ba085c50300", + "bytecode": "0x1f8b08000000000000ffed9d69741cc5b5c77b24595e46832d5bde37b1056fb246a3dd968d8cf70dcc8e018317c960b02d638b7d271b092121fb4202bcac2421fbbeef09d9f7852424908484247c7839efbdf3ce791f38afaa5537fa4f51dd6886be72b5e6f639d7537d55d3f7776fdfaaeea9aa6e3f1304412618daaa959c143c77a3bff79acffc0bdb5a123c569e93339312ceaa947056a784b326259ce352c2599b12cef129e19c9012ce8909726ab6aaa0784b9a7712435c9366cca62ca6752988692e65313d2105319d1ca4a38f9a9212cefa94704e4d09e7b4947036a484737a4a3867a48473664a3867a58473764a38e7a484736e4a38e7a584737e4a3817a48473614a381b53c279624a384f4a09e7c929e13c2541cec5c079aaf97c91f93ccd7c2e329f547789f95c6a3e97191f6bcc7e9392e54a9a359ff5373dd15050d2aaa4cdfa5bbb920e259d4abaccdf1acddfba95ac50b252498f92554a562b39ddc4628d923394ac55b24ec97a251b946c54b249c966255b946c55b24dc97625672a394bc90e25672b3947c9b94ace5372be920b945ca8e4228b65a7928b955ca2e45225bb945ca6e47225bb95ec51b257c93e257d4afa95ec577285922b951c50729592ab951c547248c96125034a8e28b946c95125c7940c2ab956c9754aae57728315b31b95dca4e46625b7589cb72ab94dc9ed4aee5072a792bb94bc58c94b94bc54c9cb94bc5cc9dd4a5ea1e4954aee51f22a25f72a79b592d728b94fc96b95bc4ec9eb95bc41c91b95bc49c99b95bc45c95b95bc4dc9fd4ade6e58a821bc43c9034a1e54f29092ff50f24e25ef52f26e25ef51f25e25ef53f2b092f72bf980920f2a7944c987947c58c947947c54c9c7947c5cc927947c52c9a7947c5ac967947c56c9e7947c5ec917947c51c997947c59c957947c55c9d7947c5dc937947c53c9b7947c5bc977943caae4bb4abea7e4fb4a7e60c5fc874a7ea4e4c74a7e62fe46e34b3f55f23353feb9f9fc85f9fca5f9fc95f59d5f2bf98da57b4cc96f2dddef94fcde941f379f7f309f7f349f4f98cf27cde79fcce79fcde75fcce753e6f3afe6f36fe6f369f3f977f3f90ff3f94ff3f98c92fe86a1f2846078eb0d12ea93dafafbf4dc0405fbd4a078d3b1a8367fa3cf46a3af31fbf449b11b67f6c759fa5ab35f6b1d6782d99f60e9ebcd7ebda59f66f6a759fae9667fbaa59f69f667823e1bc018a5d16b5db551654047795805ba7141714cb4ae960e07baf141712cb48ece632de8261add78d04d32ba09a0cb1add448a99923aa3eb0d92ca89fc1e7ddc5cd2c735f3362724cfbb4f1f773213ef94e479fbf571eb1978757e4c35c79a027933cde8ea4167ba95602ae8a61bdd34d0cd30ba06d0cd34bae9a09b6574334037dbe866826e8ed1cd02dd5ca39b0dba7946370774f38d6e2ee81618dd3cd02d34baf9a06b34ba05a03bd1e81682ee24a36b04ddc9467722e84e31ba934047fde7c9a0a3fbbb538c4ef7093519f88ed1537f147e87fa5cd09d46fd2de816515f0bbac5d4cf826e09d826dd52e84348b7cce8a83fd27feb32e5de20a9fc2ff4e9e376277d5c75647ddc95c91f379cd3ea0986e3da0b76ba2156ab4c39c175332d683b6384ec90be06ca1ba12ed5a378d03585d8f5b5638529af8af95e97f5bd1cd459e1f0bf3748d6ff9516cf4a8b791cf8cf93b3ad2d92b323de4aced9f3a1ae9d7b747f331673760b7030e46cbbe4ec88b79273761fd4b5738fee71c762ce5e041c0c39dbcd93b385bce4ecd0585710b8738f7ee78cc59cdd0f1cc9e76cbbe4ecc8b79273f676a86be71efdd61d8b393b081cc9e76c67b7dc1b8c782b3967ef85ba76eed1b8cb58ccd9bb80832167fba49f1df15672cede0f75eddca331c0b198b3f70147f239dbcd94b3ad92b3c1d0bc6510b8738fc6a3c762ce3e001cc9e7ec3e199f1df95672ce7e06eadab94773236331671f018ee473b68f6b7cb620393bb45e2308dcb947f3746331673f6fca7a6eece7666e6c01e87e61740b41f74b586740ba5f19dd89e017431be8943630e2ade436f06ba86be7f249a63c16dbc00f80832167bb256747bc959cb34f415d3bf768fdc258ccd9df020743ceee959c1df15672cefe17d4b5738fd6d28cc59c7dda94f5fdc2e3e67e6111e8fe60748b41f747a35b02ba278c6e29e89e34ba65a0fb93d13581eecf46b71c747f31ba66d03d657479d0fdd5e85a40f737a32b80ee69a36b05dddf8dae0d74ff30ba76d0fdd3e83a40f78cd1751a9d9ec7a235558f1add04f0bd3748eedc86eba882e22d63edf74279192f4f3e17143f2740b696276fab55fbde148cdcf7e5c0d3cce07b166c8c84a71978f2c9f384bf475b923f6e788e9bac9866c15613f85560f02b03b6e8d8b44ff672a0c37ea3e0606c4d9eb190015b746cda6f0546d2613f46cfe150fbd17df3bccc302f435b0aafcf68af1738c85e0dd4c9340cd76d346c75f077ea03eaa08cfd7cded231e56a982bf870562fecb70023f9981f7dc6c248199b2d46ae7e2303b6e8d8b6ed3a477c74cc5a1d316b63626cb51869bf0d18297eada3cf581829a3dd2f30f5492d23ed93282ef9d18fd988ce6b0e7478afd7e6606c4f9e313caf6d1623edb70323e90ac0c3753d8c6aafbed8e6b80fc17ca66b165d7fc85e0dd469ad1eaebb09aea70c7d68a1d47b53ecd3933f4f853c5ecf46c2c37cee5a98f2318f7de7b341b2b966b7f9bc152b6cf3d89773f593517d39d91366611666611666611666611666611666611666611666611666611666611666ff99a3e6e55ceb198e2723e95a8087639c3f7cf7943916ce013d06f33ac9cf5b14f238574feb1817593ed7409dffcd0cb33dee58278173e7cb2d1dd33aa5f05ce23aa55ed8277bb86e03d74d31ac3d0979965a3cb6ed3a477c7c5c47e2d31a8da8755a3ec52c073a5c0fd7c4c41395674d0edb8d89d92eece3693b85bc7ecf8f7e571ef527761bc1b56f4b2c9dee933aaa87fde6c88152e79df1ba41e524e77131dfd056f2eb6d0a45eb13aa82e26b05de5330ac51299aafa6b511ed96ed1aac5315fcfbdc74c0df7b83e7ae11c23a746cda5f04dfedb08e3d99cfdfd8feb01db8a95c6bf9b60cb8a9cea4aa611f3f67ca4cf733055c3f1b006f60f9441baecf48fe7e6f68bd48a1049e36e0e158bbc6745f9bc77c4c7abd4887152bd7fd32d56987f87530c42f6ecd1ad91366611666611666611666611666611666611666611666611666611666611666ff99f1fd17c48acf48b778c2384a6b6cc2f90c7a6f11ce8bdd5f356c977b0e90e69c165b3ee333ca4f570db33d68caf88e00d7f3ee782eb9e6d6a2ce25d9ab0b9efb9c3ed3b91cf1bb1fda1c31eb70c4ac9389d1ee3368bf1318297e1dc0c3d51edb2d1edb36f6196d9ec62caa9fe55abf129567ae750a8d89d91e5a2fc231af4beb45eceb578b15535c27413a9c07c777abe07a2cdfdeff62af5dc07e0ad76825df6f168ae680edb582640fd7497cc3c496d64924df0f14f29cd7086aa3b426a4e0f095ea3c0ad7b9ef9932ae156a81633de6f83b6d716b12b04f65785766787ee9dd8d747ebb1db657026b42b65bd076c608d9217d0d947f53355cd77e1724c59ad8751bb1df57e9fa5edefa5e0eea7439fcef0d92f5bfdbe2e9b69875eefc10f2ec31b8d7e3ea93ba02778c16418ca80edef332ac2972f691f69a695d87ceff78ab0ede9f529d27a18f8a5a93ee7a0f20d7fd42d47b00f17ec1754f63fb68af1daff4f580ff82fea203fede1bbcf0f580ff821c72dd97d2f117c3f1896b7c107d6da13aff635d47b9dad6f3bdf7caf5db8f7871bd21d5f93fe8ab969975b951bf635ccf5970fd6e887aee83ece13d5529be63bf90f4b511f31159c81ee6638d8935e5634704f772c777c7477c976245ebcbf177b21d3f1d87c4df116efa9b6ecb176a535de0cbbfafdfe00bcf3dd3d0fd67f2be16df0f511fd4eaf095ea4c8577c135987216ce13f695273bfe4e5bdcfd27bed7bc27799fc3f34befefa6f3dbe3b0bd1a5813b2dd82b6e9fe93ec90be06ca27550fd7a57a140f8a35b1eb3642f770c86e7fafddfa5e0eeaac74f8df1b24eb7f8fc5d36331ebdc99097976323c6bc1d557af8c88d1628811d5c1671deddffff85b1eaf31c7ebb73cbef313fbf66510d3d17a67aa3d7eebba3f596cc518ef4f5aa09fcd3aeadae3d2740d49727d3a3e0fb414ece2f3404b99e2990b8ae399b338386d4fb66c4f1e45dbf596edfa51b42d319798fb14739ffe2f0cfc3f2baa52c0589d02c69a14308e4b01636d0a18c7a78071420a1827a68071520a18b3c0783cafed0cf12978f6ff3dc5de6ba06d86f73984b158168c3c164dbc3cb1f73e689be11d2a25ff7f03ccff17564bb9ff17560ebe3735058cd352c0d89002c6e929609c9102c69929609c9502c6d929609c9302c6b929609c9702c6f929605c9002c68529606c4c01e38929603c29058c27a780f19414309e9a02c60929605cc2cb58289751f370bc47331b14afe37a3e1ee6f77a86ef2774bd4394e3ff9d2dd577e677fab694fb0e3c5cbfc0fbff28beb0f7f471ac4f28f53d7d71fff7341363a15c46aef526b8b665243cae3528795ec642b98c5ccf77e0f38623e171fd3fb8bccfdb0cc5ac1c46aef558a5ae17c4e7f0da1d3163602c94cbc8b5a61f9f371c098febb9c03c2f63a15c46aeb5af59b031129e2e8859a723660c8c857219999edd0a63d655020f3ee3d4e588190363a15c46cdb3822966dd25f0ac8098753b62e61323f224fdbef16e872d8e67e24af59d189071620a1827a58011d72070f45f716b10ba79e35328373e5ce72b6e0d02da667806238c05aeb97fbe58f4f0f2c4ae4140dbab986281cf443c5f2c56010fc7331a59b031121e62c8c1f7a6a680715a0a181b52c0383d058c3352c03833058cb352c0383b058c7352c03837058cf352c0383f058c0b52c0b830058cf85b95e15e31f6f7cbaa316e3beab7ca58b71df5bb64acdb963c973caf04db92e792e795605bf25cf2bc126c4b9e4b9e57826dc973c9f34ab02d792e795e09b625cf25cf7db29d86317e611c7b8cc8d3981c4f1e7d475ba77be0fbe90e9e0c93ef68abd703dfa99c36c6d529605c91024689e3d01ac4721835cf1a269ede1278d600cf194c3c6b4ae0390378d626cf13e6d41925f010430ebeb722058cab53c028719438fac42871ac9c380aa3300aa3301e0fc634f4e1729d19faed520ea3e659973c4f18b3b525f0ac8398d1f7f2bc8c85721935cffae479c298ad2b81673dc46c9d23660c8c85721935cf86e479c298ad2f816703c46cbd23660c8c85721935cfc6e479c2986d28816723c46c8323660c8c85721935cfa6e479c2986d2c816713c46ca323660c8c85721935cfe6e479c2986d2a816733c46c9323660c8c85721935cf96e479c2986d2e81670bc46cb323660c8c85721935cfd6e479c2986d2981672bc46c8b23660c8c85721935cfb6e479c2986d2d81671bc46cab2366be32ae4801e3ea143032c7b1502ea3e6d9cec4b3ad049eedc0732613cff61278ce049eb392e70973eacc1278882107df5b9102c6d5296094384a1c7d629438564e1c85511885b134c6d353c028e75a187d6564f87d15fb7cca9963dc76d4f32963dd76d4f32963ddb6e4b9e47925d8963c973caf04db92e792e795605bf25cf2bc126c4b9e4b9e57826dc973c9f34ab02d792e795e09b625cf25cf2bc1b6e4b9e47925d8963c973caf04db92e792e795605bf25cf2bc126c4b9e4b9e57826dc973c9f34ab02d792e795e09b625cf25cf2bc1b6e4b9e47925d8963c973caf04db92e792e73ed9de91bced42a9cfb0ee009eb31862c1e4675e1ff76c73ac67138c9f8ed53956acceb46295833a6743fcce61885f06ecd2b1699fec95cafc220f98996c174e50c79808fe938dd5563cb4fd73997c8feaebcf1de3b6a3fafab16e3baaaf1febb625cf25cf2bc1b6e4b9e47925d8963c973cf7c536966b82e1fb767abf923ec679a63cceec53fdd3e17b54a765fcd0e7e440da10876d694372ada804db92e792e795605bf25cf2bc126c4b9efb99e7e7276f3b9c1bc3df177a8b9b1b3b1f78ce638805939f79edd305964fe75a3ee5a00ebeb3f602063f3360978e4dfb17c079481bb3e65965cac49a857aab3c6124dd79bc3c61fb5a15146f71edeb02e06168072d4c7e86edeb42cba7558eb8531dccd50b19fc74b51ddabf10ce43da9873608f58b341f139f5819174e7f3f284edab3728dee2dad785c0c3d1ff30f919b6af8b2c9f7a1d71a73a98ab1731f8e96a3bb47f119c87b4316b9e35f077bd65a1de1a4f184977012f4f5b167ca62dae7d5d043c1cfd0f939f61fbda69f9b4c61177aa83b9ba93c14f57dba1fd9d701e8459985dcc9ae70c5326d62cd43bc31346d25dc8cad396cf82cfb4c5f5633b8187a39f678a7bd88f5d6cf9748623ee540773f562063f5d6d87f62f86f3500af3ea14324b9c25ce51cc1267897314b3c459e21cc52c71963847314b9c25ce51cc1267897314b3c459e21cc52c71963847314b9c25ce51cc1267897314b3c459e21cc52c71963847314b9c25ce51cc1267897314b3c459e21cc52c71963847314b9c25ce51cc1267897314b3c459e21cc52c71963847314b9ccb63d63c6b4d9958b3506fad278ca4bb8897277c7e676d50bc65acfd5e285f0c3c3b19e2c3e467b8eefd12cba7b58eb8531d6c5f9730f8e96a3bb47f099c87529857a79059e25c1eb3e65967cac49a857aeb3c6124dd4e5e9eb01f5b17146f71fdd825c0c3d1cf33f919f663975a3ead73c49dea60fbba94c14f57dba1fd4be13c08b330bb9835cf7a5326d62cd45bef0923e92e66e52984cf21ae0f8ab7b87eec52e0e1e8e799e21ef663bb2c9fd63be24e7530577731f8e96a3bb4bf0bce4329ccab53c82c71963847314b9c25ce51cc1267897314b3c459e21cc52c71963847314b9c25ce51cc1267897314b3c459e21cc52c71963847314b9c2b27ce9a678329136b16ea6df084917497b0f2b486f30e1b82e22d6ede6117f070cccb30c53d9c77b8ccf2698323ee5407dbd7650c7ebada0eed5f06e761ac33af4e21b3e4c6e8304b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e486304731fb901b9a67a329136b16ea6df484917497f2f284ef3dd818146f71eb762e039e5d0cf161f2335cb773b9e5d34647dca90eb6afcb19fc74b51ddabf1cce83300bb38b59f36c326562cd42bd4d9e30926e172f4fd88f6d0a8ab7b87eec72e0e1e8e799fc0cfbb1dd964f9b1c71a73a98abbb19fc74b51ddadf0de761b7300bb38359f36c366562cd42bdcd9e3092ee325e9eb01fdb1c146f71fdd86ee0e1e8e799fc0cfbb13d964f9b1d71a73a98ab7b18fc74b51dda277bc22ccc51cc9a678b29136b16ea6df184917497f3f214b2e0336d71fdd81ee0d9cd101f263fc37e6cafe5d31647dca90ee6ea5e063f5d6d87f6f7c279481bb3e6d96acac49a857a5b3d6124dd6e5e9eb07d6d0d8ab7b8f6b5177838fa1f263fc3f6b5cff269ab23ee540773751f839faeb643fbfbe03ca48d59f36c336562cd42bd6d9e30926e0f2f4fd8beb605c55b5cfbda073c1cfd0f939f61fbeab37cdae6883bd5c15ced63f0d3d57668bf0fce43da9835cf765326d62cd4dbee0923e9b09fa2ad0a18b7333106166360c507791678c633cb339ea99ef14cf48ca7da339ea59ef1747bc6d3e519cf42cf78167bc633db339e82673cd33ce359ee19cf24cf78da3de3a9f18c6799673ca779c6b3d2339e399ef13478c6b3c4339eac673ce33ce369f28ca7c7339e0ecf78e67ac6d3ea19cf74cf789a3de3a9f38c27e7194fad673c3b3ce399e719cf0ccf784ef08c67b2673ce33de3e9f48c67be673c6d9ef1ccf48ca7c5339e299ef1d47bc633c1339e459ef1643ce0c906cf5d379085bfef005d95f55d7d7dd9d030fc779aa7ad82eff49b72b5e3d87da0a379dd7ec777314e5c73cf68ab17f6c95e1d70f47bc2b3c8339e099ef1d47bc633c5339e16cf78667ac6d3e619cf7ccf783a3de319ef19cf64cf784ef08c6786673cf33ce3d9e1194fad673c39cf78ea3ce369f68c67ba673cad9ef1ccf58ca7c3339e1ecf789a3ce319e7194fd6339e259ef13478c633c7339e959ef19ce619cf32cf786a3ce369f78c6792673ccb3de399e6194fc1339ed99ef12cf68c67a1673c5d9ef1747bc6b3d4339e6acf78267ac633d5339e599ef12cf08ca7cac1b3838927ead9dd1d9ed866380f797ddcfd4c3e5d618e556b8e4bfc64af06ead49b81473ddf80df252e7bbe1dc796af801871bd372167f1d0febe316e7bb2657b7285d8aeb76cd757886dc973c9f34ab02d792e795e09b625cf25cf7db4fd6c72b6db31cfaac0960feba3e5fd49f13cf2fea4789e6acf78e4fd49f13cf2fea4781e797f523c8fbc3f299e47de9f14cf23ef4f8ae7f1edf978799f533c8fbccf299e47dee714cf23ef738ae769f28c47dee714cf23ef738ae791f739c5f3d479c693f38cc7b7f739c9fb93e279e4fd49f13cf2fea4781e797f523c8fbc3f299e47de9f14cf33c1339e459ef1643ce079bef727e17b8f68ade53ed0d17aceb8f72c65e13857808ec607e918fa7a7576c37319aae03b573ab8f63bec919d2b1ddf1d8db8a3ad5ed8277bf83ea62b3de159e419cf04cf78ea3de399e2194f8b673c333de369f38c67be673c9d9ef18cf78c67b2673c2778c633c3339e799ef1d47ac693f38ca7ce339e66cf78a67bc6d3ea19cf5ccf783a3ce3e9f18ca7c9339e719ef1643de359e2194f83673c733ce359e919cf699ef12cf38c6787673c359ef1b47bc633c9339ee59ef14cf38ca7e019cf6ccf78167bc6b3d0339e2ecf78ba3de359ea194fb5673c133de399ea19cf2ccf781678c653e5e0e17a27522e18de7a617f34dec7f47cb6f5fe52888bdeb2f0f7d1784e7287c548fbb8ce0079896729134fd473ef4b3db0adfda7df5a34679185bfe373385c39b5d462a47d574ee13ac2654c3c51cfeb2ff3c0b68e459329d31c7716fede048c5c39b5cc62a47d574ed5f3f2b465c167dae2d6f6609be338874c7ee6b1fd25f88e88bc8ed5762b564d56ac725067349e138cea0fc89e300b7314b3e6a1b11662c5ebd9683c273d1246d7f5958127ec1f9707c55b5cffb81d7838ae1f4c7e86fdd801cba7e58eb8531dccd5030c7ebada0eed1f70d86e0c928dc5552388c5550e9eab46391664af54e61d2964f621ce9a87d64e102bae276ef68491744b7979c2feb13928dee2fac7ab8087e3fac1e467d8275c6df9d4ec883bd5c1f67535839faeb643fb57c3792885f9400a9925cee5316b1e5a534dac59a8d7e20923e9b6b3f214f259f099b6b87eec6ae0e1e8e799e21ef663072d9f5a1c71a73ad8be0e32f8e96a3bb47f10ce83300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330fbcdac79e8591562cd42bd82278ca4bb8a956768dea110146f71f30e078187635e8629eee1bcc321cba78223ee540773f510839faeb643fb87e03c08b3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3dfcc9a87dee94aac59a8d7ea0923e9aee6e5099fdb6a0d8ab7b8798743c0c3312fc3e46738ef70d8f2a9d51177aa83b97a98c14f57dba1fdc3701e8459985dcc9a87feaf0762cd42bd364f18497790956768feb42d28dee2fab1c3c0c3d1cf33c53decc7062c9fda1c71a73a98ab030c7ebada0eed0fc0792885f9400a9925ce12e7286689b3c4398a59e22c718e6296384b9ca39825ce12e7286689b3c4398a59e22c718e6296384b9ca39825ce12e728668973e5c459f3d0ff5145ac59a8d7ee0923e90eb1f2b486f30eed41f11637ef30003c1cf3324c710fe71d8e583eb53be24e75b07d1d61f0d3d57668ff089c87b1ce7c2085cc921ba3c32cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc5ec436e689e0e5326d62cd4ebf0849174877979c2f71e7404c55bdcba9d23c033c0101f263fc3753bd7583e7538e24e75b07d5dc3e0a7abedd0fe35701e8459985dcc9aa7d39489350bf53a3d6124dd002f4f210b3ed316d78f5d033c1cfd3c939f613f76d4f2a9d31177aa83b97a94c14f57dba1fda3701ed2c6ac79ba4c9958b350afcb1346d2e175b98b892767f1e41cb1385eb6f57eb729d799cf2cfcbd1b18b9fac32e8b91f631c7919778ba9978265b3c931db1385eb6b5ff2b4df904f39985bfaf0446ae9ceab61869df955393816725134fbdc553ef88c5f1b2ad63d163ca53cc6716fede038c5c39b5d262a47d574ed5034f0f134f549fd4330ab6a3dad768d88eca95d1b02d318f8e3943bb0bc7077a82e22deebe1aaf2d1c7d15939f79d7f5bbc7f209afdf788f7abcae4fc22ccc51cc4cf7b96d59cb36c527b07868bb863916a3f93bbbdbf2290dbfb3e3980fa49059e25c1eb3b67d2c79db6d59cb36c527b078683bc61c0b263fc3fe603070c798ece5a00ee6e920839f19b04bc7a6fd41380fa5301f4821b3c4b93c666dfbdac46d0fbd7f186d537c028b87b66b9963c1e3e7507f705de08e31d9cb411dccd3eb18fccc805d3a36ed5f07e74198855998855998855998855998855998855998855998855998855998855998fd66d6b6af4fdcf6d0f83ddaa6f804160f6dd733c782c7cfa1f1fb1b02778cc95e0eeae039bf81c1cf0cd8a563d3fe0d701e84599885599885599885599885599885599885599885599885599885599885d96f666dfbc6e46d87cfe3a06d8a4f60f1d07623732c98fc0cc7ef6f0adc31267b39a883e7fc26063f3360978e4dfb37c17910666176316bdb37276e7b683e0f6d537c028b87b69b9963c1e3e7507f704be08e31d9cb411d3ce7b730f89901bb746cdabf05ce4329cc0752c82c71963847314b9c25ce51cc1267897314b3c459e21cc52c71963847314b9c25ce51cc1267897314b3c459e21cc52c71963847314b9c2b27cedaf6ad89db6e0dc7efd136c527b07868bb9539163c7e0e8ddfdf16b8634cf6725007f3f436063f3360978e4dfbb7c17918ebcc0752c82cb9313acc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc3ee486b67d7bf2b6c3e7d9d136c527b07868bb9d39164c7e86eb5fee08dc31267b39a883797a07839f19b04bc7a6fd3be03cdc21ccc2ec60d6b6ef4cde76216bd9a6f804160f6d7732c782c9cfb03fb82b70c798ece5a00e9ef3bb18fccc805d3a36eddf05e7216dcc78fe32c9d90ed76d928d2af3a9752f36e56ad0bdc4946b40f752531e07ba9799722de85e6ecae3417737f846ba5798f212d0bdd2947b40778f29af04ddab4cb91b74f79a7217e85e6dcad780ee35a67c1474f799f231d0bdd6940741f73a53be1674af37e5eb40f70653be1e746f34e51b40f72653be11746f36e59b40f71653be19746f35e55b40f73653be1574f79bf26da07bbb29df0eba7798f21da07bc0947780ee41539e08ba874c7912e8fe03caf4f94e53ae03ddbb4c3907ba779bf209a07b8f294f06dd7b4d790ae8de67caf5a07bd894a782eefda63c0d741f30e506d07dd094a783ee11539e01ba0f99f24cd07dd8946781ee23a63c1b741f35e539a0fb9829cf05ddc74d791ee83e61caf341f749535e00ba4f99f242d07dda94f1fc7ec694ef041df51777818efa8b17838efa8b97808efa8b97828efa8b97818efa8b97838efa8bbb414779f70ad051debd12749477f7808ef2ee55a0a3bcbb17749477af061de5dd6b404779771fe828ef5e0b3acabbd7818ef2eef5a0a3bc7b03e828efde083acabb37818ef2eecda0a3bc7b0be828efde0a3acabbb7818ef2ee7ed051debd1d749477ef001de5dd03a0a3bc7b10748da6fc10e84e3465ec3f4e32e57782ee64537e17e84e31e57783ee54537e0fe85e64caef05dd69a6fc3ed02d32e58741b7d894df0f3aba867d00744b4df983a05b66ca8f80aec9943f04bae5a6fc61d0359bf247409737e58f82aec5943f06ba82297f1c74ada6fc09d0b599f22741d76eca9f025d87297f1a749da64cfd876ea7babd91cf148f3af0afc9c14dba09c0dd1b247b0f46b6e8d8b45f00468a77cbe8331646ca98b718354f1b43cc3087688bfb8dd3063cad0c3c4c7e86bf71da2d9f0a964f39a8f322f0b39dc1cf0cd8a563d37e3bd8e638e7188b5a73dcd3ac58d4409d59e6e2a5af937171a463e8fc6d71f8c215c7bcc59377d8ee628e231d9bfac4ae51b0dd61d96eb66c63bf4f5b5cdbee00e64e06667ddceee48f1bb6ed15e65894cf64a7197c5a093148ca27b49d314276485f03e5e686e1ba548fe241d74e62d7ed88ce25b2dbdf6bb3be97833a5d0eff7b8364fdefb678ba2d66fd3be19486610e86f610e64097c541fbcd10bbee88d87541eca80e5e7bf34cb1ebb478683f0f3c747fd50e3aba4f217ebc9f6b1a056ebbdf6b777093ae0318f30ec6e6e4190b71d7856660245d27f07430c5cc3ed7a759f1c17b82f1561dfa6e0dd45907d7e5aca3ae6e77f332c37ed1effa678364fbf45a8678e1984300f109ac18d2460c1382e1718924792605c3e30ec706078eeeb9a2ff9cfe3d7d1940abb130f133e370a30a7458ae76e882a0787805876f697805876fabacb0e0b00ed5d73fe3b45b3484d17fe8c0e0f987fb0fef3b7ae391c1febe6d035720f5388b1e49a33c4052d4d13621181e08ea0d929db8a9b56cc525cf04f81c9f3c4f0b939fe1456fa2e553ade5530eea8c83bf4d64f0330376e9d8b43fd1613bc18e288cc5a411c462928367d228c70207d349872d95fe8e132d55962fd8a2d1273bcf1375880c9e0ac7cf1838fd37ddd8c71967c607c3279b7a4f7d47ab4f821e85d5572d3dcaaa47557517a4474df5054d8f8aea51503deaa94739f5a8a61ec5d4a3967a94528f4aea51483dead8180c8d2aea51443d6a780a703d0aacfad7bcbe3aea513f3dcaa747f5f45d95feb5a6ef44f49db7be2bd07703faae4fffa2d5a31bfa4aabef62f4155a5f55f55da2be3bd477f3faee56cf66ad52b25ac9e926d66b949ca164ad92754ad62bd9a064a3924d4a362bd9a264ab926d4ab62b3953c959c1d068fdd94ace5172ae92f3949cafe40225172ab948c94e25172bb944c9a54a7629b94cc9e54a762bd9a364af927d4afa94f42bd9afe40a255706432b78ae5272b592834a0e2939ac6440c9916068464dcfa0e919333d43a667c4f40c989ef1d2335c7a464bcf60e9192b3d43a567a4f40c949e71ba23189a5dd0b3097af640cf16e8d9013d1ba047ffef0e8646f7f568fe3dc1d068bd1e9dd7a3f17af45d8fb6ebd1753d9aae47cff568b91e1dd7a3e17af45b8f76ebd16d3d9aad47aff568b51e9dd6a3d17af4f9a1606874598f26ebd1633d5aac4787f568b01efd7d38181addd5a3b97af4568fd6ead1593d1aab475ff568ab1e5dd5a3a97af4548f96ead1513d1aaa473f3fabe4734a3eafe40b4abea8e44b4abeace42b4abeaae46b4abeaee41b4abea9e45b4abeade43bc1504e7e57c9f7947c5fc90f94fc50c98f94fc58c94f94fc54c9cf94fc5cc92f94fc52c9af94fc5ac96f943ca6e4b74a7ea7e4f74a1e57f207257f54f284922795fc49c99f95fc45c9534afeaae46f4a9e56f27725ff50f24f25cf04c3b324d871d498de8646ecf70c0ef61f3a32d83838d078e8da8383078e1cbcb1f1fa038357360e5cd77f74ffc181ebf1cb5f375fa6e98835478feeb9b1f1c0e1befe1b1a07ae1d6c1cd8dfb877e0dac37dc7f04b4f982fcd7baec53d7d7dd1c6fef38590fe779946ab4d3f48133d9be37dabad2e232075e57c295f5d9e437973a5a1d9f97387ee701b8f1d1c186ccc371e56ffee39a8bed3dfb7bc11ff764c05f9d860e3b1c13d47071bf71f1d38d4d8b21c8f3ba5ae0c27663694f1a5b50d23f73cf87f8198955c01d50300", "isInternal": false } ], - "packedBytecode": "0x000000028df71de50000003d061f8b08000000000000ffed9d099415c779ef7b860106ddb92c4212629134da1020963b7716606680cbbe0b186058b431ec0804320c2024b1ef3b08c42210c28e9dc4cebe2f4ee22cce1e67b193d88e6327b1133bce7befbc73de39efbc735ece5152d5b73ecf7f8aeaabb9a3aecb7767be3ea7e6567f53dddfaffefd55f556ddfdc320084a82ecd443a5a7837b27fa7fc6fca63ed95415e3ba523e394b8a84b3b448387b140967599170f62c12ce5e45c2d9bb4838cb8b84b34f8c9c9aad34683fc5cdfb80075de3664c1499a61545a069b2c834ed5b049af60b8aa38fea5f249c038a84f3c122e11c58249c0f1509e7c345c2f94891700e2a12ce478b84737091700e2912cea145c239ac48381f2b12cec78b84f38922e1ac2c12ce278b84f3a922e17cba48389f2912ce6763e41c019cc3cdef73e697fe37d2fc8e32bfcf9bdfd1e6778ca96399991fabd238cda65295f5bfb44ad52ad5a8546bfe5769fe57a7d2789526a83451a57a951a546a5469924a93559a62ea3e55a5692a4d5769864a33559aa5d26c95e6a83457a5792acd5769814a0b557a41a5452a2d5669894a4d2a2d5569994acb2d96669556a8b452a5552aad56e945955e52e965955e51e95595d6a8d4a2d25a95d6a9b45ea50d2a6d5469934a9b55daa2d26b2a6d55699b4aafabb45da51d2abda1d2a754daa9d22e955a55da6d69b647a5bd2abda9d23e8bf32d95de56e91d95f6ab7440a5832a1d52e9b04a47543aaad231958eab7442a5932a9d52e9b44a67543aabd23995ceab7441a58b2a5d52e9b24aefaa7445a5ab2abda7d23595aeab7443a59b868582fd7d956ea9745ba50f54baa3d2872add55e9d32a7d46a51f53e9b32a7d4ea51f57e92754fa49953eafd21754fa29957e5aa59f51e96755fa39957e5ea55f50e91755fa25957e59a55f51e95755fa35957e5da5df50e93755faa24abfa5d26fabf43b2a7d49a5df55e9f754fa7d95fe40a52fabf4872afd914a7facd29fa8f4a796e67fa6d29fabf4172a7dc5fc8fae75fda54a7f65f27f6d7effc6fc7ed5fc7ecd5ae66f55fa3bcbf6f72a7dddb27d43a56f9afc3f98df6f99df7f34bfdf36bfdf31bfff647effd9fcfe8bf9fdaef9fd9ef9fd57f3fb6fe6f7fbe6f707e6f7dfcdef0f551a372c9b2f0fdaa64c1053bf53b3a145df2721b18707ed27ad450ff33ffaad34f632334fbfa45d4f33dfd3b2f732f3bdacf5949bf972cb3ec0cc0fb0ec03cdfc40cbfeb0997fd8b20f32f383c09e08e07aa9b16b5b0f632a011bc56129d87a06ed35d1b65eb43ab0f50eda6ba16db41d7b81ad8fb1f506db03c6560eb684b1f521cd54aa30b64c10574ca45af47a9371afd7dc43ea1b3fef3abdde7e9e78fbc7cfbb41af7780075e1d1f0f9a75f587b819686c03c0f690b13d08b6878d6d20d81e31b687c036c8d81e06dba3c6f608d8061bdb20b00d31b647c136d4d80683cd747bc110b03d666c43c1f6b8b10d03db13c6f618d82a8ded71b03d696c4f80ed2963ab041b8d4f79126ccf18db53607bd6d89e061bf5a9cf808d8eeb9e3536dd4ffcbf009631f652b03d47fd30d846501f0cb691d4ff826d14f5bd607b1e7c936d34f42b641b636cd447e9ffd5997c2688ab4da4c336313eeef5aa35ebf54e8c7fbde13db7faa04dd70cf8190f5a35987c8ce37aaad0778949e487ec65909f0365a91ce941fb1962d7fb930926df9063b93a6bb9249499e0a87f2688b7fe132d9e8916b38eff49c0117fcc56a725663b3ce51db3cd50d68e3d3ae6e98a313b1f383cc46c9d9f984da72466b3d71c82c01d7b74dcdb1563761570c41fb3b512b31d9ff28ed99d50d68e3d3af7e98a31bb0938e28fd9f175726cd0e129ef983d0465edd8a3f3dfae18b3bb81c343ccb6483fdbe129ef98bd0065edd8a36b315d31668f0247fc313bd153cc564bcc06d9fb4741e08e3dba2ed81563f63270c41fb3eb5ae4d8a0c353de31fb05286bc71e5da3ee8a31fb21707888595fd767d312b3d9fbe641e08e3dba5fd21563f6674c5edf67f86b739f6118d8fec6d81e03db578ded71b07dcdd89e807ac5df06d6d7481be8f094771bf833286bc772a5c977c536f025e0f010b37512b31d9ef28ed96f40593bf69e32f9ae18b35f010e0f313b4162b6c353de31fb7d286bc71e8d69e88a31fb2d93d7c70bff608d77d3b66f19db7360fb47631b01b66f1bdb48b07dc7d84681ed9f8ced79b0fdb3b18d06dbbf18db18b07dd7d8c682ed7bc6360e6cff6a6c29b0fd9bb15581edfbc69606db0f8cad1a6cff6e6c3560fba1b1d51a9bbe2740e353be6c6ce5e02f13c4b76d1350379a4aacf90ce4abfcf2a492c083beaae3f755adeb9e0e3a5ef76ae0a9f150f704f8e8084f0df0d4c6cf53e565df93ca6ee3b4a569027ca5a15e1ef651617f5617b4d794e6c95f126cd856c73b1827c4cf982e015fb46e9a9f008c64ab0546ea4ba9fde8beb95f491baf87b614ee9fd15f0638c85f1994f9e3a16d65071ab60af83ff6a3b596cd535c867141be68dd344ffe2aa03eb585674c7794b1c662f4d54794802f5ab7edbb02f229bffae4e4a963e2bbde936fbb4f23cdeb0be07b82e5bbdaf28d7d274db9f66d784cebe17ca2cad3b172d80f369a75d1790af9c1e38749a0415c7542df749e427ec85e06f9ba92b6b2548ef4a07e98d8751ba66d89ecf672e3ade59250a6de51ff4c106ffdedf3a6068b59ef6f9e877da187f6d0ee5c95d64df3d5a05d438476f5a01d95790eb4f370ec99f31caf0678a8ffc6fe2c55609e1403df780c4bdb0dcfa5f038c0d7f64a598c34efda5e1380b1c6c1e8e198309d6b7f580d8c649b083c694f9a456dd73413df3ece2b4bc0071d9bdbe736655066528fb6b24d255ed9d278ec4c5347cf79e3df4ee914b68f8ef0786e43559ee23185d76f3e0ae28d35bb5fb2fb9ba86b3cbefaf2b4c543f3e44f98855998855998855998855998855998855998855998855998855998855998f9336b1eba9f80e3b6a85c2d13467bfc9bafebfce1fbcaccbaf01ed0f7bc8e074ba7f0de1f8d831869d5b90cca9494b6b1fd00c683d96389a2b6a58fb1b1b9b625f9c3f16085b887586df1d8be2b1cfae07d69d4ccd71828bbcfa0791c9f46faa580c7577bacb2786cdfd867a4996a16d5cffa1ad3171567aef18495b1f94eaff3755f57bf1b4abf5fd1de7fd55a9a86ef6db46ce13df31e6df5f6719f37dffbceb8dfa07c9cf771715c06fa8abf9f4db71b9f501ab4df5760acfb1aab45ed8ac646d45bbecba04cbfd2b66dd300ffcf04f7ee1bb00cad9be647c2b20dd6bafbf9ab6fce7128f88c93fdac15d5ad0eb8a9cc43b0effe0393f7b40f4ce7fb8c04ee93e3dfbf64c78ba4f3e0c1fd9d8f63164ffbd114c663dce345ec7190aee365fb793b9fcfba45ed8b733d9f27ccc22cccc22cccc22cccc22cccc29cfa6493300bb3300bb3300bb3300bb3300bb330c7ccac79ec7be878efb79a096381c6d884f733e81d6b785fec73a56d7e7ddf03a47b4ea3ac3ae333caffa7b48dedf3268f631ff0bd52ae6de9ebde5ad4b6247fdcc63e5438f4d19a3538346bf4c468f71934df088ca45f03f0f86a8f51efee70f51929a69a45f5b3bee23e2ace5cbe2b63f39d1d2fe263fc1a8d17b1fb13aa03eecf6a2c1bde078f7a5f589565bbdf7d806b5c1be5f19d0bf1c74fbadd3d607bac20be5f95ca7cc5684be324e2ef07d2299f6d85da288d09493bea4a65be0afbb9bf35791c2b540debfa9ee3ff34e51a93807deae4f8eb1c6edf29665db47d273b7c67803526df55e8bbc45a37d9cb20ffddd27b19480fd29ad8751bc1f758452d57632d978432931cf5cfc45cffc9160f6e633de9d8f93ac4d9f7e058cf579f342942a391a01195c1635e5fe36aed3ed21e338d63227b5b65f0f894cafc0fe8a3a2c6a4bbc654fa3a5e881acf89c70bae631abb8ef6d8f1ee3e1ef03fa1bf6880ff67824f3e1ef03f21865cc7a5b4fe51b07ee2ea1d44ef5ba8cc7f59fb515f6debe3de7be53af7235e1c6f4865cae0fd5475269fcfb9dffd3a8f779dfbe1725175c77e21ee7d23c623b2e0fb22a94c5fa335c5634304f704c7b203229625adec771fe279291e23c4ffdec86c7f33d9aa0bb5a94950172a3308eae2e798297bfce9eb1d9974ac437d5095a3ae546618b4b5c74d3e01db099f291ae3f83f4db98e3f493f5de7a9f1d739dcbed3ccba68fb4e75f89e0eac31f9ae42df74fc497ec85e06f9d13ddaca5239d283b426f60ae044767bb97a6bb92494c938ea9f89b9fe532d9ea916b38e9d2721cec6c0b316befaea4ce0d6681468f4237f6073bdd7d8b58ff1f53c5ad43ea60a18ed7e13f793857c56cebe7eeb3a3ea1323fdab7233ff4b3094759fbba342d17e7f8747c1ea8dd71618f7bfd6662d63319b4d7336971f8f4ddcff2ddaf80be0758be0714d0b7682e9a73d29cd3377f70df520a8c3ece1bf11a794718f17d04b45c0f60f4f5cc663a0f467ce735eeef88d1c77bc23bfb4d213cd6e9098c3e9e37cef77a752d30e23b0688d1c733daf9bea71a9fdba6e57a03a38fef1ae137943ac2e8fad65139fc7af8ae515567bf0382df3aea038c3ebe0d9208da7fcfe4e318f19b7cb4dc03c0e8e33e5222687f5dede318f19b12b45cc23363ae7dbbe7b13fe97caf4114625c41d4b106faf670fd3f8d63133aa245a35f9e9cc73ee8dbc3f5af508bc6a0e35ae0fd390ff746c3763c290f1ebc8748cb3d088c533c314ece83710a30d272038131e389714a1e8c1960a4e51e02460fd72143c64c1e8c78bd8eec0f03e3344f8c53f3609c068cb4dc23c0e8e39a6202fc7684713a30d2728380718627c6e97930ce00465aee51609ce98971461e8c338191961b0c8cb33c31cecc83711630d272438071b627c6597930ce06465a6e2830cef1c4383b0fc639c048cb0d03c6b99e18e7e4c138171869b9c780719e27c6b97930ce03465aee71609cef89715e1e8cf38191967b02181778629c9f07e30260a4e52a8b80f1c922607caa08189f2e02c6678a80f1d922602c2f02c6e7817161fc8ce179ea823c181702cf0bf1f3849a2dcc83e705bf3ce1fb09173a7c2d8adf573adfba2f029ec5f1f384db62511e3cc49084e55ef0cb98ee2ca3e659123f4fa8d9e23c789680668b1d9a79604c779651f334c5cf136ab6240f9e26d06c8943330f8ce9ce326a9ea5f1f3849a35e5c1b314346b7268e68131dd5946cdb32c7e9e50b3a579f02c03cd963a34f3c098ee2ca3e6591e3f4fa8d9b23c78968366cb1c9a79604c779651f334c7cf136ab63c0f9e66d06cb943330f8ce9ce326a9e15f1f3849a35e7c1b302346b7668c6891179e27e7776b3c3d74a0675270664ec53048c0f140123de4ff7d17fe5ba9fdeec579f7467f5f1b5bd72dd4f47dfab3c69b132e8b816abfcf2e4bc9f8ebe577bd26255d0712d5603cf8b1eb448808f8ef0104312967bb00818071601e34345c0f87011303e52048c838a80f1d122601c5c048c438a80716811300e2b02c6c78a80f1f122607ca20818577866cc75fef26217f71d75aed2d57d479d977475df12e712e7ddc1b7c4b9c47977f02d712e71de1d7c4b9c4b9c7707df12e712e7ddc1b7c4b9c47977f02d712e71cec9f74b1e7c27c0074db9aef1134312965b218c5d9a11792ae3e34961ddd1d7cb0ceafeb283a7c453ddd1d72b0cea4e0cc5c6f8521130ae280246d1313b06b1338c9ae7554f3cafe4c1f32af0acf1c4f36a1e3c6b80a7257e9e30a6d6e4c1430c49586e451130be54048ca2a3e8c8895174ec3e3a0aa3300aa330de0fc662e8c3653f933d7759d30946cdb3367e9e50b3963c78d68266b4dc0b7e19d39d65d43cebe2e709355b9b07cf3ad06cad43330f8ce9ce326a9ef5f1f3849aadcb83673d68b6cea19907c674671935cf86f87942cdd6e7c1b301345befd0cc0363bab38c9a6763fc3ca1661bf2e0d9089a6d7068e68131dd5946cdb3297e9e50b38d79f06c02cd363a34f3c098ee2ca3e6d91c3f4fa8d9a63c783683669b1c9a79604c779651f36c899f27d46c731e3c5b40b3cd0ecd3c30a63bcba8795e8b9f27d46c4b1e3caf81665b1c9a71655c51048c2f1501a3671dd39d65d43c5b3df1bc9607cf56e0d9e689676b1e3cdb80e7f5f879c298da96070f312461b91545c0f85211308a8ea2232746d1b1fbe8288cc2288cf931be5c048cb2ad85912ba387f3ab9ccfa76cebe2bea39e4fe9eabea39e4fe9eabe25ce25cebb836f897389f3eee05be25ce2bc3bf896389738ef0ebe25ce25cebb836f897389f3eee05be25ce2bc3bf896389738ef0ebe25ce25cebb836f897389f3eee05be25ce2bc3bf896389738ef0ebe25ce25cebb836f897389f3eee05be25ce2bc3bf896389738ef0ebe25ce25cebb836f897389f3eee05be25ce29c93efedf1fb4ee7fb0ceb76e079dd83169eea99d2ebdd61d6f5518cfa69addeb0b4da66699584323b40bf373ce857027e69dd344ffef2651ece80d993ef745fb58e3e507ff2f192a587f6ff294f758feaeb3fd5c57d47f5f55ddd77545fdfd57d4b9c4b9c7707df12e712e7ddc1b7c4b9c43917df982f0bda8edbe9fd4a7a1d3b4dbea799a7f22fc37254a6a677f6b75f206dc8876f6943b2afe80ebe25ce25cebb836f897389f3eee05be29c5f9c633ccc2b004f60f10439785e65c6338319cf64663ca398f1a499f16c65c63381194f33339ee5cc789a98f1f460c6b38819cf4c663c5398f14c64c653cd8c6725339e8dcc78c631e359c78c670d339ef9cc786631e3c930e3a967c6339c194f0d339e55cc789632e359cc8c6701339eb1cc786633e319c18c672a339e428cf7cd87a78119cf73cc786a99f16c62c6b39e194f05339e24339e16663ca399f1cc61c6f322339e69cc781a99f1d431e34931e3d9c28c6724339ebecc78fa31e359c68c6709339e85cc78e632e3798519cf74663c9398f1ac66c6339e194f15339ed798f16c66c6b381194f7f663c0398f18c61c6b396194f09039e4470ef3b4512f0ffed602bb596d58f61550c6bfbff2e632f85655a4dbe8763ddbbc046cf76b53a96459d76415d32269ffa6453a813facac03cf9ab008e56263c6b99f18c61c63380194f7f663c1b98f16c66c6f31a339e2a663ce399f1ac66c6338919cf74663caf30e399cb8c6721339e25cc789631e3e9c78ca72f339e91cc78b630e34931e3a963c6d3c88c671a339e1799f1cc61c6339a194f0b339e24339e0a663ceb99f16c62c653cb8ce739663c0dcc78b633e399ca8c6704339ed9cc78c632e359c08c6731339ea5cc785631e3a961c6339c194f3d339e0c339e59cc78e633e359c38c671d339e71cc783632e359c98ca79a19cf44663c5398f1cc64c6b388194f0f663c4dcc789633e36966c6338119cf56663c69663ca398f14c66c6338319cfabcc78e639783c7c1f2ee4a1f1a1b46e9adfcec4b787ed107e176fb7a73aed31ebea65d64bfce4af0ccaa4fb647ff5f8065c96b8ecf1bc786d790f68b4d3535da2de39bcb38bfb8e7ae77057f73dc0f23da09bf896389738e7e47b4ffcbed3f82c084d25d67c06f2b87ff1f10c8da77ab6dbb7c7fdcddbbd96563b2dad92506637e8b7d7837eaee3059a277ff9320f67c08c715119c41b176fc65fa71f7d4b97747dd3d217ebb5cf93a651fb907d5ddc77d43ea4abfb8eda877475df12e712e7ddc1b7c4b9c47977f02d712e71cec9f75b261fe379630a7de86bbf743ef016f87dc7e44b62f4abd7f5b659177dfb9738de011e2a7305ae454b9b97361f976fd9b7499c7707df9ce3dcced33d447cafb9af7bbc51b15888fbcbf7d377542c7675df51b1d8d57d4b9c4b9c73f2bd3f7edfe13dc4ed41fb29d73dc4fdc0f3b6072d3cd5333c773a60d569bb55a72494c173b9031eea59027e69dd347f00b643b1316b1e7ab702be0f91cabdc884916c6ffbe509dbd78b41fb2957fb3a003c1eda4195a77a86edeba055a7171dba53198cd5831eeae96a3b347f10b643b1316b1e7a1718b126a0dc2b4c18c9b6df2f4fd8be5e09da4fb9dad741e0f1d1ff78aa67d8be0e59757ac5a13b95c1583de4a19eaeb643f387603b1c2a3266cd4363c7893501e55e65c248b6037e796a1250679a72b5af43c0e3a3fff154cfb07d1db6eaf4aa43772a83b17ad8433d5d6d87e6c99f300b7314b3e65963f2c49a80726b983092eda0579e9a5402ea4c53ae7eec30f01c8a9d27db8f79d03decc78e58755a13dcab3b95c1583de2a19eaeb643f347603be4c3bca708994567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da39845e7ce316b9e16935f637e1350ae850923d90ef9e5099fdf6909da4f25d67c06f24780e7b0077d3cd5331cf77ed4aa538b43772a83edeba8877abada0ecd1f85ed900ff39e2264169d3bc7ac79d69a3cb126a0dc5a268c643bec9727ecc7d606eda75cfdd851e0f1d1cf7baa67d88f1db3eab4d6a13b95c1f675cc433d5d6d87e68fc17610666176316b9e75264fac0928b78e0923d98e78e54987cf21ae0bda4fb9fab163c0e3a39ff7a47bd88f1db7eab4cea13b95c1583deea19eaeb643f3c7613be4c3bca708994567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974ee3e3a6b9ef5264fac0928b79e0923d98e7ae5a90eef3bac0fda4fb9ee3b1c071e1ff7653ce91ede773861d569bd43772a83edeb84877abada0ecd9f80edd0d599f71421b3c4466198253684398a59624398a398253684398a59624398a398253684398a59624398a398253684398a59624398a398253684398a59624398a398253684398a59624398a398253684398a59624398a39839c486e6d960f2c49a80721b983092ed985f9ef0bd071b82f653ae713b2780e7b8077d3cd5331cb773d2aad30687ee5406dbd7490ff574b51d9a3f09db419885d9c5ac79369a3cb126a0dc46268c643bee9727ecc73606eda75cfdd849e0f1d1cf7baa67d88f9db2eab4d1a13b95c1583de5a19eaeb643f3a7603b08b330bb9835cf269327d60494dbc484916c27fcf284fdd8a6a0fd94ab1f3b053c3efa794ff50cfbb1d3569d363974a73218aba73dd4d3d57668fe346c0761166617b3e6d96cf2c49a80729b993092eda45f9e7402ea4c53ae7eec34f0f8e8e73dd533ecc7ce5875daecd09dca60ac9ef1504f57dba1f933b01d8a8d59f36c3179624d40b92d4c18c976ca2f4fd8beb604eda75cedeb0cf0f8e87f3cd5336c5f67ad3a6d71e84e653056cf7aa8a7abedd0fc59d80ec5c6ac795e3379624d40b9d7983092edb45f9eb07dbd16b49f72b5afb3c0e3a3fff154cfb07d9db3eaf49a43772a83b17ace433d5d6d87e6cfc176283666cdb3d5e4893501e5b6326124db19cf3c09a8334db9dad739e0f1d1ff78aa67d8bece5b75daead09dcaec837a9ef7504f57dba1f9f3c043d33ce0f1159781c51338f4a16906339ec9cc784631e34933e399c08ca79919cf72663c4dcc787a30e359c48c6726339e29cc782632e3a966c6b39219cf38663cf399f1ec65c6338b194f86194f3d339ee1cc786a98f1ac62c6b39419cf62663c0b98f18c65c6339b19cf08663c5399f13430e3a965c653c18c27c98c6734339e39cc78a631e36964c653c78c27c58c6724339ebecc78fa31e359c68c6709339e85cc78f631e399cb8c673a339e49cc785633e319cf8ca78a194f7f663c0398f18c61c653c2802711dc3b762501ffdf0bb6f326bf156c174cfe2cd84a1d3ee8dee379b095993cada3b74a8386ddbb6ed4c9d7b812f4958179f257011c1798f08c61c63380194f7f663c55cc78c633e359cd8c6712339ee9cc78e632e3d9c78c6721339e25cc789631e3e9c78ca72f339e91cc7852cc78ea98f13432e399c68c670e339ed1cc7892cc782a98f1d432e36960c6339519cf08663cb399f18c65c6b38019cf62663c4b99f1ac62c653c38c6738339e7a663c19663cb398f1ec65c6339f19cf38663c2b99f15433e399c88c670a339e99cc781631e3e9c18ca78919cf72663ccdcc782630e34933e319c58c6732339e19cc78e63978f67ae249066d5306e6f732f0ade7eb41173d25e0fff81ce13e4f8c7b2d469adf078cc8eb5bb37e164f3f4bb3fbe95bd79fae5df635bfb8bdf0391e0edbab5f01341b60f10cb034bb9fbeb516746f89c6d8e0f6c2e730386c2f1c87e8a17fae49583c7a2ab1e633903fef591f4ff54ce138c58f625cafd6eaa2a5d55e4bab24943907fa5df4a05f4970ef7baf689efc09b33047316b1eba77e11a3fbb800923d9f039914bf1f3d4242c1e3de5ea1f2f79d6c7533dc37eec72e0d6fd12e84e6530562f7ba86709f8a575d3fc6587efca205e2ddeed8016ef3a78de2db016e42f5fe67345c8cc4167cdb3d0e4893501e516326124db45e0b9123b4f3a95b078f494ab7fbce2591f3ff5ccf6095703b7ee5740772a83edebaa877a96805f5a37cd5f85ed20ccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccbc99350f8d6126d604945bc484916cef02cf7bb1f364ef3b208f9e72dd7778cfb33e7eea99bdef702d70ebfe1ee84e653056af79a86709f8a575d3fc35d80ec22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc2cc9b59f32c3679624d40b9c54c18c9761578aec7cf5393b078f494ebbec375cffa78aa6778dfe146e0d6fd3ae84e6530566f78a86709f8a575d3fc0dd80ec22ccc2e66cd43ef9027d604945bc284916cd780e766ec3cd9fba7c8a3a75cfdd84dcffaf8a967b61f7b3f70eb7e1374a73218abef7ba86709f8a575d3fcfbb01df261be5c84cca2b3e81cc52c3a8bce51cca2b3e81cc52c3a8bce51cca2b3e81cc52c3a8bce51cca2b3e81cc52c3a8bce51cca2b3e81cc52c3a771f9d350f7d5b81581350ae890923d96e00cfadd879aa53098b474fb9ee3bdcf2ac8f9f7a66ef3bdc0edcbadf02dda90cb6afdb1eea59027e69dd347f1bb6435767be5c84cc121b856196d810e62866890d618e6296d810e62866890d618e6296d810e62866890d618e6296d810e62866890d618e6296d810e62866890d618e6296d810e62866890d618e6296d810e62866890d618e62e6101b9a67a9c9136b02ca2d65c248b6f781e783f8796a12168f9e728ddbf9c0b33e9eea198edbb913b875ff0074a732d8beee78a86709f8a575d3fc1dd80ec22ccc2e66cdb3cce4893501e596316124db6de0f9307e9e74c2e2d153ae7eec43cffa78aa67d88fdd0ddcba7f08ba53198cd5bb1eea59027e69dd347f17b6c3dd2263d63ccb4d9e5813506e391346b2dd011e0f7117f2242d1e9aff90816f3ddf6cf215e617b7573330def5c498cff64a1640b37e164f3f4bb3fbe95bd77fa5c9f735bfb8bd5602e35d4f8cf96caf7e05d06c80c533c0d2ec7efad65aac32f9fee617b7d72a60bceb89319fed35c02f4f3a61f1e829d7f1c65de0f94cfc3ce179dcdd3c783e033c9f8e9fa7ca533d537abd3f06ec71ad576bf5594babbb96564928830c9ff5a05f09f8a575d33cf91366618e62c6bef0aef94d40b90f993092edd3c0e3a3dfd0751f6dd645ebefa9d23707b4f9f5717d0daf2df432eb250ef2570665fef79036b66f1bb60af83f6d375d9fdb96cdd3336f55aeebbc344ffe2a02f7b9beaf6ba851d71e6e03cf6d8766ef3b34bbe989d17ea695e66f02a3eb3aafafe745a3ae3be3f3a21f5a3a72d30c9f03be053cbe8e8ba3e2cc779bcbf7f8f29683e7a3f87852181be8cb47ac62dfd691babbda4e8c75afc27b58e8cb431b08f75363ccba68fd7a5ff05f03bc6a5e83fd03eda7c658752e83327f0efba91e0f66f3aefd546970ef75e14a63a732f4ff8f8cdd5e4765707faf2d5540bedefc628c924df6bfedef61d65b3a72d30cdb35ded7f4701c98f39a18f9c36dd8e0d0ac818166d8160a116751d77c30ce1a2c1db9698671c6e15a1e6ec34687668d0c34e3d89f355a3a72d3ccd59fa18e0b1cdc0b187073ec5316583a72d3ccd5a7a08e0b1ddc0b1970736cd70b2d1db969f671ed7a91837b11036e8eed7a91a52337cd3eae5d2f76702f66c0cdb15d2fb674e4a6d9c7b5eb250eee250cb839b6eb25968edc34fbb876dde4e06e62c0cdb15d37593a72d3ece3daf55207f75206dc1cdbf5524b476e9a7d5cbb5ee6e05ec6809b63bb5e66e9c84db38f6bd7cb1ddccb1970736cd7aeb1b59c342bf435bb7cee41343b346b66a019c73ea5d9d2919b669cef41ac7468b69281661cfbb395968edc34e37c0f629543b3550c34e3d89fadb274e4a659547fc6ed99aff8f5c97e7bb7b363767cc494a73848791a67138ec1b6bf69608f39c26f1a781e8b9373cc1cf91366611666611666611666611666611666611666611666611666611666611666feccf8be3abcbf42e53e60c24836bc27e5e33abfaefb58b32e5abf7ede73e5409f7ed3edbeb14bcfa2da1c6550e6dce036b6970c1b3eff8ff764dfb76cf2fc7f9bbf0a873e5ab31b0ecdae7b62bc6131d2fc756024fdf09bd6373cf1d87d98ed1bfb8cf7996a9604db4de0f135c6242ace5ce35b2a63f39d5ee7a7eda453fafd747d82b6fec46e23a8a98f38ccf71dcdd82e7cdd57f650cfb0dfbf6ed5e99655a72494190ef5bc9f6d8ba652e0f1d51f05164fe0d087a679cc786630e399cc8c6702339e6798f1a499f13cce8c6714339e66663ccb99f13cca8ca78919cf83cc787a30e359c48ca70f339e99cc78a630e399c88ce759663cd5cc789e60c6f33c339e95cc780633e319c88c671c339e0798f19431e399cf8c6716339e0c339e7a663cc399f1d430e3a964c6b38a19cf10663c0f31e359ca8c27c18ce70e339ec5cc781630e319cb8ca727339ed9cc784630e399ca8ca78119cf73cc786a99f13cc98c6728339e8799f15430e34932e3e9c58c6734339e39cc78a631e36964c653c78ce729663c29663cc398f1f81eb7972fcf23cc784632e359c68ca72f339e7ecc789630e3e9cd8c6721339eb9cc78a633e399c48c6735339ef1cc789e66c653c58ce731663c8398f18c61c6d39f19cf00663ce5cc784a18f024827b9f15c2e7d36e818d9e69b90db652c7fa681c2c95d7c7554f0cbb77dda58e75df7030a04ed7a02e19934f7db2a9dd733c2566bd344ffe2a80e306139e72663c0398f1f467c6338619cf20663c8f31e3a962c6f334339ef1cc785633e399c48c673a339eb9cc781632e3e9cd8c6709339e7ecc78fa32e359c68c6724339e4798f1dc64c6338c194f8a19cf53cc78ea98f13432e399c68c670e339ed1cc787a31e34932e3a960c6f330339ea1cc789e64c653cb8ce739663c0dcc78a632e319c18c6736339e9ecc78c632e359c08c6731339e3bcc7812cc789632e3798819cf10663cab98f15432e3a961c6339c194f3d339e0c339e59cc78e633e32963c6f300339e71cc780632e319cc8c6725339ee799f13cc18ca79a19cfb3cc782632e399c28c6726339e3ecc781631e3e9c18ce741663c4dcc781e65c6b39c194f33339e51cc781e67c69366c6f30c339e09cc782633e399c18c671e339e528b07ffafaf35d078aeeb60a3ff7fcedc2cef67ea71ddf217433d5251df9ef1a5194e19872651df9ee1c0338319cf64663c1398f13cc38c27cd8ce771663ca398f13433e359ce8ce751663c4dcc781e64c6d38319cf22663c7d98f1cc64c6338519cf44663ccf32e3a966c6f304339ee799f1ac64c6339819cf40663ce398f13cc08ca78c19cf7c663cb398f16498f1d433e3a961c653c98c6715339e21cc781e62c6b394194f8219cf1d663c8b99f12c60c63396194f4f663cb399f18c60c63395194f03339ee798f1d432e3799219cf50663c0f33e3a960c69364c6d38b19cf68663c7398f14c63c6d3c88ca78e19cf53cc7852cc788631e3b9c98ce711663c2399f12c63c6d397194f3f663c4b98f1f466c6b39019cf5c663cd399f14c62c6b39a19cf78663c4f33e3a962c6f318339e41cc78c630e3e9cf8c6700339e72663c250c78a2bef542ffef01b6f74cfe0ed8ae9afc4db05d31f9eb607bd7612b75b090bff7c046e34aae828deeb55c011b5dbf225ffa78eed961f7b2963aead4c3c17ad551a7f71ccbe276a4653241bcdb117d65609efce1b768de63c253ce8c6700339efecc78c630e319c48ce731663c55cc789e66c6339e19cf6a663c9398f14c67c6339719cf42663cbd99f12c61c6d38f194f5f663ccb98f18c64c6f308339e9bcc788631e34931e3798a194f1d339e46663cd398f1cc61c6339a194f2f663c49663c15cc781e66c6339419cf93cc786a99f13cc78ca78119cf54663c2398f1cc66c6d39319cf58663c0b98f12c66c67387194f8219cf52663c0f31e319c28c6715339e4a663c35cc78ea99f16498f1cc62c6339f194f19339e0798f18c63c6339019cf60663c2b99f13ccf8ce709663cd5cc789e65c6339119cf14663c3399f1f461c6b388194f0f663c0f32e36962c6f328339ee5cc789a99f18c62c6f338339e34339e6798f14c60c6339919cf0c663cf398f1945a3c788d3c0536ca57818df269b051be1a6c94af011be56bc146f93ab0517e3cd8283f016c949f0836cad335377c9ea610efed245fb46e9abf068c343e0db709e51b80fbb265d3dc973c715fb6b869fe1230521d2e838df28dc07dd1b269ee0b9eb82f5adc347f0118a90e17c146f94960a3fc64b0517e0ad8a6803fb2517e2ad8283f0d6c949f0e36cacf001be567828df2b3c046f9d960a3fc1cb0517e2ed8283f0f6c949f0f36ca2f30bf7a1b9fb76c7a1b9f33f94c10ef36265fb46e9a3f078cb4bdcf838df20b81fbac65d3dc673c719fb5b869fe0c30521dce828df28b80fbb465d3dca73c719fb6b869fe1430521d4e838df28b81fba465d3dc273c719fb4b869fe0430521d4e828df24b80fbb865d3dcc73c711fb7b869fe1830521d8e838df24dc07dd4b269ee239eb88f5adc347f0418a90e47c146f9a5c07dd8b269ee439eb80f5bdc347f0818a90e87c146f965c07dd0b269ee039eb80f5adc347f00180f99fc41b0511edfb3b1da13e34d8bf1a6e5bb02f2788cb6dfb269c6773c31eeb71869fe1d60244df703cf7e4f3c072c1edb77127459cd54b324d80e008fafb670cde2b966f9c66d88c7d4f596cde776adb718697e3f30da6da11cf271f3acb6786cdf49d0e50053cd5c6dc1677f76c9e2b964f9c66d88e7400d96cde7766db01869be1e18edb6500ef9b879a2fa33f297045d5633d5ccd516ca812d6e9e0b16cf054b0b7c0e08cfc3565b369fdb35aaff680046d2aa106d332acec85f12746964aa59126c85d86f9eb378ce59be711be279f302cbe673bb2eb018691efb5cbb2d94433e6e9e468bc7f69d045d0e30d5ccd516ca812d6e9e3316cf194b8b0ac8e3758e0396cde7768dea3f160023695588b6191567e42f09ba2c64aa59126c1867bef69ba72c9e53966fdc86785d6a9165f3b95d17598c348f7daedd16ca211f37cf428bc7f69d045d1a996ae66a0be5c01637cf098be784a54505e4f13a62a365f3b95da3fa8f45c0485a15a26d46c519f94b822e8b996a86e72a1867bef69bc72c9e63966fdc8678dd778965f3b95d97588c348f7daedd16ca211f37cf628bc7f69d045d1632d5ccd516ca812d6e9e2316cf114b0b1ce786d7e9175a369fdb35aaff58028ca45521da66549c91bf24e8d2c454333c57c138f3b5df3c64f11cb27ce336c4fb2a4b2d9bcfedbad462a479ec73edb6500ef9b8799a2c1edb77127459cc5433575ba800b6e5c0bdd8b2f9d436aa0d2f05c6264b5b9fed236a5b2f061d4997654c35c3f305d7fea319b8975b369fda2eb7b8975bdabae2b11cf271f32cb3786cdf49d0a589a966ae78c431d62b81bbc9b2f9d436aa1d2d07c66596b63e8fafa2b67513e848ba3433d50c8f9b9739745c05dc2b2d9b4f6d575adc2b2d6d5df1580ef9b8799a2d1edb77127459c65433573c96035bdc3c51f7215615c077d4b5e942f88eba5e5908df51f7180be13beaba46217c479deb16c277d475c342f83e64f93e5440df51e3c00ae13b6a6c50217c478d17e9eaed5bfaf3c2f7e7f7b36fe9aefdf921cbf7a102fa963e35ba4f5d16bfef74027cd05462cd67208fd7dd9a3d68e1a99e293c37fa28c6f5bace31edf3203cc7c473b5fb755e5e8ccc181725f1f94ee1f508fc961a5d1f48818dae0f55818dae0fa6c146d787abc146f7266ac046f7c56ac146f764e7818dc603cc071b8d45b900361a07751e6c3406ef1cd868fce759b0d1d8e33360a371efa7c1f6b6c99f02db5b267f126cfb4cfe04d8de34f9e360db6bf2c7c0b6c7e48f826db7c91f015babc91f06db2e933f04b69d268fd7723e65f2785dea0d93c7eb7b3b4cbe0e6cdb4d7e3cd85e37f90960db66f213c1b6d5e4f11b7daf99fc55b06d3179fc1edf6693bf0cb64d267f096c1b4dfe22d83698fc24b0ad37f9c9605b67f253c0b6d6e433606b31f9a9605b63f2d3c0f6aac94f07db2b263f036c2f9bfc4cb0bd64f2b3c0f6a2c9cf06db0a939f03b6174c7e2ed83e34f98360bb6bf278cdaad4e4f1fa34bd2f06efe9d03bebf0fe14bdc717ef27d3bbe9f1be3c7daf07c7d1949b3c8e47a2f7f8e0f8417a371d8ec34c983c8e9bae30791c7f9e34797c5e84be4583cf2ad1f772f683adbfc9bf0336fa8edfdb607bd0e4df021bbd2f6e1fd81e32f937c1f6b0c9ef051b7d7b650fd8e87b74bbc146ef886905db6093df05b62126bf136cf4fef74f816d98c9bf0136faeedc0eb03d6ef2dbc146ef377b1d6c9526bf0d6c4f9afc56b03d65f2af818dbee7b6056cf4ce8ecd60a3f7866d02db7093df08367a7ffa06b08d30f9f560a3eff0ac031bbddb652dd89e37f916b08d36f935601b63f2af828dde35fd0ad8e87d872f838df6c52f818df6c52f828df6c52bc046fbe217c046fbe20fc146fbe2bbe657b73fdd2e6f99f94c10df718ff6773b683fe53af62606e489f35836093ce8eb66ec754f87c7cdef9b75959af5521cdc04dfd763f79d3d66bf61d6d5d3acf7bae5bb0ccabc3fb86ddb5c83ff67a00eb41c96a175d3fc5858f69ab5ee7ea6be373cd5f7bac544dc378089cadc19dc5676a1e92ccb619918d9c2f34f8ab50034c429037962f0a3553a3c1f783f0f9e1bc0137f3bc99e0ffb88096c5b719f0fdbd751ec584b4299eba09faff7d5dcb078689efc09b3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300b337f66cd43f713883501e56e3161241bdeebf2719d1fefa5e23daf71705fc7f7fdbd5e66bde3ac3a9741997786b4b1559b7c05fc9fb65bd4b6f4709f30e7b6247ff8ae7bbc17e4615be61c1f7b03786e3834bbe6d0ec3d4f8c769f41f3f82d06d2ef1af0f86a8fd72d1edb37f61937986a16d5cffa8afba83873f9ae8ccd777a9d9fb116e9941ee3a4c772d8fd09d501f767b72d1bde07af80653f30bf09d0e703d0e77ef601f6b804eca76e0363fcf1936e770f98fa7e1afb40fe700cc40aa32d8d8188bf1f48a77cb6156aa3f6d80aac2b957909f673af983c8e03ba05eb7addf17f9a728d49c03ef54afc750eb7efbb665db47daf387c5f06d6987c57a1ef1293c80fd9cb20bf6d485b59fbfb2fa435b1e3376a90dd5eeeb6b55c12ca5c75d43f13c45bff2b16cf158b59c74e0bc4d9eb70ace7ab4fba1aa1d158d088cae031af873145ce3e9238c89f2e43dbbfb755068f4fa9cc6ee8a3745da89fa77ae23826dc07f83a5eb865d58fe6f178c1754c63d751c7c7ca816dbcdd7dacdf71e82faec1ff33c1271feb771c62c8755c4aeb1f07eb27aede41f4be85ca9cb6f6a33ece27715bda7ae258422a731efaa12f77e2bcee7e9da3479dd7f9f8a61d9e6bd0946bff8efb181ffd8ba77aa65cfbae6b569d92506638d4d3c3718c73bf45f357c0b7afef18920f3a86ba6e695106653eb4fa8e281df1bac48d82d425ed3c1e1ce7a80b95f99cd54fbdeb81c9e776c3e32cbdde9b8eba52992f40fff7d3703c4fdb09fbd22f3afe4f53aefe80f4d375be187f9dc3ed4bcf19d2f6bde8f07d1e5863f2ddee9d0f74bc4f7ec85e06f9df84fd3795233d486bfc46223d3787ecf672d7ade59250e692a3fe9920defa477dff91fce9d8f93988b32fc2f1beaf7ef3528446e340232a83d756eceb2e51fb7d1f63e973edf76f02a3dd6fe2b18b4fb6f72d36fb7ab9eb7890cad0b2783cf8a7d0cf261c65edfb003ecec53ec9f31d1e74ae419da99f223f63c06eeb4cff739d337ccdda9ff9be1741dcf6b9009e8f10b7bd9fc1f3916f58e7b4f6f9225eeba467e43c1d93d7e07534dac7d8c7e4781ded3bd6f110d989eb8ea32ea401952d85ffd3ff3eb2cae275b75ccbd979d7f9f8875639971f2fcff655a55236bf7dfe85d7bdf1fbdab6aed40e30e6685d765ba165f17cf07f597d925d56f749ff35a04d1fda8ef88ce66dcbe6e79e41b6cdddb6ea6f5fa7d58cf6b5205fd751f2ed4b7d5f67f3d447a7f0da45796ceb9db8d675cde596a55532b8f77a0697fb6cf169915eefbae7e8d2a290633ba2b470dd238e4f8bba75ae7ecea5c57b0e1e5fd777a3b478cfe13b462d36baae89b8b4b8eae0f175be1da5c55587eff8b418efbc5ee5d2e28a83c7d779579416e42f5fe6f71830975bf9787cd784fdfdbb1dd0e25d074ffcd7a4726bf1aec3777c5a54d569df973ba0c565078faffb98515a5c76f88e4f8b0913b5ef4b1dd0e29283e75281b5b8e4f01d635c6cd0be2f76408b8b0e1e0fd71a736a71d1e13bc6e3c3b08d5ce88016171c3c170aacc50587ef18b568d1becf77408bf30e1e5fd741a3b438eff01d9f166b6bb5ef731dd0e29c83e75c81b538e7f01d9f162d13b4efb31dd0e2ac83e76c81b538ebf01de339541817673aa0c51907cf99026b71c6e13b3e2dd687c75aa73ba0c56907cfe9026b71dae13b3e2d52e13ef55407b438e5e03955602d4e397cc71817e1f9e4c90e6871d2c173b2c05a9c74f88e713f12c6c5890e6871c2c173a2c05a9c70f88e4f8b8de1f5a7e31dd0e2b883e77881b538eef01de33597302e8e75408b630e9e6305d6e298c3777c5a5487fbd4a31dd0e2a883e76881b538eaf01d9f161bc27b62473aa0c51107cf91026b71c4e13bc6e3ceb0bf38dc012d0e3b780e17588bc30edf311e7786d72f0e75408b430e9e4305d6e290c3778c7d6778dc79b0035a1c74f01c2cb016071dbe633cee0cb538d0012d0e38787c7de33e4a8b030edf311e7786fb91fd1dd062bf83677f81b5d8eff01d635c847de73b1dd0e21d07cf3b05d6e21d87ef18af6b857de7db1dd0e26d07cfdb05d6e26d87ef18cf47c26b7c6f75408bb71c3c6f15588bb71cbe63bc57141e83efeb8016fb1c3cfb0aacc53ef0edeb791df24163b1465b5a944199db43b3bf34162b4a475a871e3373db51973763af4b765cd9de88babc0975a1329f81ba94833d4e264f750d63668f59178d4dffc051572af31343dbca7edee413b04deec0ba7eddf17f9a4aacf90ce4493f5de7d6f8eb1cc62a7ddb81b66fabc3f74e608dc97715fa2e3189fc90bd0cf2bf36b4ad2c95233d486b62d76d84be6d81ecf672fbace5925066b7a3fe9920defab75a3cad1673f8dc03c419c5919fbe2bcbb43b42a3d1a01195c1317b1f78e2b1c7101207f9d36568fbf7b6cad0b26550e677a18fc271a554cf4470efb8495dbf3d9eea47be68dd344ffe9260bb098c761d757c7c13c67ed2b73de8bb1fda46dff1c0ef6dd559365dd7f19eea4abe68dd343f1e18e9bb227585674c7794b1d662d43c133d689600bf34e5da5f4c049e091e783cd533dc0fd55b751a6fd5290965f0d9c67a0ff52c01bfb46e9aaf07df3eb6396a41fbe41196166550e63bd6f163948eb40e1dbf758ebaf8d2b1d6e2a975f89ee459475a37f589930ae0bbc1f25d63f9d66d1b634c4fb9da760330377a60d6eb9d1cff7ac3b64ddfc3a378263f3550a70c6810579dd07789b56eb29741feff0ebd9781f4a07d27b1eb7684b113b5dc446bb9249499e4a87f26e6fa4fb678701beb491f37fc071c577a680f610c4cb23868be06b49b1ca1dd24d08ecae0beb7ce93768d164fa3e55bf3d0f1553dd8e83885f813f0ff7401b8ed7eafdec14d36fc96a1eb38ab367ec69cc759b5c048b646e069f0a499bdad4758fae031416fab0c2d5b06651e301f40a467b5ecb2baddf52b69ab177d2333c6ef49857d7a2f0f7ae1f73b03d027b034a48918ca83b66f7cc6c9f340d0f60dcf5dad3b76b66cdad0b4217bdb93d0ca2c4cfc2d7154a3146c98efe1b00541fb4f9596818d3e55da136ca5962cf889542a4f9f4ef42117ea41eb2eb338cb81254edff899579a72854e6fe0f111ca3a74e853af267456ecdcd2ba01e3a3a7c5d999d8d1ffeb91a35cd4ba280eca3cd41d9968dd344ffeb43e49937fa365ddd6a93b37ed7e7dc3f6d65d086b372ecc975822d8bfae653048b031d17a7a5ae2f4895f9c1a6cbc365f00fe0260090c4f79fc3c61a0d2b781d7b56cdbb678f7da6d5bd6cddabd7d5deb961ddb51d1de9672516adb4d5e4faeae0ecbdae57b396cf6845f62ee0d36fa127339d8c87f1fb011c703f03f7b4b786913c361fd14c6fa7f65a6c2bd4c8528046937a4fb13dd66f46b53f5ee5f7ffa595f99d39b4fbf2d4e7fba597faaf99120fb29e64783eca796f5d3ddfa485c1f39e84f25eb4f23eb4f215706d94f1d3f15643f65fc4cd0f6a9623d7d1978f5a789f5a186fef4b0fed4f0f341f652e19820fb5a35fd1a0b7dc8aa4f85f5619e3eadd187e0fab04b1f6ee9cb05fad2913e8cd18788faf0471fb2e843707de83dc5683d35c87eaa5c7f9a7c4690fdf4b8fed4b8feb4f89c20fbe9f0792acd0fb29fd55e18643f1fac3fc5ad3ff9ad3fddad3f11ae3ff5ad3f1fae3f0dde1c643f3bbc32c87e6e7e7590fd34b1fe64b1fe94b1fec4b1fef4f19a20fba9e4b541f6d3caeb83eca798f5279af5a79b3707d94f3deb4f406f0db29f8c7e3dc87e625a7f7a5a7f925a7faa5a5f66d7b717f465777d495b5f72d5b73df4ed1e7d3958dfa6d4b76df56d6c7d5b5f0f73d0c33e0e05d961414782ecb0313d8c4e0f2bd4c32cf5b0d353417658b21ea6ad87adeb61fcfab106fd98877eec453f06a41f8bd28f89e9c7e6f4a38ffa5142fd68ac7e54583f3aad2ff5eac7cef5ed3f7d89575feed69781f525e9bb2a7d5aa5cfa8f4632a7d56a5cfa9f4e32afd844a3fa9d2e755fa824a3fa5d24fabf4332afdac4a3fa7d2cfabf40b2afda24abfa4d22fabf42b2afdaa4abfa6d2afabf41b2afda64a5f54e9b754fa6d957e47a52fa9f4bb2afd9e4abfafd21f04d9f8fb4395fe48a53f56e94f54fa5395fe4ca53f57e92f54fa8a4a7fa9d25fa9f4d72afd8d4a5f55e96b2afdad4a7fa7d2dfabf47595bea1d23755fa0795bea5d23faaf46d95bea3d23fa9f4cf2afd8b4adf55e97b2afdab4affa6d2f755fa814affaed20f83b6cf8d6367d1d7f4304f9af996d6d60dafbfd15ad9baa3f2f5dddb5ab7bcb16d5fe5de2dad9b2b77ecd9b073e3b61d7b71e1bf300b0f36f35377ee6cd957b965fbfa0d6f56eed8dd5ab96363e5da1dbbb7af6fb773fc0fb3d0b07b3db6ac5f1fedecff7f12d28f3ae93469fa3efae2fcdcdc75ebdfa313823cd29985ea7b74ae42d5668f43a75d4bb3c77895bbb6ed68ad4c556e577fd5ce74c7de0debc756e2ff76299177b556ee6a6dd9d95ab971e78ed72babc6e27aabfa74a212eff6f10373d350742a549a8774a226ad433ab7398e0df904a4a73ae9f44e676af8d9ce2cf4279d24fc6ab42cbb76af6dddd9b2ae357ae1af7f9285bfdd996afecf4e56f3d6d04e38fb746716fad2d0ce117ebb33cefa0cebb8b3e0bf0114e4d33b244205009b2d6c6f000000263d1f8b08000000000000ffed9d07741c459ac75bb22ccb1e8d6559ce5198e024dba351b66c2ce7c89a643060c0191b6ccbd822c76513bb2cbbb039b26c860536e71cd8c8eec2b21936c212eebd7befeedd7bc7ddbd77c75d554f7da7ff14d58d46f427576bbe7eeff3547faae9ef57fffebabaa7babafd7c100415416119a16c4ef0d285fede633e73af6c694e705b394ece8a947056a68473444a38ab52c23932259cd529e11c9512ce9a94708e4e9053b35506c54bd2bc6318744d9a3193324d6b53a06936659a8e4d81a675413afaa87129e1ac4f09e7f8947036a48473424a3827a68473524a3827a784734a4a38a7a684735a4a38a7a78473464a3867a68473564a3867a784b331259c27a584734e4a384f4e09e7290972ce03ce53cde769e673aef9a43af3cde702f3b9d07c3699365699f545ca162b5ba2f9acbfe91b0d79652dca5acddf1acddfda94b52beb50d6a9ac4bd95265ddca96295baeec74652b4cfb572a5ba56cb5b235cad62a5ba76cbdb20dca362adba46cb3b22dcace50f62a655b959da9ec2c65672b3b47d9b916cb3665e7293b5fd976651728bb50d945ca7628bb58d925ca2e55b653d92e65bb95ed51b657d93e65fb955da6ec80b283ca2e577685b243ca0e2b3ba2ac57d95165572a3ba6ecb8b23e4bb3ab945dadec1a65d75a9cd729bb5ed90dca6e547693b29b95dda2ec5665af56769bb2d7287badb2d7297bbdb23728bb5dd91b95bd49d91dcadeacec4e656f51f656657729bb5bd9db94bd5dd93b94bd53d9bb94bd5bd97b0c0b25fb7b95bd4fd9fb957d40d90795dda3ec43caee55f661651f51f651651f53f671659f50f64965f729bb5fd9a7943da0ec41650f29fbb4b2cf28fbacb2cf29fbbcb22f28fba2b22f29fbb2b2af28fbaab2af29fbbab26f28fba6b26f29fbb6b2ef28fbaeb2ef29fbbeb21f287b58d90f95fd48d98f95fdc4d2fca7ca7ea6ec11653f377fa331a45f28fba5293f6a3e1f339fbf329f8f5bdff9b5b2df58bedf2afb9de5fbbdb23f98f21fcde713e6f349f3f927f3f967f3f917f3f957f3f937f3f977f3f994f97cda7cfec37c3e633e9f359fcf99cfe795d54f28946b82fea52748a8df69ddd7a5ef3f90d8a706c58bd66284f91b7d361a7f9559a74fd26ea4591f69f9abcd7ab5b59d1ab35e63f9ebcd7abde56f30eb0d967fa2599f68f9279bf5c9e0cf04300e69fcda37c2b82ac047795809be9141b126da574d9b03dfa8a0580beda3fd580dbed1c6370a7c638caf067c19e31b4d9a29ab35be9e20a99cc8edd2dbcd26bd5d736f666cf2bc7bf476eb9878c725cfbb4f6fb79e8157e7c778b3ad7190370dc6570f3ed3ad04e3c137d1f81ac037c9f826806fb2f14d04df14e39b04bea9c637197cd38c6f0af8a61bdf54f0cd30be69e09b697cd3c137cbf866806fb6f1cd045fa3f1cd02df49c6371b7c738caf117c271bdf49e03bc5f8e6808ffacf93c147d770a7189fee135e08e03bc65f09bed3a8cf05df5cea6fc1378ffa5af0cda77e167c0b2036f916421f42be26e3a3fe48ffaddd947b82a4f23fbf576fb723e9edaa2debed7625bfddf0bed5d2a05fd71e88d3015a759b728273639a317685318a43fe2a28af87ba548ff4a0730ab1eb7347a72977c77cafddfa5e16ea743adadf1324dbfe2e8ba7cb62d6f9bf0c3892cfd99666c9d9012f25e7ec36a86be71e5ddf0cc79cdd041c0c39dbc693b3f99ce46c617c2108dcb947d7b8c33167b70347f239db26393bf0a5e49cbd12eadab947bf738663ceee078ee473b6a34dae0d06bc949cb3b7405d3bf7e8b7ee70ccd93ee060c8d92ee96707bc949cb377425d3bf768dc6538e6ec6dc0917cce7631e56c8be46c50b8571404eedca331c0e198b3770147f239bba74bae0d06bc949cb3f7435d3bf7683c7a38e6ec3dc0c190b35ce3b379c9d9c23df22070e71edd1b198e39fba029ebfb0c8f9afb0c33c0f798f1cd04dfaf8c6f16f81e37bed9d0aee48f81bd2d720c0c7829f918f829d4b573b9d19487e331f06de060c8d936c9d9012f25e7ecefa1ae9d7b734c7938e6eccf818321673b246707bc949cb3cf405d3bf768fec270ccd9274c595f2ffcd15c2f9c06be278c6f2ef89e34be79e0fb93f1cd07df9f8d6f01f8fe627c0bc1f757e36b02dfdf8c6f11f8fe6e7c8bc1f794f12d01dfd3c69703df3f8caf197ccf185f1e7ccf1a5f0bf89e33be56f03d6f7c6dc6a7ef09d0fc94878daf06dad41324b76fc3392941f15261adf740b9899727970d8ae75553acc5c9c76ad16d5f140cbced8b81670943db331063203c4b8027973c4f38f7a239f9ed86fb7891a56906622d8276e519da5501b168dbb44ef1b2e0c3fe20ef606c499e315f01b168dbb4de028ce4c3fe89fa573a7e74df3cb6a29f97e1580acfcf18af0738285e15d479a8a1bfee78c3560b7fa73ea016cad87fe72c1f53ae36e3fc7eda36ad370323b53137f48cf981322eb118b9fa8d0a8845dbb663d73af4d19ab538346b65626cb11869bd151849bf96a167cc0f94d1ee1798faa4e681f649a44b6ee8351bd07ecd820fafe15a1d8c6dc93386fbb5d562a4f53660245f1e78b8ce8751c7ab2fb139ae43309fe99c45e71f8a5705754e1dd15f77299c4f19fad07ca9d7a6d8a727bf9ff2393c9f0d848779df3533e5630efbce17836473cd3ee673965678cc635fced54f46f5e5144f98855998855998855998855998855998855998855998855998855998855998fd678eba2fe79acf702219c9d70c3c1ce3fce17b7cccb6f01ed023705f27f9fb16f91cdeaba7798cf3ac3657419d7faae8677bd4314f02ef9d2fb67c4cf394c27d89f3947a609de2e1bc0d9c37c530f724e45968f1d8b16b1dfaf8388fc4a7391a51f3b47cd22c0b3e9c0fb78889272acf1639623726163bbf87e7d8c9e7f43b53f47bc7a83fb18f119cfbb6c0f2e93e69de88fe7673e440a9f79df1bc41e524efe362be61ace4e7dbe48be6275406c5e70abca66098a35274bf9ae646b459b1aba0ce7f54f4ef9b76f87b4ff0d239425887b64debf3e0bbedd6b6ebf8da1bdb1fb6013795abadb6350137d5f96f38773f505928335dcfe471fe6c00bc81d5265a707e46f2d77b85f922f912785a818763ee1ad3756d0ef331e9f922ed9656aeeb65aad306fa25febc90393eecdf3cb44ef184599885599885599885599885599885599885599885599885599885599885d97f667cff05b1e233d2cd9e300ed11c9bf07e06bd8f08ef8bdd59d91f97fb1e20dd739a6fb5199f517ea2b29fed6e53c67704b89e77c77dc9756f2d6a5f52bcdae0a5cfe933edcb01bffba1d5a159bb433386f7a835bbfa0c5aef0046d2af1d78b88ec7368bc78e8d7d46aba79a45f5b35cf357a2f2cc354fa131b1d885f9221cf77569be887dfe6ab634c57912e4c3fbe0f86e159c8fe5dbfb5fecb90bd84fe11cade4fbcd7cd13d607bae20c5c379125f32dad23c89e4fb817c8ef31c41c728cd09c93bda4a75be0ee7b96f9a32ce156a866d3de2f83b2d717312b04f1dea775a626c1fde69f9b3cafebaf6bb2949eb52df6999b3bee7e33b2dbf0379f6085ceb71f5499d815ba379a011d5c16b5eaef3973d67db9eab8cfde828ab0e7db70aeafc1afaa852de15c575bd10750ec0eb05fbbc3090b9e3e53e1ff069e82fdae1ef3dc12b9f0ff834e490ebba94b63f1fb64f5ca382e8730bd579ce3a8f32ccd71bd07baf5cbffd8817e71b529d7f86be6a9699971bf53bc6f59c05d7ef86a8e73e281e5e5395d276ec17923e37623e220bc5c37c7cc1cac7f608eec58eeffe57c477492b9a5f8ebf936dfdb40e9df09d9e447428f4375d565be898ea84b6509dffb58e9be4af990ad79fc9b7b5f87a88faa016475bffffda08de05576dca19d84fd8574e74fc9d96b8eb4fd24fb77959f26d0ef7ef72b32ddabfcb1cb14f07d6846237636cbafea438e4af82f28411fd75a91ee9415a13bb3e46e81a0ed9edefb559dfcb429d6e47fb7b8264dbbfcce2596631ebdc190d7936119eb5e0eaabbb23349a0f1a511d7cd6d1fefd1f758e3991ef25b5fb4d3c4f0e259b3d7eebba3ea13a780d4d754e36cc754667bbae3d2e4de79024e7a7e3f3400b212e3e0fb49049cf6c50ac67d6e2e08c5d67c5ae1bc2d8f556ecfa218c2d9a8be63e69eed3ff8581ff6745650a1847a480b12a058c2353c0589d02c6512960ac4901e3e814308e49016306184fe4b99d419fbc67ffdf53ecb506c666789f43a8455330702d16f1f2c45efb606c8677a894fcff0d30ff5f58cd83fdbfb0b2f0bdf129606c4801e38414304e4c01e3a414304e4e01e39414304e4d01e3b414304e4f01e38c1430ce4c01e3ac1430ce4e0163630a184f4a01e39c14309e9c02c65352c0786a0a186b52c0b88097313f5846cdc3f11ecd4c503c8febe57898dfeb19be9fd0f50e518eff77b6d4b633bfd3b779b0efc0c3f90bbcff8fe22b7b4f1fc7fc8452dfd317f77f4f3331e607cbc8354f1be7280f84079ff1743d3bc2c0981f2c23d7f31df8bce140785cff0f2eeff33605cd06c3c8351fabd4f982f81c5e9b433306c6fc6019b9e6f4e3f38603e1713d1798e365cc0f96916bee6b06620c84a71334eb7068c6c0981f2c23d3b35ba1669d25f0e0334e9d0ecd7c62449ea4df9ddde988b5d483b61303328e4e01e3981430e2fd74aeb9fb51f7d33b79f5c90f561faefd15773f1d63333c53196ab13418b816ddbc3cb1f7d33136c3b315a1163897fee5b458063ccb19b4c804c5cf1bbc1c0f3164e17be353c0d89002c60929609c9802c64929609c9c02c62929609c9a02c66929609c9e02c61929609c9902c65929609c9d0246e6e770637fbf2c1be6b1a37eab0cf7d851bf4b867b6cc973c9f372882d792e795e0eb125cf25cfcb21b6e4b9e47939c4963c973c2f87d892e792e7e5105bf25cf2dca7d86918e317c6e1c7883c8dc9f1e4b0ed188be39da1a5b6fd74074f0553db31d60a0fda4e0c69635c9e02c6ae14308a8e8539888361ac017fd23c2b4ae0e9019e954c3c3d25f0ac049e55c9f38439b5b2041e62c8c2f7ba52c0b83c058ca2a3e8e813a3e8583e3a0aa3300aa3309e08c634f4e1729e29fc76190ca3e6599d3c4fa8d9aa1278568366f4bd1c2f637eb08c9a674df23ca166ab4be059039aad7668c6c0981f2ca3e6599b3c4fa8d99a1278d682666b1c9a3130e607cba879d625cf136ab6b6049e75a0d95a87660c8cf9c1326a9ef5c9f3849aad2b81673d68b6cea11903637eb08c9a6743f23ca166eb4be0d9009aad7768c6c0981f2ca3e6d9983c4fa8d98612783682661b1c9a3130e607cba8793625cf136ab6b1049e4da0d94687660c8cf9c1326a9ecdc9f3849a6d2a81673368b6c9a199af8c5d29605c9e0246661df38365d43c5b98783697c0b30578ce60e2d95202cf19c0f3aae479c29c3aa3041e62c8c2f7ba52c0b83c058ca2a3e8e813a3e8583e3a0aa3300a63698ca7a78051f6b530facac8f0fb2af6f994338679eca8e753867beca8e753867b6cc973c9f372882d792e795e0eb125cf25cfcb21b6e4b9e47939c4963c973c2f87d892e792e7e5105bf25cf2bc1c624b9e4b9e97436cc973c9f372882d792e795e0eb125cf25cfcb21b6e4b9e47939c4963c973c2f87d892e792e7e5105bf25cf2bc1c624b9e4b9e97436cc973c9f372882d792e795e0eb125cf25cf7d8abd35f9d8f9529f61dd0a3c1ccfd432b533a7b77ba6d9d68b09eaa7b53acbd2ea0c4bab2cd43913f43b8b41bf0a884bdba6758a572af3691e3033c5ce8f55db180deda718cb2d3d74fcb399da1ed5d79f3dcc6347f5f5c33d76545f3fdc634b9e4b9e97436cc973c9f372882d792e79ee4b6c2c5705fdd7edf47e25bd8d734c79a459a7faa7c3f7a8ce8c5185cfba408e218ed8720cc9b9a21c624b9e4b9e97436cc973c9f372882d79ee679e9f9b7cecf0de18febed04bdcbdb17381e71c062d98da99d36dda66b5e96cab4d59a883efacddc6d0ce0a884bdba6f56db01fd2c6ac79969932b166a0de324f18c9770e2f4f787c2d0b8a97b8e36b1bf0301c07cd4ced0c8faff3ac362d73e84e753057cf6368a7ebd8a1f5f3603fa48d59f3ac306562cd40bd159e3092ef5c5e9ef0f85a11142f71c7d779c0c3d1ff30b5333cbeceb7dab4c2a13bd5c15c3d9fa19dae6387d6cf87fd9036e62cc423d64c50bc4f7d6024df365e9ed64c507cfce825eef83a1f7838fa1fa67686c7d776ab4d3d0edda90ee6ea768676ba8e1d5adf0efb419885d9c5ac7956c2dff592817a2b3d6124df79ac3cadb90cb49996b87e6c3bf070f4f34cba87fdd805569b563a74a73a98ab1730b4d375ecd0fa05b01f4a615e9e4266d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d17970cc9a679529136b06eaadf284917ce7f3f284cfefac0a8a970a6bbd07ca1700cf76067d98da19ce7bbfd06ad32a87ee54078faf0b19dae93a7668fd42d80fa5302f4f21b3e83c3866cdb3da94893503f5567bc248beedbc3c613fb63a285ee2fab10b8187a39f676a67d88f5d64b569b54377aa83c7d7450ced741d3bb47e11ec0761166617b3e65963cac49a817a6b3c6124df05ac3cf9f039c43541f112d78f5d043c1cfd3c93ee613fb6c36ad31a87ee5407737507433b5dc70eadef80fd500af3f214328bcea27314b3e82c3a47318bcea27314b3e82c3a47318bcea27314b3e82c3a47318bcea27314b3e82c3a47318bcea27314b3e85c3e3a6b9eb5a64cac19a8b7d61346f25dc8cad312de77581b142f71f71d76000fc77d1926ddc3fb0e175b6d5aebd09deae0f17531433b5dc70ead5f0cfb61b8332f4f21b3e4c6d0304b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e486304731fb901b9a679d29136b06eaadf384917c17f1f284ef3d5817142f71f3762e069e1d0cfa30b5339cb77389d5a6750edda90e1e5f9730b4d375ecd0fa25b01f8459985dcc9a67bd29136b06eaadf784917c3b7879c27e6c7d50bcc4f56397000f473fcfd4ceb01fbbd46ad37a87ee540773f5528676ba8e1d5abf14f683300bb38b59f36c306562cd40bd0d9e3092ef625e9eb01fdb10142f71fdd8a5c0c3d1cf33b533ecc7765a6ddae0d09dea60aeee6468a7ebd8a1f59db01f760ab3303b9835cf465326d60cd4dbe80923f92ee1e5c967a0cdb4c4f5633b8187a39f676a67d88fedb2dab4d1a13bd5c15cddc5d04ed7b143eb142f8dcc9a679329136b06ea6df284917c97f2f284c7d7a6a078893bbe7601cf4e067d98da191e5fbbad366d72e84e7530577733b4d375ecd0fa6ed80f6963d63c9b4d995833506fb3278ce4dbc9cb131e5f9b83e225eef8da0d3c1cfd0f533bc3e36b8fd5a6cd0edda90ee6ea1e8676ba8e1d5adf03fb216dcc9a678b29136b06ea6df184917cd84fd152098c5b9818038b31b0f4419e6ecf78da3ce3c97bc6b3d8339ead9ef1ccf28ca7c9339e299ef18cf78c67b4673c233ce399ed19cf7ccf78a67ac6b3c8339e06cf78c678c653e5194fbb673c2d9ef12cf18c67ae673cd33ce399e019cf02cf78329ef18cf48c67ba673c133de3a9f58c27eb194fb5673c9d9ef17478c6d3ea194fb3673c333ce399e419cf58cf78ea3ce319e519cf52cf78667ac6b3d0339ec99ef18cf38ca7de339e1acf78e679c653e1014f2678e97dfa0cfc7d2bf82aadefeafefcdf1afaff4ef7452be13b7b4d798463db7bc0b7db94f73abe8b3a71ddebc5583db04ef16a8163af273cf33ce3a9f18ca7de339e719ef14cf68c67a1673c333de359ea19cf28cf78ea3ce319eb19cf24cf786678c6d3ec194fab673c1d9ef1747ac653ed194fd6339e5acf78267ac633dd339e919ef1643ce359e019cf04cf78a679c633d7339e259ef1b478c6d3ee194f95673c633ce369f08c6791673c533de399ef19cf6ccf784678c633da339ef19ef14cf18ca7c9339e599ef16cf58c67b1673c79cf78da3ce3e9f68ca7d2c1b395896777d0bff4c0fa564f6233ec879cdeee3ea636ed37dbaa36db257e8a5705755e300311fa7e037e97b8b698b2abafdb0f1aed666a4bd6e2a1f5ddc33c769d15bbae4c62d75bb1ebcb24b6e4b9e47939c4963c973c2f87d892e792e73ec67e31b9d86df27ea081f3c8fb81e2797c7b3f90bc8f279e47dec713cf23efe389e791f7f1c4f3c8fb78e279e47d3cf13c559ef1c8fb78e279e47d3cf13cf23e9e781e791f4f3c8fbc8f279e47dec713cf23efe389e791f7f1c4f3c8fb78e2797c7b1fb3bc1f289e47de0f14cf23ef078ae791f703c5f3c8fb81e279e4fd40f13cf33ce3a9f080e7e5de0f84eff5a1b98dbbc147f327e3de239481edec071f8dc7d136f4f9e17f1a5eca5009dfb9ccc1b5cf118fe25ce6f8ee50e88eb17a609de2d502c7659ef0ccf38ca7c6339e7acf78c679c633d9339e859ef1ccf48c67a9673ca33ce3a9f38c67ac673c933ce399e119cf56cf789a3de369f58ca7c3339e4ecf78aa3de3c97ac653eb19cf44cf78a67bc633d2339e8c673c0b3ce399e019cf34cf78e67ac6b3c4339e16cf78da3de3a9f28c678c673c0d9ef12cf28c67aa673cf33de399ed19cf08cf78467bc633de339e299ef13479c633cb339ec59ef1e43de369f38ca7db339e4a070fd73b7fa29e8f1e8af70dbd5c6cbdbe1074d14b06fe3e14f354b65a8cb4be05189197781632f1443dd7bdd083d8bafd746ea07b1619f87b133072e5d4428b91d65d3985f3e49a9878a29e476ff220b6d6827e4bd03de50cfc1d9f73e3caa9268b91d65d3955cfcbd39a8136d3123797068f398e7dc8d4ce1c1e7f09be0321a7b5da6269b5c8d22a0b7586629e73547f40f1845998a398350f5dcb132b9ecf86e23d0d0361749d5f1978c2fe717150bcc4f58f5b8087e3fcc1d4ceb01f3b60b569b14377aa83b97a80a19dae6387d60f38623706c96a7170005a1c74f01c1c622d285ea9cc5b53c8ec83ce9a87eef5102bcedf5de20923f916b2f2e4731968332d71fde341e0e1387f30e91ef609975b6d5ae2d09deae0f17539433b5dc70ead5f0efb4198855998855998855998855998855998855998855998855998855998855998fd66d63cf44c2bb166a05eb3278ce4dbc2ca53b8efd01c142f71f71d2e071e8efb324cba87f71daeb0dad4ecd09dea60ae5ec1d04ed7b143eb57c07e1066611666611666611666611666611666611666611666611666611666611666bf99350fbd4b815833502fef0923f90ef2f284cf83e583e225eebec315c0c3715f86a99de17d8743569bf20edda90ee6ea218676ba8e1d5a3f04fb419885d9c5ac79e89d78c49a817a2d9e3092ef72569ec2fdd396a07889ebc70e010f473fcfa47bd88f1db6dad4e2d09dea60ae1e6668a7ebd8a1f5c3b01f4a613e904266d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169dcb4767cd43fff71fb166a05eab278ce4bb8295a725bcefd01a142f71f71d0e030fc77d1926ddc3fb0e47ac36b53a74a73a787c1d6168a7ebd8a1f523b01f863bf38114324b6e0c0db3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b30fb9a179da4c99583350afcd1346f21de2e509df7bd016142f71f3768e00cf61067d98da19cedbe9b5dad4e6d09deae0f1d5cbd04ed7b143ebbdb01f8459985dcc9aa7dd94893503f5da3d6124df615e9e7c06da4c4b5c3fd60b3c1cfd3c533bc37eeca8d5a67687ee540773f528433b5dc70ead1f85fd903666cdd361cac49a817a1d9e30920fcfcb1d4c3c598b27ebd0e244c5d6eb9da65c6b3e33f0f74e60e4ea0f3b2c465ac71c475ee2e964e2a9b378ea1c5a9ca8d8bafd4b4d79acf9ccc0df970223574e755a8cb4eecaa93ae059cac4536ff1d43bb43851b1b516dda63cce7c66e0efddc0c895534b2d465a77e5543df07433f144f549dd43103beaf81a8ad851b93214b145f368cd198ebb707ca03b285ee2aeabf1dcc2d15731b533e73a7f775b6dc2f3375ea39ea8f393300b731433d3756e6bc68a4dfa04160f2dbdcc5a0ce5efec4eab4d69f89d1dc77c2085cca2f3e09875ec2b138f5d78df28c6267d028b87962b99b5e06967a13f3816b835a67859a883797a8ca19d151097b64debc7603f08b3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3dfcc3af6f1c46317c6ef3136e913583cb41c67d682a79d85f1fbbec0ad31c5cb421ddce77d0cedac80b8b46d5aef83fd20ccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22ccc7e33ebd857251f3b7c1e0763933e81c543cb55cc5a30b5331cbfbf3a706b4cf1b25007f7f9d50cedac80b8b46d5abf1af683300bb38b59c7be26f1d885fb79189bf4092c1e5aae61d682a79d85fee0dac0ad31c5cb421ddce7d732b4b302e2d2b669fd5ad80fa5301f4821b3e82c3a47318bcea27314b3e82c3a47318bcea27314b3e82c3a47318bcea27314b3e82c3a47318bcea27314b3e82c3a47318bcee5a3b38e7d5de2b15bc2f17b8c4dfa04160f2dd7316bc1d3cec2f8fdf5815b638a97853a98a7d733b4b302e2d2b669fd7ad80fc39df9400a992537868659724398a398253784398a59724398a398253784398a59724398a398253784398a59724398a398253784398a59724398a398253784398a59724398a398253784398a59724398a398253784398ad987dcd0b16f483e76f83c3bc6267d028b87961b98b5606a6738ffe5c6c0ad31c5cb421dccd31b19da59017169dbb47e23ec0761166617b38e7d53f2b1f3192b36e913583cb4dcc4ac05533bc3fee0e6c0ad31c5cb421ddce73733b4b302e2d2b669fd66d80f6963c6fd57915cec70de26c5a8349fda778b298f00dfada65c05be579bf248f0dd66cad5e07b8d298f02df6ba16de47b9d292f00dfeb4db91b7c6f30e5a5e0bbdd943bc1f74653ee00df9b4cb9177c7798f251f0bdd994af04df9da67c0c7c6f31e5e3e07bab29f781ef2e53be0a7c779bf2d5e07b9b295f03beb79bf2b5e07b87295f07be779af2f5e07b9729df00be779bf28de07b8f296f05df7b4d7934f8de67ca63c0f77e28d3e7074cb9167c1f34e52cf8ee31e5b1e0fb9029d781ef5e531e07be0f9b723df83e62cae3c1f751536e00dfc74c7902f83e6eca13c1f709539e04be4f9af264f0dd67ca53c077bf294f05dfa74c791af81e30e5e9e07bd0946780ef21539e09be4f9bf22cf07dc6946783efb3a68cfbf773a67c13f8a81fb8197cd40fdc023eea076e051ff503af061ff503b7818ffa81d7808ffa81d7828ff2ee75e0a3bc7b3df828efde003ecabbdbc14779f746f051debd097c947777808ff2eecde0a3bcbb137c94776f011fe5dd5bc147797717f828efee061fe5dddbc04779f776f051debd037c9477ef041fe5ddbbc04779f76ef051debd077c9477ef051fe5ddfbc0477987fd42a3297f007c2799f207c137c794ef01dfc9a6fc21f09d62caf782ef5453fe30f84e33e58f806fae297f147cf34cf963e09b6fca1f071f9d9b3e01be85a6fc49f03599f27de05b64caf7836fb1297f0a7c4b4cf901f0e54cf941f0359bf243e0cb9bf2a7c1d762ca9f015fab297f167c6da64cfd823efe6aa12dd4ce5ae05ee4e0215f0df0f404c95e33512cda36ade78191746c1e7ac6fc40197316a3e66965d00c738396b8df24adc0d3c2c0c3d4cef037499bd5a6bcd5a62cd4390ddad9c6d0ce0a884bdba6f53688cdb1cf518b6ab3ddb996165550e76173a2d2e7bf381d691b3a7f9b1d6de1d23167f1e41cb13b9975a46d539fd83904b1dbadd84bacd8d89fd312776cb703730703b3de6e57f2db0d8f6dfacd47f94c7196409bba4183a4da84b12b8c511cf25741f9c986feba548ff4a07327b1ebe388f625b2dbdf6bb5be97853a9d8ef6f704c9b6bfcbe2e9b298f5f5ff630dfd1c0cc74398039d1607ad2f01edba22b4eb04eda80e9e7b9b98b4ebb07868bd0978e8faaa0d7c749d42fc789db66808b8ed7eafcdc14dbe76606c72303627cf185e6735598cb4de0c8ce4eb009e7626cdec7d3dd7d207af09465975e8bb5550e75fe1bc9c71d4d5c7ddd88afe76d1eff5178364fbf46a06bd702c21007d024b435a88a126e81f6f4892674cd03f9e70bcaff7d8aecbf69dbd6fd7de0a40abb230f1b3c2d18c4af0617984c31704c5c32638dc4ac32638dc5a69c982c335545fff8cd3cda2a1897d870ff66d3bb2efc89e63d71deddbb7774bef65483dd2a247d2a8162029fa68a909fa07787a82646fb4545bb1e292a7063e4725cfd3ccd4cef0a437da6a53b5d5a62cd419097f1bcdd0ce0a884bdba6f5d18ed8097644a1166306a0c51807cf9821d60207c9c987472afd1d6f8c545a6dc1231adb64e779a20da280a7c2f62b0c9cfe9b3ed8479ac68c0afa7736f59efa8a56ef043dbaaacf5a7af4548f96ea2e489fccf4e8a71eedd4a39b7a34538f5eead14a3d3aa94723f5e8a31e6dd4a38b7a34b131288c16ead1413d1a780a703d0cacfad7bc3e3bead13c3d7aa747ebf455953efbeb2b117de5adaf12f52f477d75a07fd1ead10d7da6d55731fa0cadcfaafa2a515f1deaab797d75bb4cd97265a72b5b61b45ea96c95b2d5cad6285bab6c9db2f5ca3628dba86c93b2cdcab6283b43d9ab82c208fc99cace5276b6b273949dab6c9bb2f3949daf6cbbb20b945da8ec22653b945dacec1265972adba96c97b2ddcaf628dbab6c9fb2fdca2e0b0ab36d0e2abb5cd915ca0e293bacec4850b8f3a5ef74e93b5bfa4e96be73a5ef54e93b53fa4e94bef3a4ef34e93b4bfa4e92be73a4ef14e9bb04faae80be0b706b5018e5d7a3fa7a145f8fdaeb517a3d2aaf47e16f0f0aa3ec7a54fd8ea0306aae47c9f5a8b81e05d7a3de7a945b8f6aeb516c3d6aad47a9f5a8b41e85d6a3ce7a94598f2aeb51643d6aac4789f5a8b01e05be37288cf2ea515d3d8aab476df528ad1e95d5a3b0f7058551563daaaa4751f5a8a91e25d5a3a27a14548f7aea51cecf2bfb82b22f2afb92b22f2bfb8ab2af2afb9ab2af2bfb86b26f2afb96b26f2bfb8eb2ef2afb9eb2ef2bfb4150c8c11f2afb91b21f2bfb89b29f2afb99b24794fd5cd92f94fd52d9a3ca1e53f62b658f2bfbb5b2df28fbadb2df29fbbdb23f28fba3b227943da9ec4fcafeacec2fcafeaaec6fcafeaeec29654f2bfb87b267943dabec3965cf07fd773bb0a378c1acd0c8fbaebebe7d878ff635f6f5361ebeea50dfc1a387ae6bbce660df81c6deabf71ddb7fa8f71afcf2174dd744b715561e3bb6ebbac68347f6eebbb6b1f7aabec6defd8dbb7baf3ab2f7387ee971f3a5192f8db86befdee8604f55be02d2670719f4dfcdf7e886cdc6f8b6fde760047971305f9a3362700d9a6ece2ccbccfa39852bdac6e3877afb1a738d47d4bfbb0ea9efecdbbbb811ff765c897cbcaff178dfae637d8dfb8ff51e6e6c5e5c245466108df841c320bef42f0d036f79f07f1d300ba085c50300", + "packedBytecode": "0x000000028df71de50000003e021f8b08000000000000ffed9d0774154796f75b4280f0d3b3088e045b3860c004e9492449c023679325030e801019e3210c382272c618b0c946e0999d4db339cfe69c739cd9303bb333bb3b3b73ce37bb5ffecef1f9aafad51dfd55543ff4e4aec77dd2ed734aaffaaabaefaffe7dab3a55777f270882a22033f550e9e9e0ee89fe9f36bf959f6eaa8a715d953e398b0a84b3b840387b140867498170f62c10ce5e05c2d9bb40384b0b84b34f8c9c9aad38683fc5cdfb80075de3664c1498a66505a069b2c0347db000342d0f0aa38fea5b209cfd0a84b37f81700e2810ce870a84f3e102e17ca440381f2d10cec70a84f3f102e11c58209c830a84737081700e2910ce270a84f3c902e1ac2810cea105c2f95481703e5d209ccf1408e7b331728e00ce61e6f739f33bdcfc529991e6f779f33bcafc8e36752c31f363541aabd954aab2fe9752a95aa51a95c659ff1bafd2049526aa34c9fcafc2fcaf56a53a95ea559aacd21495a61a1da6a9345da5192acd5469964ab3559aa3d25c95e6a9345fa5052a2d5469914a2fa8b458a5252a2d5569994acb555aa1d24a951a546a54e9459556592cab555aa3d24b2abdacd22b2abdaad25a95d6a9b45ea5269536a8d4acd2469536a9b459a52d2a6d55699b4adb55daa1d26b2aed54e975953ea3d22e9576abb447a5bd2a7d56a57d2aed57e90d4bb337557a4ba5b7557ac7e27c57a5032ab5a87450a5432a1d56e9884a47553aa6d271954ea87452a5532a9d56e98c4a67553aa7d27b2a9d57e97d952ea87451a54b2a7da0d2872a5d56e98a4a5755baa6d275956ea874d3b05043f848a55b2ab5aa745ba53b2a7dacd2e754fabc4adfa7d21754fa7e957e40a51f54e98754fa6195bea8d28fa8f4a32afd984a3faed24fa8f4932afd944a3fadd2cfa8f4b32afd9c4a3fafd22fa8f425957e51a55f52e99755fa15957e55a55f53e9d755fa0d957e53a5df52e9b755fa1d957e57a5df53e9f755fa0395fe50a53fb234ff6395fe44a53f55e9cfccffe83ad89fabf41726ff97e6f7afccef5f9bdfbfb196f95b95feceb27d59a5af58b6bf57e91f4cfe1fcdef3f99dfaf9adf7f36bf5f33bf5f37bfff627ebf617ebf697effd5fcfe9bf9fd77f3fb2df3fb1fe6f7dbe6f73b2add1c9cc997066d533a88a94faa696ed6f75048ec6141fb496bd1c3fc8f7e2b8cbdc4ccd32f69d7d3ccf7b4ecbdcc7c2f6b3da566bed4b2f733f3fd2cfb00333fc0b23f6ce61fb6ec8f9af947c19e08e05aaab16b5b0f632a021bc56131d87a06ed35d1b65eb43ab0f50eda6ba16db41d7b81ad8fb1f506db03c6560ab684b1f521cd542a33b67410574c54aed3eb4dc6bd5e737fe9c1f8799bf47acb3df1f68d9fb759afb79f075e1d1ffdcdbafa42dc0c30b67e607bc8d8fa83ed61631b00b6478ced21b03d6a6c0f83ed31637b046c8f1bdba3601b686c8f816d90b13d0e36d3ed0503c136c4d80681ed09631b0cb6278d6d08d82a8ced09b00d35b627c1f694b155808dc6ae0c05db33c6f614d89e35b6a7c1467dea3360a363be678d4df7133d8b601963a73e2a5c86fa61b00da73e186c23a8ff05db48ea7bc1f63cf826db28e857c836dad8a88fd2ff9b68f2e920ae36910adbc4a4b8d7abd6acd75b17ff7ac3fb71f5419bae69f03309b49a6cf2318ef9a942df4526911fb297407e2e94a572a407ed67885def4f6a4d7e7296e5265acb25a14cada3fee920defad7593c7516734fc8fb89d9ea94c46c87a79c63b601cadab147c73c5d316617008787981d2f31dbe129e798dd0065edd8a3e3deae18b3ab80c343ccaef313b3a94a89d9ccf5af2070c71e9dfb74c598dd041cf1c7ec3889d98e4f39c76c0b94b5638fce7fbb62ccee058ef86376c23a3936e8f09473cc9e83b276ecd1b598ae18b38781c343cc364b3fdbe129e798bd0165edd8a3eb825d3166df078ef8637692a798ad96980d32f73283c01d7b748dba2bc6ec2de0883f669be4fa6cc7a79c63f6e7a0ac1d7b74bfa42bc6ec178123fe98dde0ebfa6c4a623633862308dcb147f7eeba62cc7ec9e4f5bdb1bf34f7c69e00db5f19db9360fb6b187b40b6bf31b6a1502f0f6d60a2b4810e4f39b781bf85b2762c3f65f25db10dfc11707888d97512b31d9e728ed96f42593bf6684c43578cd9af008787986d9298edf09473ccfe77286bc71e8dafe98a314be343f5f1c23f9ae385e160fb27631b01b6af1adb48b0fdb3b13d0fb6af19db28b07dddd84683ed5f8c6d0cd8be616c63c1f64d63ab04dbbf1a5b15d8fecdd85260fb7763ab06dbb78cad066cff616ce3c0f66d631b0fb6ef18db0463d3f7b1684cd5ef1a5b2970a583f8b66d0234a0a9c89a4f43beca2f4f651278d05775fcbeaa75dd5341c7eb5e0d3c351eea9e001f1de1a9019e71f1f384e349c7c7bfde701ba72c4d13e02b05f59ae0a15e45e08bd64df3e42f09366cd3131c8cb1ef9fd57eb4087cd1ba697e2230920dfb18ea73a9fde8be7948511baf87b614ee9fd15f1a38c85f09941937b8adec50c35606ffc7fe769c65f31497615c902f5a37cd93bf32a8cfb8fc33a63aca586331faea238ac017addbf65d06f94abffa64e519cfc477ad27df769f469ad7e6c1f744cb77b5e51bfb4e9ab2eddb2602b387f394aafb719e82c70f1cce536616b595b5cf37a81fcef53c6582b51cc7f39449b02ff4d01ec218a8b53868be1ab4ab8bd0ae16b4a332cf8176befaf149160fcd8f031eeabfb13fabcc334f2503df780c4bdb0dcfa5f038c0d7f6aab41869deb5bd2602a3eb58c5c3f94cd663951a60241b5ec74979d22c6abba698f8f6102b617f443ee8d89cda2ff92b81320b7ab4955d0b7da58f18c178a4a9a3e7bcf16fa754780e5e93030f6e3b0fe755559ee2b112afdf7c12c41b6b76bf54636915758dc7575f9eb278689efc09b3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300b337f66bc9f84e3b6a8dc38268cf6f8375fd7f9c377ec9975e13da0ef7a1d0f96aac4b130340e628455e71228535edcc6f63f603c983d96286a5bfa181b9b6d5b923f1c0f968f7b88d5168fedbbcca10fde9746cd7c8d81b2fb0c9ac7f169a45f25f0f86a8f55168fed1bfb8c1453cda2fa595f630ba2e2cc359eb02236dfa9265ff775f5fbccf43b41edfdd7384bd3f05da3964df7492ff468abb78ffbbcb9de77c6fd06e5e3bc8f9b041ef4157f3f9b6a373ea13868bfafc058f735568bda158d8da8b57c97409921c56ddba60efe9f0eeede3760195a37cd8f8065ebac7597fbab6fd67128b5c04df95e56ddc6033795790af6dd7f66f29ef681a95c9f91c07d72fcfb97cc7891540e3cb8bff371cce2693f5a89f118f778117b1ca4eb7899cae018520fe377b3ee8bc99f300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330f367d63cf63d74bcf75bcd84314f636cc2fb19f42e36bc2ff693c56d7e7ddf03a47b4e23ad3ae333ca013ca3fcb3860dc73ee07ba55cdbd2d7bdb5a86d49feb88d7d2873e8a335ab736856ef89d1ee3368be1e1849bf3ae0f1d51ea3dedde1ea332a996a16d5cffa18bb902dce5ce3142a62f39d192fe2e3be2e8d17b1f75ff67b8d709c04d9f03e78d4fbc2aa2cdbfdee035ce3da288fef5c88bfdf4cb5bb076c8f15247f384ee22b465b1a27117f3f90aaf4b98fa0364a6342528eba5299af16b795fd9ac9e358a16a58d7771dffa729db9804ec53a7c45fe770fb4e35eba2ed3bc5e13b0dac31f9ae42df45d6bac95e02f9ff567c3703e9415a13bb6e23f81eaba8e56aace5925066b2a3fee998eb3fc5e2c16dac271d3bdf8038fb2e1cebf9ea93264768340234a2329edf03eb1ce76b8f55c67eb4b7558696c5f75bfe1fe8a3a2c6a4bbf601be8e17a2f60178bc60ef173a3276bcbb8f07ec63ce437c8c07a4759707edc7e405d6fa47c2fa89ab7710bd6ff9deb938acdfe798f57bbdf7ca75ee47bc38de90caf48773bf99269fcbb9dffd3a8f779dfbe1725175c77e21ee7d23c623b2e0fb22a9cc602b1eeb22b8273a967d326259d2ca7ef7219e97e23142fcef8dccf43753acba509b9a0c75a132cf5aed26fe63a6ccf1a7af7764d2b10ef541558eba529991d0d646997c02b613ee17ea1cffa729dbf127e9a7eb3c2dfe3a87db77ba59176ddf690edf33803526df55e89b8e3fc90fd94b205f0bfb132a477a90d6c45e069cc86e2f576b2d9784326947fdd331d77f9ac533cd62d6b13316e2ac0e9eb5f0d557a703b7462341a3eff9039bebbdc6ae7d8cafe7d1a2f63155c068f79bb89fcce7b372f6f55bd7f10995c163682a330ffad984a3ac7d5d9a968b737c3a3e0f84c785f83c90af73a464d05ecfa4c5e1d377b9e5bb3c8fbefb59befbe5d1b7682e9a73d29cd3377f70df520c8c3ece1bf1fa484718f17d04b45c0f60f4f5cc662a07c66a60c4fd1d317a78bf725567bf2984c73a3d81d1c7f3c6b95eafc6f79be33b0688d1c733dab9bea71a9fdba6e57a03a38fef1ae137943ac2e8fad65129fc7af8ae515567bf0382df3aea038c3ebe0d9208da7fcfe45e8c938091967b00187ddc474a04edafabdd8b11ef57d27209cf8cd9f6ed9ec7fea472bd06918f710551c71ae8dbc3f5ff148e4de88816f57e79b21efba06f0fd7bf422dea838e6b81f7e73cdc1b0ddbf1e41c78f01e222dd71f18a77a629c9203e35460a4e5060063da13e3d41c18d3c048cb3d048c1eae43868ce91c18f17a1dd91f06c6e99e18a7e5c0381d1869b94780d1c735c504f8ed08e30c60a4e51e05c6999e1867e4c038131869b9c780719627c6993930ce02465aee71609ced8971560e8cb38191961b088c733c31cece81710e30d272838071ae27c6393930ce05465a6e3030cef3c4383707c679c048cb0d01c6f99e18e7e5c0381f1869b92780718127c6f939302e00465aee49605ce88971410e8c0b819196ab2800c6a105c0f85401303e5d008ccf1400e3b305c0585a008ccf03e3a2f819c3f3d48539302e029e17e2e709355b9403cf0b7e79c2f7132e72f85a1cbfaf54ae755f0c3c4be2e709b7c5e21c78882109cbbde09731d55946cdb3347e9e50b32539f02c05cd963834f3c098ea2ca3e659163f4fa8d9d21c789681664b1d9a79604c759651f32c8f9f27d46c590e3ccb41b3650ecd3c30a63acba87956c4cf136ab63c079e15a0d97287661e18539d65d43c2be3e709355b9103cf4ad06c8543330f8ca9ce326a9e86f87942cd56e6c0d3009aad7468e68131d55946cdd3183f4fa859430e3c8da0598343330f8ca9ce326a9e17e3e709356bcc81e745d0acd1a1192746e489fb7de38d0e5fab18d49d1890b14f01303e50008c3806c147ff956d0c42a35f7d529dd5c7d7f6ca3606017daff6a4c5aaa0e35aacf6cb93750c02fa5ee3498bd541c7b558033c2f79d022013e3ac2430c4958ae7f01300e2800c6870a80f1e102607ca400181f2d00c6c70a80f1f102601c58008c830a80717001300e2900c6270a80f1c902607cd13363b6f39797bab8efa87395aeee3beabca4abfb96389738ef0ebe25ce25cebb836f897389f3eee05be25ce2bc3bf896389738ef0ebe25ce25cebb836f897389734ebe5ff6e03b013e68ca768d9f1892b0dc8bc2d8a51991a7223e9e4aac3bfa7a8541dd5f71f01479aa3bfa7a9541dd89a1d0185f2e00c6170b805174cc8c41ec0ca3e659eb89e7d51c78d602cf3a4f3c6b73e059073cebe3e709636a5d0e3cc49084e55e2c00c6970b805174141d39318a8edd47476114466114c6fbc158087db8ec6732e72e9d61d43c4df1f3849aadcf81a70934a3e55ef0cb98ea2ca3e6d9103f4fa859530e3c1b40b32687661e18539d65d43ccdf1f3849a6dc881a71934dbe0d0cc0363aab38c9a6763fc3ca166cd39f06c04cd9a1d9a79604c759651f36c8a9f27d46c630e3c9b40b38d0ecd3c30a63acba87936c7cf136ab629079ecda0d92687661e18539d65d43c5be2e70935db9c03cf16d06cb343330f8ca9ce326a9eadf1f3849a6dc981672b68b6c5a19907c654671935cfb6f87942cdb6e6c0b30d34dbead08c2be38b05c0f87201307ad631d55946cdb3dd13cfb61c78b603cf0e4f3cdb73e0d9013cafc5cf13c6d48e1c78882109cbbd58008c2f1700a3e8283a7262141dbb8f8ec2288cc2981be32b05c028db5a18b9327a38bfcafa7cca8e2eee3beaf994aeee3beaf994aeee5be25ce2bc3bf896389738ef0ebe25ce25cebb836f897389f3eee05be25ce2bc3bf896389738ef0ebe25ce25cebb836f897389f3eee05be25ce2bc3bf896389738ef0ebe25ce25cebb836f897389f3eee05be25ce2bc3bf896389738ef0ebe25ce25cebb836f897389f3eee05be25ce2bc3bf896389738ef0ebe25ce25ce39f9de19bfef54aecfb0ee041e1fcfd47aaa67a55eefeb665d9fc4a89fd6ea3396563b2cad9250e675d0ef331ef42b02bfb46e9a277fb9320f63c0ecc977ea41b58e3e507ff2f1b2a587f6bfcb53dda3fafa5d5ddc77545fdfd57d47f5f55dddb7c4b9c47977f02d712e71de1d7c4b9c4b9c73f18df992a0edb89ddeafa4d7b1dbe47b9a792aff0a2c4765e6f5cefc9607d2867cf8963624fb8aeee05be25ce2bc3bf896389738ef0ebe25cef9c539c6c3bc3cf004164f9085271ff70f72e119c38c6706339ec9cc782630e3a962c6339219cf66663ccdcc78d633e3e9c18c673e339e99cc785e62c6338519cf44663c29663c5b99f1ac64c6b38c19cf68663c8b99f1bcca8c6716339ea9cc78d630e399c48ca79a19cf36663c5b98f10c63c6b391194f13339e05cc78d632e399cd8c27cd8ca79619cf70663c35cc78b633e3798e194f23339e06663ccb99f12c61c653c68c27c98c6721339e39cc78a631e3a963c6338e19cf2a663c9b98f16c60c6b38e19cf08663c0f32e32967c6339619cf5c663cd399f1d433e319cf8ca79219cf6a663c2b98f12c65c6d397194f3f663c8b98f18c62c653c4802711dcfd8e9304fc7f27d88aad65f563616f0e6efbff1e632f8665f69a7c0fc7baf7808d9e35dbeb581675da0375499b7ce5a79b429dd0571ae6c95f1970ec65c2338a19cf22663cfd98f1f465c6b39419cf0a663cab99f15432e319cf8ca79e19cf74663c7399f18c65c653ce8ce741663c2398f1ac63c6b38119cf26663cab98f18c63c653c78c671a339e39cc781632e34932e32963c6b38419cf72663c0dcc781a99f13cc78c673b339e1a663cc399f1d432e34933e399cd8c672d339e05cc789a98f16c64c6338c19cf16663cdb98f15433e399c48c670d339ea9cc786631e3799519cf62663ca399f12c63c6b39219cf56663c29663c1399f14c61c6f312339e99cc78e633e3e9c18c673d339e66663c9b99f18c64c653c58c6702339ec9cc786630e319c38c6727339e790e1e0fdfcf0b7968bc2aad9be67732f1ed613b84df0dfcaca73aed33ebea65d64bfce4af04ca2cea93f9d5e3757059e2b2c71763acee038d767baa4bd43b99777771df51ef64eeeabea3dec9dcd57d4b9c4b9c73f2bd2f7edf297c3685a6226b3e0d79dcbff878a6c7533ddbeddbe3fe26f07e4babdd96564928f359d06fbf07fd5cc70b344ffe72651ec68019e3a22288372ede88bf4edffbd630e9fa86a52fd6eb4d4f9a46ed43deece2bea3f6215ddd77d43ea4abfb96389738ef0ebe25ce25cebb836f897389734ebedf32f918cf1b2bd187bef64be7036f81df774cbe2846bf7a5d6f9b75d1b79189e31de0a1323f02d7a2a5cd4b9b8fcbb7ecdb24cebb836fce716ee7e91e22beb7dbd73ddea858ccc7fde5fbe93b2a16bbbaefa858eceabe25ce25ce39f97e377edfe13dc49d41fb29db3dc47781e76d0f5a78aa6778ee74c0aad34eab4e492883e772073cd4b308fcd2ba69fe006c874263d63c34961ddfcf48e55e62c248b6b7fdf284edeba5a0fd94ad7d1d001e0feda0ca533dc3f6d562d5e92587ee540663b5c5433d5d6d87e65b603bb41418b3e6a167e7883501e55e65c248b677fdf284edebd5a0fd94ad7db5008f8ffec7533dc3f675d0aad3ab0edda90cc6ea410ff574b51d9a277f85c8ac79d69a3cb126a0dc5a268c643be097a7260175a6295bfb3a083c2d1ef4f154cfb07d1db2eab436b85b772a83b17ac8433d5d6d87e60fc17610666176316b9e7526bfd6fc26a0dc3a268c646bf1ca535399803ad394ad1f3b043c3efa794fba87fdd861ab4eeb1cba53198cd5c31eeae96a3b347f18b6432eccfb0a905974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974ee1cb3e6596ff2c49a8072eb993092eda05f9ef0f99df541fba9c89a4f43fe30f01cf2a08fa77a86e3de8f58755aefd09dca60fb3ae2a19eaeb643f347603be4c2bcaf009945e7ce316b9e269327d604946b62c248b6437e79c27eac29683f65ebc78e008f8f7ede533dc37eeca855a72687ee5406dbd7510ff574b51d9a3f0adb419885d9c5ac7936983cb126a0dc06268c643bec9527153e87b821683f65ebc78e028f8f7ede93ee613f76ccaad30687ee540663f598877abada0ecd1f83ed900bf3be0264169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d159748e62169d45e72866d1b9fbe8ac799a4d9e581350ae990923d98e78e5a90eef3b3407eda76cf71d8e018f8ffb329e740fef3b1cb7ead4ecd09dca60fb3aeea19eaeb643f3c7613b7475e67d05c82cb1911f66890d618e6296d810e62866890d618e6296d810e62866890d618e6296d810e62866890d618e6296d810e62866890d618e6296d810e62866890d618e6296d810e62866890d618e6296d810e628660eb1a179369a3cb126a0dc46268c643bea97277cefc1c6a0fd946ddcce71e039e6411f4ff50cc7ed9cb0eab4d1a13b95c1f675c2433d5d6d87e64fc07610666176316b9e4d264fac0928b7890923d98ef9e509fbb14d41fb295b3f7602787cf4f39eea19f66327ad3a6d72e84e6530564f7aa8a7abedd0fc49d80ec22ccc2e66cdb3d9e4893501e536336124db71bf3c613fb639683f65ebc74e028f8f7ede533dc37eec9455a7cd0edda90cc6ea290ff574b51d9a3f05db419885d9c5ac79b6983cb126a0dc16268c643be1972795803ad394ad1f3b053c3efa794ff50cfbb1d3569db63874a73218aba73dd4d3d57668fe346c874263d63c5b4d9e5813506e2b1346b29df4cb13b6afad41fb295bfb3a0d3c3efa1f4ff50cdbd719ab4e5b1dba53198cd5331eeae96a3b347f06b643a1316b9e6d264fac0928b78d0923d94ef9e509dbd7b6a0fd94ad7d9d011e1ffd8fa77a86edebac55a76d0edda90cc6ea590ff574b51d9a3f0bdba1d09835cf769327d60494dbce84916ca73df324a0ce34656b5f6781c747ffe3a99e61fb3a67d569bb43772af326d4f39c877abada0ecd9f031e9ae6018fafb80c2c9ec0a10f4d6398f1cc60c6339919cf04663c55cc784632e3e9c18c673e339e99cc78a630e399c88c27c58c6725339e65cc784633e3799319cf62663cb398f14c65c6b38619cf24663cd5cc788631e359c08c6736339e34339e5a663cc399f1d430e36964c6d3c08c6739339e25cc78ca98f12499f12c64c6338719cf34663c75cc78c631e359c58c6704339e0799f19433e319cb8c673f339eb9cc78a633e3a967c6339e194f25339ed5cc785630e359ca8ca72f339e7ecc781631e319c58ca788014f22b87b2c4d02febf1f6c34e6633bd8de33f933602b76f8a07bc5e7c05662f2b48ede2a1d1d7cf7ba51275fe35cd0571ae6c95f1970bcc7846714339e45cc78fa31e3e9cb8c6729339e15cc785633e3a964c6339e194f3d339ee9cc78e632e3d9cf8c672c339e72663c0f32e319c18c6715339e71cc78ea98f14c63c6338719cf42663c49663c65cc789630e359ce8ca781194f23339e1a663cc399f1d432e34933e399cd8c6701339e61cc78aa99f14c62c6b38619cf54663cb398f12c66c6f326339ed1cc789631e359c98c27c58c6722339e29cc786632e399cf8ca707339e91cc78aa98f14c60c6339919cf0c663c6398f1cc73f0ecf7c4633fe745f3fb19f8d6f3b5a08b9e12f07f7c0eec4d4f8cfb2d469a7f131891d7b766e5164fb9a5d9fdf4adeb4ff72a1e34bfb8bdf0b9070edbab3c0f9af5b378fa599add4fdf5a0bbaf74f636c707be138680edb0bc7697ae89f6b12168f9e8aacf934e4cf79d6c7533d2b71dce42731ae576b75ded26abfa55512ca9c05fdce7bd0af08fcd2ba699efc09b33047316b1eba97eb1acfbb900923d9f03d2aefc7cf5393b078f494ad7f7cdfb33e9eea19f6631702b7eeef83ee540663f582877a16815f5a37cd5f70f8ae08e2d5e26207b4b8e8e0b998672dc85faecc670b909983ce9a87c6ae136b02ca2d62c248b6f3c073297e9e9a84c5a3a76cfde325cffa78aa67d8277c10b875bf04ba53196c5f1f78a86711f8a575d3fc07b01d7261be5080cca273e79835cf629327d604945bcc84916c1781e7c3d8795295098b474fd9fab10f3debe3a79e997eec72e0d6fd43d09dca60fbbaeca19e45e097d64df397613b08b3300bb3300bb3300bb3300bb3300bb3300bb330eb32c22cccc22cccc22cccc2cc9759f3d0b3e7c49a80724b983092ed03e0b9123b4fe6be03f2e829db7d872b9ef5f153cfcc7d87ab815bf72ba03b95c158bdeaa19e45e097d64df357613b08b3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300b336f66cd43dfb020d604945bca84916c9781e75afc3c35098b474fd9ee3b5cf3ac8fa77a86f71dae076eddaf81ee540663f5ba877a16815f5a37cd5f87ed20ccc2ec62d63cf42e44624d40b9654c18c97615786ec4ce93b97f8a3c7acad68fddf0ac8f9f7a66fab19b815bf71ba03b95c158bde9a19e45e097d64df337613be4c27ca100994567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974169da3984567d1398a5974ee3e3a6b1efa4627b126a0dc72268c64bb0e3c1fc5ce535d99b078f494edbec3479ef5f153cfcc7d875b815bf78f40772a83edeb96877a16815f5a37cddf82edd0d5992f1420b3c4467e98253684398a59624398a398253684398a59624398a398253684398a59624398a398253684398a59624398a398253684398a59624398a398253684398a59624398a398253684398a59624398a39839c486e65961f2c49a80722b983092ed26f0b4c6cf5393b078f4946ddc4eab677d3cd5331cb7733b70ebde0aba53196c5fb73dd4b308fcd2ba699efc09b33047316b9e95264fac0928b7920923d96e01cf9df87952098b474fd9fab13b9ef5f154cfb01ffb3870eb7e0774a73218ab1f7ba86711f8a575d3fcc7b01d0a8d59f334987cabf94d40b906268c64bb0d3c1ee22ee4495a3c347f87816f3ddf68f265e617b757233072d85ec93c68566ef1945b9add4fdfbafeab4cfe41f38bdb6b153072d85ee579d0ac9fc5d3cfd2ec7efad65aac36f9bee617b7d76a60e4b0bdfaf9e549252c1e3d653bdef818783e1f3f4f781ef7710e3c9f079ecfc5cf53e5a99e957abddf07ec71ad576bf5054bab8f2dad92500619bee041bf22f04beba679f227ccc21cc58c7d21b126a0dc1d268c64fb1cf0f8e83774dd479975d1fa7baaf450ff36bfadf1fbadc16b0bbdcc7a8983fc9540999707b5b13d66d8cae0ffb4dd747d6e59364fcfbc55b9aef3d23cf92b0bdce7fabeaea1b65a3cb6ef32873e5ab39b0ecd6e7862b49f69a5f91bc0e8baceebeb79d1a8ebcef8bce81d4b476e9ae173c01f018fafe3e2568ba7d5a1858f3697ebf1e5470e9e4fe2e3a9c4d8405f3e6215fbb68ed4ddd57662ac7b15dec3425f1eda40b89f1a6dd645ebd7fb82dafe5e35afc1fe81f653a3ad3a97409949b09f9a92653f551cdc7d5db8c2d8a90cfdff1363b7d75111dcdf6b4b6590af35bf18a36493fd6ffb7b98b5968edc34c376dd0a3cad9e78a2ae89913fdc86750ecdea1868866d211f7176c7e2b17d27419756a69a619ce17183affeec5ed7f2701bd63b34ab67a019c7feacded2919b66aefe0c755ce8e05ec8809b639fb2d0d2919b66ae3e05755ce4e05ec4809b63bb5e64e9c84db37bb5ebc50eeec50cb839b6ebc5968edc34bb57bb5ee2e05ec2809b63bb5e62e9c84db37bb5eba50eeea50cb839b6eba5968edc34bb57bb5ee6e05ec6809b63bb5e66e9c84db37bb5ebe50eeee50cb839b6ebe5968edc34bb57bb5ee1e05ec1809b63bb5e61e9c84db37bb5eb950eee950cb839b6eb95968edc34bb57bb6e70703730e0e6d8ae1b2c1db96996effb0051f79bc81f6ec34687668d0c34e3d8a7345a3a72d32cdff701ee35061bb7e12a8766ab1868c6b13f5b65e9c84db37cf76751f79bc81f6ec3d50ecd5633d08c637fb6dad2919b6651fd596bfc3c398fb76ff5aa4fe63bcbad39f0e098291f31e5290e2a3d8da90ac7dbdbdfaf68b5b44a06777fd3c2e7d8c3a8f191e44f98855998855998855998855998855998855998855998855998855998855998f933e3f3ccade617df857a870923d9f09e948febfcbaee63ccba68fdfad9de5b03dafcc67fdf225589f75ce9b9e331569d4ba0ccff7dbc8ded63c386f71769bb456dcbfbf56e057c1f05de0bba9ff73b3f726876c3a1d9754f8c769f41f3d781d17effc3fd7ef7c41d4b476e9a45f5b3773cf144c5d91d87ef8ad87ca79a7cddd7d5ef22ec13dcbdffbae3d0d4471ce6faee0b6c17ad1e787cbde742d7e9ba55a75b569d92506618d4f37eb62d9ae6018faffe28b07802873e341533e399c18c670c339ec9cc784a99f14c60c653c58c6724339e6798f13cc18ce731663cfd99f1f461c6d38319cf7c663c3399f14c61c63391194f8a19cfb3cc785632e3799219cf32663c8f33e359cc8c6700339e0798f18c66c653c28ce779663cb398f14c65c6b38619cf24663cd5cc78f2710f34179e61cc782a98f10c64c6f310339e05cc7812cc787a32e399cd8c27cd8ca79619cf70663c35cc781a99f13cc78ca78119cf50663ccb99f10c62c6b38419cfc3cc781632e32963c69364c6d38b19cf1c663cd398f1d431e319c78c6715339ea798f10c66c6f308339e11cc781e64c653ce8c672c339edecc78e632e399ce8ca79e19cf78663c95cc785633e3799a19cf0a663c4398f1dc66c6b39419cf22663c8f32e3e9cb8ca71f339e51cc788a18f02482bb9f5dc2e7e56e818d9eb169055bb1637d342e97caebe3ceb383ef5e77b163dd371c0ca8d335a84bdae42b3fddd4eeb9a222b35e9a277f65c0718309cf28663cfd98f1f465c6f328339e45cc789632e3b9cd8c6708339e15cc789e66c6b39a194f25339ef1cc78ea99f14c67c63397194f6f663c6399f19433e3799019cf08663c8f30e319cc8ce729663cab98f18c63c653c78c671a339e39cc787a31e34932e32963c6b39019cfc3cc789630e319c48c6739339ea1cc781a98f13cc78ca791194f0d339ee1cc786a99f1a499f1cc66c6d393194f8219cf02663c0f31e319c88ca78219cf30663c3799f15433e399c48c670d339ea9cc786631e3799e194f09339ed1cc781e60c6338019cf62663c8f33e359c68ce749663c2b99f13ccb8c27c58c6722339e29cc786632e399cf8ca707339e3ecc78fa33e3798c19cf13cc789e61c63392194f15339e09cc784a99f14c66c6338619cf0c663cc5cc78e6593cf87f7dad81ae2f5e071bfdff3fcdcdfb72538feb96bf18ea5119f56d1e5f9ae194766812f56d1e0e3c3398f18c61c63399194f29339e09cc78aa98f18c64c6f30c339e2798f13cc68ca73f339e3ecc787a30e399cf8c6726339e29cc782632e34931e3799619cf4a663c4f32e359c68ce771663c8b99f10c60c6f300339ed1cc784a98f13ccf8c6716339ea9cc78d630e399c48ca79a19cf4d663c15cc780632e3798819cf02663c09663c3d99f1cc66c69366c653cb8c6738339e1a663c8dcc789e63c6d3c08c6728339ee5cc780631e359c28ce761663c0b99f19431e34932e3e9c58c670e339e69cc78ea98f18c63c6b38a19cf53cc780633e3798419cf08663c0f32e32967c63396194f6f663c7399f14c67c653cf8c673c339e4a663cab99f13ccd8c6705339e21cc786e33e359ca8c6711339e4799f1f465c6d38f19cf28663c450c78a2be8543ffef01b6ab267f1b6c574cfe26d82e9bfc75b07de8b0153b58c8df55b0d138972b60a37b7597c146d78bc8973edebd38f86ed662479d7a3858af38ea74d5b12c6e475a261dc4bb1dd1571ae6c91f7eabe72a139e51cc78fa31e3e9cb8ce751663c8b98f12c65c6739b19cf10663c2b98f13ccd8c6735339e4a663ce399f1d433e399ce8c672e339edecc78c632e32967c6f320339e11cc781e61c6339819cf53cc785631e319c78ca78e19cf34663c7398f1f462c69364c653c68c6721339e8799f12c61c6338819cf72663c4399f13430e3798e194f23339e1a663cc399f1d432e34933e399cd8ca727339e04339e05cc781e62c63390194f05339e9bcc78aa99f14c62c6b38619cf54663cb398f13ccf8ca78419cf68663c0f30e319c08c6731339ec799f12c63c6f324339e95cc789e65c69362c6339119cf14663c3399f1cc67c6d383194f1f663cfd99f13cc68ce709663ccf30e319c98ca78a19cf04663ca5cc782633e319c38c6706339e62663cf32c1ebca6580936ca57818df229b051be1a6c94af011be5c7818df2e3c146f90960a3fc44b0517e12d8284ff78cf0f99e7cbc178e7cd1ba69fe1a30de3679dc2694af03ee0f2c9be6bee489fb038b9be62f0123d5e103b051be1eb82f5a36cd7dc113f7458b9be62f0023d5e122d8283f196c949f0236ca4f05db54f04736ca4f031be5a7838df233c046f99960a3fc2cb0517e36d8283f076c949f0b36cacf031be5e7838df20bc046f985e6576fe3f72d9bdec6e74d3e1dc4bb8dc917ad9be6cf03236deff7c146f945c0fd9e65d3dce73c71bf6771d3fc3960a43abc0736ca2f06eeb3964d739ff1c47dd6e2a6f933c04875380b36ca2f01eed3964d739ff2c47ddae2a6f953c04875380d36ca2f05ee93964d739ff0c47dd2e2a6f913c04875380936ca2f03eee3964d731ff3c47ddce2a6f963c04875380e36ca2f07eea3964d731ff1c47dd4e2a6f923c04875380a36caaf00eec3964d731ff2c47dd8e2a6f943c04875380c36caaf04ee83964d73b778e23e6871d37c0b30521d0e828df2f83efa359e186f5b8cb72ddf781f178f2d0f5836cdf8ae27c6031623cdbf0b8c2d267f00780e78e269b1786cdf49d0650d53cd92606b019e5a4f3cd72c9e6b961638de0dcf05d658369fdb758dc548f30780b1c5e4f3d1365b2c1edb771274a965aa59126c2dc053e789e792c573c9d2029f73c173b75acbe673bbd65a8c765f8171867d85afb6d962f1d8be93a04b1d53cdb0cf6d019e7a4f3c172c9e0b96166590c7f3c73acbe673bbd6598c765f8171867d85afb6d962f1d8be93a04b3d53cdb0cf6d019e164f3ce72d9ef3966fdc8678bebfd0b2f9dcae0b2d469ac73ed76e0ba5908f9ba7dee2b17d27419716a69ab9da82cffeec9cc573cef28ddb10afcf2cb26c3eb7eb228b91e61702638bc9e3365ce489a7c5e2b17d2741977aa69ab9da4229b0c5cd73c6e239636981e3f6f07a5abd65f3b95da3fa8f45c0d862f2f9689b2d168fed3b09ba2c66aa191e43b6004f8b279e5316cf29cb376e43bcfeb9c4b2f9dcae4b2c469ac73ed76e0ba5908f9b67b1c563fb4e822e2d4c3573b5059ffdd9098be784e51bb7215eaf5e6ad97c6ed7a51623cd2f01c61693c76db8d4134f8bc563fb4e822e8b996ae66a0b3efbb36316cf31cb376e43bcbfb0ccb2f9dcaecb2c469a5f0a8c765b28857cdc3c51fd19f94b822e2d4c3573b58552608b9be788c573c4d2a20cf2783fa8c5b2f9dcae2d1623cd2f0346d2aa05785a3cf144c519f94b822ecb996a96045b3ef69b872c9e43966fdc8678ff6e8565f3b95d57588c34df028c765b28857cdc3ccb2d1edb77127459cc5433575b401d1b807ba565f3a9ed4a8b9be65700e362878e2b3df144f5292b41c7064b476e9ab9e211756c04ee06cbe653db068bbbc1d2d6158fa5908f9b27aa5d37808e8d968edc3473c56319b0ad02eec596cda7b651eda80118975bdafadcef456debc5a023e9d2c85433bcae89db9ad8560377a365f3a96da3c56db719dcd6d8661a3df1446deb46d0917459c55433ec7bf07cc8d7f947d4fdae7cf88eba07920fdf51d7c5f3e13bea5a693e7c47dd47cf87efa86b2af9f07daff36c9fbea3ae59e6c377d4795f3e7cdfb67cdfcea3efa8f16ff9f01d3526aaabb76fd99774af7dc9fdecd7baebbe44fa739efdf9eaf87da71241fb731a3d1559f369c8e3f9cb2a0f5a78aa67259e137e12e37a5de7d6cb2dadf0dc1acf517d9dffadb678689efc152233c645517cbe2bf13a0c7edf91ae8b54828dae8b55818dae8ba6c0d662f2d560a37b323560a3fb81e3c046f7a2e7838dc6412c001b8dc1b900361afff53ed868ece179b0d1b8d7f7c04663aecf818dc6fb9f051b3d6b72066cef98fc69b0bd6df2a7c0f696c99f04db9b267f026c6f98fc71b0ed37f96360db67f247c1f659933f02b6bd267f186c7b4cfe10d8769b3c5e7bdf65f2782de733268fd7f25e37f9f160db69f213c0f69ac94f04db0e939f04b6ed267f1b6cdb4cfe0ad8b69a3c7e5f748bc97f00b6cd267f096c9b4cfe22d8369afc64b0359bfc14b06d30f9a9606b32f934d8d69bfc34b0ad33f9e9605b6bf233c0f6aac9cf04db2b263f0b6c2f9bfc6cb0bd64f273c0f6a2c9cf05db0b263f0f6c1f9bfc41b07dcee4f13a68b1c9e3f57b7adf14def3a2775ee23d4f7aaf34de6fa76f7fb4808dbeaf85e38c4a4d1ec7b8d17ba5707c25bd4b12c7a9264c1ec79597993c3ed340dffdc06743e8db51f85c52b9c9e33371f4cdcf0360a3ef92be0b367af7d43b601b60f26f838ddee9fc16d8e83b176f828dbeddf406d8e87b9ffbc146ef98da07b6c74dfeb3601b68f27bc136c8e4f7808dbed1b41b6cf49dc85d60a377497d066cf47ec4d7c15661f23bc136d4e45f03db5326bf036cf4fdc5ed60a377466d031bbd77702bd88699fc16b0d1f70e36836db8c96f021b7dc76b23d8e8dd50cd607bdee437806d94c937816db4c9af071bbd9b671dd8c69afc5ab0d13efb55b0d13efb15b0d13efb65b0d13efb25b0d13efb45b0d13efb05b051dfff31d8a8efa7fe43b753dd7e6f99f97410df7194f6d71ab49fb21dcb1303f2c4796c9c041ef47533f6baa7c2e3f08fccba8acd7a295e6e82efebb1fbce9c03dc30ebea69d67bddf25d02654a07b66d9b6bf0ff34d48196c332b46e9a1f03cb5eb3d65d6eea7bc3537daf5b4cc47d0398a84cd9c0b6b25f32f952582646b6f07c96622d000d714a439e18fc68950acf2f3eca81e706f0c4df4e32e7d73e6202db56dce7d7f675193bd69250e63ae8e7ebbd58372c1e9a277fc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc2cc9f59f3d0fd04624d40b95b4c18c986f7ba7c5ce7c77bae78cfebe6c036bfbeefeff532eb1d6bd5b904ca7c6b601bdb6d932f83ffd3768bda961eee1366dd96dfbb5706f5c17b411eb665c873d3e2b17d9739f4d19a5d736876d513a3dd67d03c7ef385f4bb063cbedae3758bc7f68d7dc60da69a45f5b33ec625648b33d718848ad87ca79a7cddd7d563a1f4580e7bff75cbd214c740e03df32f419f44cbdc31bf09d0e70ee8733ffb007b5c02f653adc0187fbf996a770f98fafe3196a63806e2b78cb6340622fe7e2055e9731f416dd41e5b8175a532bf0ffbb93f34791c07740bd6f515c7ff69ca362601fbd4cbf1d739dcbe1f9a75d1f6bdecf0fd01b0c6e4bb0a7d1799447ec85e02f92f0f6c2b6b7f678ab42676fc1616b2dbcbb55acb25a1cc1547fdd341bcf5bf6cf15cb69875ecfc09c4d957e058cf579f742542a331a01195c1635e5ffbaf8f2c1ee2207fd88ff6b6cad0b22550e6ebd047e9ba503fdf6a7ea3f601be8e17a2f60178bc60ef17128e3aeaf8b835a08db7bb8ff5fb2fe82faec1ffd3c1a71febf75f1043aee3525aff58583f71f50ea2f72d54e67f59fb511fe793b82d6d3d712c2195f97fd00fa50665f2b99cd7ddaf73f4a8f33a1fdfcec4730d9ab2eddf711fe3a37ff154cf4ad7beeb9a55a724941906f5f4701ce3dc6fd1fc65f0edeb7ba9e4838ea1ae5b5a944099a4693bd47744e988d7256ee4a52e29e7f1e058475da8cc00a84b29d8e364f2b9ddf0384baff7a6a3ae54e6b1416d65079a7c02b613f6a5231cffa7295b7f40fae93a5f8cbfcee1f6a5e71669fb5e74f87e1f5863f2ddeefd1574bc4f7ec85e02f9e183dacadadfee24adf15bacf47c1db2dbcb5db7964b42994b8efaa78378eb1ff59d59f2a7636708c419c591cf7ef35284466341232a83f70aeceb2d51fb7d1f63e9b3edf76f02a3dd6fe2b18b4f36fb9cc5be5eee3a1ea43278ce426526403f9b7094f57f1f20736c83f5a27a04565d03abae9e8e5f6b8a2c3dd3e06734d8293e4967fa1f1ee3529969d6feccf7756ce2a67ae0fdbaab16f7354b57bc16351bb875dcdbe716789ceef99cb6068fc5ece3a1abc04e65165ac743f671dc6d475decfd747170f7b9e02756597c3629db7276fe236b19bc66e0ba268b7ddf1560499b7ce5a799aa2a2b91a9d8a185ebbea9ab4fa2768031675f47196dad07afa3acb1fa24bbacee936afbb7e943db11afb5dbd7df3d3d9b16b6b95b56fd6d8d3463abc9e336f4710fa33b3e9b561adb7a27ad2fc4f10ca5563e1edfa90db8afc8a6c555078faf6b9a515a5c75f88e4f8bf14daefd874b8b2b0e1e5fe798515a5c71f88e518b8dae6b0b2e2d2e3b787c9d6b446971d9e13b3e2d26b4bbae914d8b0f1d3cf15fd3c8ae05de13cb85f90a03e6522b1f8fef9af5ae7b772e2d3e70f0f8ba7717a5c5070edff1695135de75ceeed2e29283e7529eb5b8e4f01d9f161327b9aea9b8b4b8e8e0f1707d2dab16171dbe638c8b66bcbe964d8b0b0e9e0b79d6e282c3778cc787e3b35d33442dde77f0f8baf617a5c5fb0edf316ab14efb3edf012dce3b78cee7598bf30edff169b17e9cf6fd5e07b478cfc1f35e9eb578cfe13b3e2dd64dd4becf75408b730e9e7379d6e29cc3778ce750615c9ced8016671d3c67f3acc55987eff8b4d8101e6b9de98016671c3c67f2acc51987eff8b4a80cf7a9a73ba0c56907cfe93c6b71dae13bc6b808cf274f75408b530e9e5379d6e294c3778cfb91302e4e76408b930e9e9379d6e2a4c3777c5a6c0caf3f9de88016271c3c27f2acc50987ef18afb9847171bc035a1c77f01ccfb316c71dbee3d3a23adca71eeb8016c71c3cc7f2acc53187eff8b4680eef891ded8016471d3c47f3acc55187ef188f3bc3fee24807b438e2e03992672d8e387cc778dc195ebf38dc012d0e3b780ee7598bc30edf31f69de171e7a10e6871c8c17328cf5a1c72f88ef1b833d4e26007b438e8e03998672d0e3a7cc778dc19ee475a3aa0458b83a725cf5ab4387cc7181761df79a0035a1c70f01cc8b316071cbe63bcae15f69def76408b771d3cefe6598b771dbe633c1f09aff1bdd3012dde71f0bc93672dde71f88ef15e51780cfe7607b478dbc1f3769eb5781b7cfb1867825ad058ac5196162550e681c1995f1a8b15a523ad039fa1c5babc157b5d32e3cade8ca8cb5b50172ad317ea520af638993cd5358c19fa96028d4d6f75d495ca3c3cb8adeca3269f806d721bd635ccf17f9ab28d4122fd749df7c55fe73056e91b10b47df7397cef05d6987c57a16f1a9b4e7ec85e02f96707b795a572a407694decba8dd0373090dd5eee6d6bb92494d9efa87f3a88b7fefb2c9e7d1673f8dc03c419c5919fbe2bc3b43f42a351a01195c1317bad9e78ec3184c441fe7419dafebdad32b46c099419037d148e2ba57a2682bbc74deafabde1a97ee48bd64df3e42f09b61bc068d751c7c74330f693be0142df07d136fade077ebf6bbc65d3759de0a9aee48bd64df3138091be3f323eff8ca98e328eb31835cf240f9ae1375568cab6bf98043c133df078aa67b81faab5ea34c1aa5312cae0b38db51eea59047e69dd345f0bbe7d6c73d482f6c9c32d2d4aa0cc42ebf8314a475a878edff18ebaf8d2719cc533cee17bb2671d69ddd4274ece83ef3acb778de55bb76d8c313d656bdb75c05cef8159af774afceb0ddbf654b32e8a67f25303754a830671d5097d1759eb267b09e49b06dfcd407ad0be93d8753bc2d8895a6e92b55c12ca4c76d43f1d73fda7583cb88df5a48f1b5e84e34a0fed218c81c91607cdd780765322b49b0cda5119dcf75679d2aedee2a1f92ae0a1e3ab5ab0d1710a7ee392fe9fca03b7ddefd53ab8c986df3cac72308e8f9f313cceaab218697e3c3092ad1e78ea3c69666febe1963e784cd0db2a43cb964099fdb05f4e38ca86cf1d17b5d58bbea5f9498cf5d2ebede5412ffcce6700fa04968634114369d0f62dd038791e08dabef5b97bcfce5deb36352f6bcedcf624b4120b137f8b1cd528061be67b386c41d0fe93a62560a34f9af6045bb1250b7e4a95cad3a7137dc8857ad0ba4b2cce526089d3377e0e96a66ca1d31b787c84b20e1dfa24ac099dc65d5bf634637cf4b4383b133bfa7f3db2948b5a17c5418987ba2313ad9be6c99fd6873e8ffbfabaa66dd3766ddabba3f9b53dbb11d66e5c982fb244b07f5dcb60906063a2f5f4b4c4e913bf3835d8786dbe00fc05c012189ed2f879c240a56f0837addbbe7dc9def5dbb734cddefb5ad39e2d3b5f43457b5bca45a96d37793db9ba3a2c6b97efe5b0d9137eb1b937d8e88bcda56023ff7dc0461c0fc0ffec2de1a54d0c83f55318ebff95980af73215a210a4dd90ee4f749bd1af4dd5bb7ffd8968fd4968bdf9f4dbe2f4279ef5279df5279cf5279bf5279af52799f5db24f413defac8614890f984f29341e615b14383cc2790f5278f9f09da3e69aca7df055efd09637da8a13f51ac3f49fc7c90b954383ac8bc564dbfc6421fb2ea432b7d98a74f6bf421b83e2dd7875efa7281be74a40f63f421a23efcd1872cfa105c1f7a4f355a4f0b329f34d79f309f19643e51ae3f49ae3f413e37c87c627cbe4a0b82cca7b6170599cf07ebcf87ebcf76ebcf79ebcf7cebcfcfebcf82ebcf85ebcf88ebcfcceb4f0feb4fd0af0e329fda7e29c87cb6f89520f399e3b541e6b3c8eb83cc6794370499cf2eebcf31ebcf346f0e329f75d69f7bde16643e0fbd23c87c4e5a7f665a7f7e5a7f965a7fae5a7fc65a7fde5a5f72d7b71af425787d795b5f7ed5b740f4ad1f7d6958dfb2d4b770f52ded962033e4e150901912a38708e921537a08991e527722c80cb9d44350f5905c3d44590fd9d643d8f5907efd88837ee4433f02a31f09d28f48e947c6f42374fa3148fd58a17e4c563f36ac1fa3d6977df523e8fa56a0bedcdb1a642e09df09329f5ffe9c4a9f57e9fb54fa824adfafd20fa8f4832afd904a3facd21755fa11957e54a51f53e9c755fa09957e52a59f52e9a755fa19957e56a59f53e9e755fa0595bea4d22faaf44b2afdb24abfa2d2afaaf46b2afdba4abfa1d26faaf45b2afdb64abf1364e2f1f754fa7d95fe40a53f54e98f54fa6395fe44a53f55e9cf54fa7395fe42a5bf54e9af54fa6b95fe46a5bf55e9ef54fab24a5f51e9ef55fa0795fe51a57f52e9ab2afdb34a5f53e9eb2afd8b4adf50e99b2afdab4affa6d2bfabf42d95fe43a56fabf49da0ed33e5d8790c363dce5033bf6ecf9ee61dafefa9d8b3b362c7deed7bb6bcbefd8d8a7d5bf66caed8f9d9e65d1bb7efdc870b7fd92cfcb8999fb66bd7ba372ab6bcb6a1797fc5cebd7b2a766eac58bf73ef6b1bdaed2cffb75968f0dd1ed76dd810edacb4c7a7202debd139a783cc72f4a5fa79d9ebf6448f4e08f24c67169adbc90acd357b203a0d5b9e39e6abd8bd7de79e8aca8ad7d45fb573ddb9af79c3980afcdf6e25f2ee3d15bbf7acdbb5a762e3ae9d3b2aaac6e07a17f6e94425bed8c70f4c6ff35ea24e85ca6f0eec444dbe36b0739be33f3f0de9ffeca4d3b2419da861ffce2c347e50e708d3832265d9bd77fd9e5deb9af6442f3cebd32cbca033d55cddc96af619dc0967e59d5968f4e0ce112ee88cb37d39380bfe3f4095c818d45505009b2d6c6f00000027451f8b08000000000000ffed9d69741cc5b5c77b24595e46832d5bde37b1056fb246a3dd968d8cf70dcc8e018317c960b02d638b7d271b092121fb4202bcac2421fbbeef09d9f7852424908484247c7839efbdf3ce791f38afaa5537fa4f51dd6886be72b5e6f639d7537d55d3f7776fdfaaeea9aa6e3f1304412618daaa959c143c77a3bff79acffc0bdb5a123c569e93339312ceaa947056a784b326259ce352c2599b12cef129e19c9012ce8909726ab6aaa0784b9a7712435c9366cca62ca6752988692e65313d2105319d1ca4a38f9a9212cefa94704e4d09e7b4947036a484737a4a3867a48473664a3867a58473764a38e7a484736e4a38e7a584737e4a3817a48473614a381b53c279624a384f4a09e7c929e13c2541cec5c079aaf97c91f93ccd7c2e329f547789f95c6a3e97191f6bcc7e9392e54a9a359ff5373dd15050d2aaa4cdfa5bbb920e259d4abaccdf1acddfba95ac50b252498f92554a562b39ddc4628d923394ac55b24ec97a251b946c54b249c966255b946c55b24dc97625672a394bc90e25672b3947c9b94ace5372be920b945ca8e4228b65a7928b955ca2e45225bb945ca6e47225bb95ec51b257c93e257d4afa95ec577285922b951c50729592ab951c547248c96125034a8e28b946c95125c7940c2ab956c9754aae57728315b31b95dca4e46625b7589cb72ab94dc9ed4aee5072a792bb94bc58c94b94bc54c9cb94bc5cc9dd4a5ea1e4954aee51f22a25f72a79b592d728b94fc96b95bc4ec9eb95bc41c91b95bc49c99b95bc45c95b95bc4dc9fd4ade6e58a821bc43c9034a1e54f29092ff50f24e25ef52f26e25ef51f25e25ef53f2b092f72bf980920f2a7944c987947c58c947947c54c9c7947c5cc927947c52c9a7947c5ac967947c56c9e7947c5ec917947c51c997947c59c957947c55c9d7947c5dc937947c53c9b7947c5bc977943caae4bb4abea7e4fb4a7e60c5fc874a7ea4e4c74a7e62fe46e34b3f55f23353feb9f9fc85f9fca5f9fc95f59d5f2bf98da57b4cc96f2dddef94fcde941f379f7f309f7f349f4f98cf27cde79fcce79fcde75fcce753e6f3afe6f36fe6f369f3f977f3f90ff3f94ff3f98c92fe86a1f2846078eb0d12ea93dafafbf4dc0405fbd4a078d3b1a8367fa3cf46a3af31fbf449b11b67f6c759fa5ab35f6b1d6782d99f60e9ebcd7ebda59f66f6a759fae9667fbaa59f69f667823e1bc018a5d16b5db551654047795805ba7141714cb4ae960e07baf141712cb48ece632de8261add78d04d32ba09a0cb1add448a99923aa3eb0d92ca89fc1e7ddc5cd2c735f3362724cfbb4f1f773213ef94e479fbf571eb1978757e4c35c79a027933cde8ea4167ba95602ae8a61bdd34d0cd30ba06d0cd34bae9a09b6574334037dbe866826e8ed1cd02dd5ca39b0dba7946370774f38d6e2ee81618dd3cd02d34baf9a06b34ba05a03bd1e81682ee24a36b04ddc9467722e84e31ba934047fde7c9a0a3fbbb538c4ef7093519f88ed1537f147e87fa5cd09d46fd2de816515f0bbac5d4cf826e09d826dd52e84348b7cce8a83fd27feb32e5de20a9fc2ff4e9e376277d5c75647ddc95c91f379cd3ea0986e3da0b76ba2156ab4c39c175332d683b6384ec90be06ca1ba12ed5a378d03585d8f5b5638529af8af95e97f5bd1cd459e1f0bf3748d6ff9516cf4a8b791cf8cf93b3ad2d92b323de4aced9f3a1ae9d7b747f331673760b7030e46cbbe4ec88b79273761fd4b5738fee71c762ce5e041c0c39dbcd93b385bce4ecd0585710b8738f7ee78cc59cdd0f1cc9e76cbbe4ecc8b79273f676a86be71efdd61d8b393b081cc9e76c67b7dc1b8c782b3967ef85ba76eed1b8cb58ccd9bb80832167fba49f1df15672cede0f75eddca331c0b198b3f70147f239dbcd94b3ad92b3c1d0bc6510b8738fc6a3c762ce3e001cc9e7ec3e199f1df95672ce7e06eadab94773236331671f018ee473b68f6b7cb620393bb45e2308dcb947f3746331673f6fca7a6eece7666e6c01e87e61740b41f74b586740ba5f19dd89e017431be8943630e2ade436f06ba86be7f249a63c16dbc00f80832167bb256747bc959cb34f415d3bf768fdc258ccd9df020743ceee959c1df15672cefe17d4b5738fd6d28cc59c7dda94f5fdc2e3e67e6111e8fe60748b41f747a35b02ba278c6e29e89e34ba65a0fb93d13581eecf46b71c747f31ba66d03d657479d0fdd5e85a40f737a32b80ee69a36b05dddf8dae0d74ff30ba76d0fdd3e83a40f78cd1751a9d9ec7a235558f1add04f0bd3748eedc86eba882e22d63edf74279192f4f3e17143f2740b696276fab55fbde148cdcf7e5c0d3cce07b166c8c84a71978f2c9f384bf475b923f6e788e9bac9866c15613f85560f02b03b6e8d8b44ff672a0c37ea3e0606c4d9eb190015b746cda6f0546d2613f46cfe150fbd17df3bccc302f435b0aafcf68af1738c85e0dd4c9340cd76d346c75f077ea03eaa08cfd7cded231e56a982bf870562fecb70023f9981f7dc6c248199b2d46ae7e2303b6e8d8b6ed3a477c74cc5a1d316b63626cb51869bf0d18297eada3cf581829a3dd2f30f5492d23ed93282ef9d18fd988ce6b0e7478afd7e6606c4f9e313caf6d1623edb70323e90ac0c3753d8c6aafbed8e6b80fc17ca66b165d7fc85e0dd469ad1eaebb09aea70c7d68a1d47b53ecd3933f4f853c5ecf46c2c37cee5a98f2318f7de7b341b2b966b7f9bc152b6cf3d89773f593517d39d91366611666611666611666611666611666611666611666611666611666611666ff99a3e6e55ceb198e2723e95a8087639c3f7cf7943916ce013d06f33ac9cf5b14f238574feb1817593ed7409dffcd0cb33dee58278173e7cb2d1dd33aa5f05ce23aa55ed8277bb86e03d74d31ac3d0979965a3cb6ed3a477c7c5c47e2d31a8da8755a3ec52c073a5c0fd7c4c41395674d0edb8d89d92eece3693b85bc7ecf8f7e571ef527761bc1b56f4b2c9dee933aaa87fde6c88152e79df1ba41e524e77131dfd056f2eb6d0a45eb13aa82e26b05de5330ac51299aafa6b511ed96ed1aac5315fcfbdc74c0df7b83e7ae11c23a746cda5f04dfedb08e3d99cfdfd8feb01db8a95c6bf9b60cb8a9cea4aa611f3f67ca4cf733055c3f1b006f60f9441baecf48fe7e6f68bd48a1049e36e0e158bbc6745f9bc77c4c7abd4887152bd7fd32d56987f87530c42f6ecd1ad91366611666611666611666611666611666611666611666611666611666611666ff99f1fd17c48acf48b778c2384a6b6cc2f90c7a6f11ce8bdd5f356c977b0e90e69c165b3ee333ca4f570db33d68caf88e00d7f3ee782eb9e6d6a2ce25d9ab0b9efb9c3ed3b91cf1bb1fda1c31eb70c4ac9389d1ee3368bf1318297e1dc0c3d51edb2d1edb36f6196d9ec62caa9fe55abf129567ae750a8d89d91e5a2fc231af4beb45eceb578b15535c27413a9c07c777abe07a2cdfdeff62af5dc07e0ad76825df6f168ae680edb582640fd7497cc3c496d64924df0f14f29cd7086aa3b426a4e0f095ea3c0ad7b9ef9932ae156a81633de6f83b6d716b12b04f65785766787ee9dd8d747ebb1db657026b42b65bd076c608d9217d0d947f53355cd77e1724c59ad8751bb1df57e9fa5edefa5e0eea7439fcef0d92f5bfdbe2e9b69875eefc10f2ec31b8d7e3ea93ba02778c16418ca80edef332ac2972f691f69a695d87ceff78ab0ede9f529d27a18f8a5a93ee7a0f20d7fd42d47b00f17ec1754f63fb68af1daff4f580ff82fea203fede1bbcf0f580ff821c72dd97d2f117c3f1896b7c107d6da13aff635d47b9dad6f3bdf7caf5db8f7871bd21d5f93fe8ab969975b951bf635ccf5970fd6e887aee83ece13d5529be63bf90f4b511f31159c81ee6638d8935e5634704f772c777c7477c976245ebcbf177b21d3f1d87c4df116efa9b6ecb176a535de0cbbfafdfe00bcf3dd3d0fd67f2be16df0f511fd4eaf095ea4c8577c135987216ce13f695273bfe4e5bdcfd27bed7bc27799fc3f34befefa6f3dbe3b0bd1a5813b2dd82b6e9fe93ec90be06ca27550fd7a57a140f8a35b1eb3642f770c86e7fafddfa5e0eeaac74f8df1b24eb7f8fc5d36331ebdc99097976323c6bc1d557af8c88d1628811d5c1671deddffff85b1eaf31c7ebb73cbef313fbf66510d3d17a67aa3d7eebba3f596cc518ef4f5aa09fcd3aeadae3d2740d49727d3a3e0fb414ece2f3404b99e2990b8ae399b338386d4fb66c4f1e45dbf596edfa51b42d319798fb14739ffe2f0cfc3f2baa52c0589d02c69a14308e4b01636d0a18c7a78071420a1827a68071520a18b3c0783cafed0cf12978f6ff3dc5de6ba06d86f73984b158168c3c164dbc3cb1f73e689be11d2a25ff7f03ccff17564bb9ff17560ebe3735058cd352c0d89002c6e929609c9102c69929609c9502c6d929609c9302c6b929609c9702c6f929605c9002c68529606c4c01e38929603c29058c27a780f19414309e9a02c60929605cc2cb58289751f370bc47331b14afe37a3e1ee6f77a86ef2774bd4394e3ff9d2dd577e677fab694fb0e3c5cbfc0fbff28beb0f7f471ac4f28f53d7d71fff7341363a15c46aef526b8b665243cae3528795ec642b98c5ccf77e0f38623e171fd3fb8bccfdb0cc5ac1c46aef558a5ae17c4e7f0da1d3163602c94cbc8b5a61f9f371c098febb9c03c2f63a15c46aeb5af59b031129e2e8859a723660c8c857219999edd0a63d655020f3ee3d4e588190363a15c46cdb3822966dd25f0ac8098753b62e61323f224fdbef16e872d8e67e24af59d189071620a1827a58011d72070f45f716b10ba79e35328373e5ce72b6e0d02da667806238c05aeb97fbe58f4f0f2c4ae4140dbab986281cf443c5f2c56010fc7331a59b031121e62c8c1f7a6a680715a0a181b52c0383d058c3352c03833058cb352c0383b058c7352c03837058cf352c0383f058c0b52c0b830058cf85b95e15e31f6f7cbaa316e3beab7ca58b71df5bb64acdb963c973caf04db92e792e795605bf25cf2bc126c4b9e4b9e57826dc973c9f34ab02d792e795e09b625cf25cf7db29d86317e611c7b8cc8d3981c4f1e7d475ba77be0fbe90e9e0c93ef68abd703dfa99c36c6d529605c91024689e3d01ac4721835cf1a269ede1278d600cf194c3c6b4ae0390378d626cf13e6d41925f010430ebeb722058cab53c028719438fac42871ac9c380aa3300aa3301e0fc634f4e1729d19faed520ea3e659973c4f18b3b525f0ac8398d1f7f2bc8c85721935cffae479c298ad2b81673dc46c9d23660c8c85721935cf86e479c298ad2f816703c46cbd23660c8c85721935cfc6e479c2986d28816723c46c8323660c8c85721935cfa6e479c2986d2c816713c46ca323660c8c85721935cfe6e479c2986d2a816733c46c9323660c8c85721935cf96e479c2986d2e81670bc46cb323660c8c85721935cfd6e479c2986d2981672bc46c8b23660c8c85721935cfb6e479c2986d2d81671bc46cab2366be32ae4801e3ea143032c7b1502ea3e6d9cec4b3ad049eedc0732613cff61278ce049eb392e70973eacc1278882107df5b9102c6d5296094384a1c7d629438564e1c85511885b134c6d353c028e75a187d6564f87d15fb7cca9963dc76d4f32963dd76d4f32963ddb6e4b9e47925d8963c973caf04db92e792e795605bf25cf2bc126c4b9e4b9e57826dc973c9f34ab02d792e795e09b625cf25cf2bc1b6e4b9e47925d8963c973caf04db92e792e795605bf25cf2bc126c4b9e4b9e57826dc973c9f34ab02d792e795e09b625cf25cf2bc1b6e4b9e47925d8963c973caf04db92e792e73ed9de91bced42a9cfb0ee009eb31862c1e4675e1ff76c73ac67138c9f8ed53956acceb46295833a6743fcce61885f06ecd2b1699fec95cafc220f98996c174e50c79808fe938dd5563cb4fd73997c8feaebcf1de3b6a3fafab16e3baaaf1febb625cf25cf2bc1b6e4b9e47925d8963c973cf7c536966b82e1fb767abf923ec679a63cceec53fdd3e17b54a765fcd0e7e440da10876d694372ada804db92e792e795605bf25cf2bc126c4b9efb99e7e7276f3b9c1bc3df177a8b9b1b3b1f78ce638805939f79edd305964fe75a3ee5a00ebeb3f602063f3360978e4dfb17c079481bb3e65965cac49a857aab3c6124dd79bc3c61fb5a15146f71edeb02e06168072d4c7e86edeb42cba7558eb8531dccd50b19fc74b51ddabf10ce43da9873608f58b341f139f5819174e7f3f284edab3728dee2dad785c0c3d1ff30f919b6af8b2c9f7a1d71a73a98ab1731f8e96a3bb47f119c87b4316b9e35f077bd65a1de1a4f184977012f4f5b167ca62dae7d5d043c1cfd0f939f61fbda69f9b4c61177aa83b9ba93c14f57dba1fd9d701e8459985dcc9ae70c5326d62cd43bc31346d25dc8cad396cf82cfb4c5f5633b8187a39f678a7bd88f5d6cf9748623ee540773f562063f5d6d87f62f86f3500af3ea14324b9c25ce51cc1267897314b3c459e21cc52c71963847314b9c25ce51cc1267897314b3c459e21cc52c71963847314b9c25ce51cc1267897314b3c459e21cc52c71963847314b9c25ce51cc1267897314b3c459e21cc52c71963847314b9c25ce51cc1267897314b3c459e21cc52c71963847314b9ccb63d63c6b4d9958b3506fad278ca4bb8897277c7e676d50bc65acfd5e285f0c3c3b19e2c3e467b8eefd12cba7b58eb8531d6c5f9730f8e96a3bb47f099c87529857a79059e25c1eb3e65967cac49a857aeb3c6124dd4e5e9eb01f5b17146f71fdd825c0c3d1cf33f919f663975a3ead73c49dea60fbba94c14f57dba1fd4be13c08b330bb9835cf7a5326d62cd45bef0923e92e66e52984cf21ae0f8ab7b87eec52e0e1e8e799e21ef663bb2c9fd63be24e7530577731f8e96a3bb4bf0bce4329ccab53c82c71963847314b9c25ce51cc1267897314b3c459e21cc52c71963847314b9c25ce51cc1267897314b3c459e21cc52c71963847314b9c2b27ce9a678329136b16ea6df084917497b0f2b486f30e1b82e22d6ede6117f070cccb30c53d9c77b8ccf2698323ee5407dbd7650c7ebada0eed5f06e761ac33af4e21b3e4c6e8304b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e4863047314b6e087314b3e486304731fb901b9a67a329136b16ea6df484917497f2f284ef3dd818146f71eb762e039e5d0cf161f2335cb773b9e5d34647dca90eb6afcb19fc74b51ddabf1cce83300bb38b59f36c326562cd42bd4d9e30926e172f4fd88f6d0a8ab7b87eec72e0e1e8e799fc0cfbb1dd964f9b1c71a73a98abbb19fc74b51ddadf0de761b7300bb38359f36c366562cd42bdcd9e3092ee325e9eb01fdb1c146f71fdd86ee0e1e8e799fc0cfbb13d964f9b1d71a73a98ab7b18fc74b51dda277bc22ccc51cc9a678b29136b16ea6df184917497f3f214b2e0336d71fdd81ee0d9cd101f263fc37e6cafe5d31647dca90ee6ea5e063f5d6d87f6f7c279481bb3e6d96acac49a857a5b3d6124dd6e5e9eb07d6d0d8ab7b8f6b5177838fa1f263fc3f6b5cff269ab23ee540773751f839faeb643fbfbe03ca48d59f36c336562cd42bd6d9e30926e0f2f4fd8beb605c55b5cfbda073c1cfd0f939f61fbeab37cdae6883bd5c15ced63f0d3d57668bf0fce43da9835cf765326d62cd4dbee0923e9b09fa2ad0a18b7333106166360c507791678c633cb339ea99ef14cf48ca7da339ea59ef1747bc6d3e519cf42cf78167bc633db339e82673cd33ce359ee19cf24cf78da3de3a9f18c6799673ca779c6b3d2339e399ef13478c6b3c4339eac673ce33ce369f28ca7c7339e0ecf78e67ac6d3ea19cf74cf789a3de3a9f38c27e7194fad673c3b3ce399e719cf0ccf784ef08c67b2673ce33de3e9f48c67be673c6d9ef1ccf48ca7c5339e299ef1d47bc633c1339e459ef1643ce0c906cf5d379085bfef005d95f55d7d7dd9d030fc779aa7ad82eff49b72b5e3d87da0a379dd7ec777314e5c73cf68ab17f6c95e1d70f47bc2b3c8339e099ef1d47bc633c5339e16cf78667ac6d3e619cf7ccf783a3de319ef19cf64cf784ef08c6786673cf33ce3d9e1194fad673c39cf78ea3ce369f68c67ba673cad9ef1ccf58ca7c3339e1ecf789a3ce319e7194fd6339e259ef13478c633c7339e959ef19ce619cf32cf786a3ce369f78c6792673ccb3de399e6194fc1339ed99ef12cf68c67a1673c5d9ef1747bc6b3d4339e6acf78267ac633d5339e599ef12cf08ca7cac1b3838927ead9dd1d9ed866380f797ddcfd4c3e5d618e556b8e4bfc64af06ead49b81473ddf80df252e7bbe1dc796af801871bd372167f1d0febe316e7bb2657b7285d8aeb76cd757886dc973c9f34ab02d792e795e09b625cf25cf7db4fd6c72b6db31cfaac0960feba3e5fd49f13cf2fea4789e6acf78e4fd49f13cf2fea4781e797f523c8fbc3f299e47de9f14cf23ef4f8ae7f1edf978799f533c8fbccf299e47dee714cf23ef738ae769f28c47dee714cf23ef738ae791f739c5f3d479c693f38cc7b7f739c9fb93e279e4fd49f13cf2fea4781e797f523c8fbc3f299e47de9f14cf33c1339e459ef1643ce079bef727e17b8f68ade53ed0d17aceb8f72c65e13857808ec607e918fa7a7576c37319aae03b573ab8f63bec919d2b1ddf1d8db8a3ad5ed8277bf83ea62b3de159e419cf04cf78ea3de399e2194f8b673c333de369f38c67be673c9d9ef18cf78c67b2673c2778c633c3339e799ef1d47ac693f38ca7ce339e66cf78a67bc6d3ea19cf5ccf783a3ce3e9f18ca7c9339e719ef1643de359e2194f83673c733ce359e919cf699ef12cf38c6787673c359ef1b47bc633c9339ee59ef14cf38ca7e019cf6ccf78167bc6b3d0339e2ecf78ba3de359ea194fb5673c133de399ea19cf2ccf781678c653e5e0e17a27522e18de7a617f34dec7f47cb6f5fe52888bdeb2f0f7d1784e7287c548fbb8ce0079896729134fd473ef4b3db0adfda7df5a34679185bfe373385c39b5d462a47d574ee13ac2654c3c51cfeb2ff3c0b68e459329d31c7716fede048c5c39b5cc62a47d574ed5f3f2b465c167dae2d6f6609be338874c7ee6b1fd25f88e88bc8ed5762b564d56ac725067349e138cea0fc89e300b7314b3e6a1b11662c5ebd9683c273d1246d7f5958127ec1f9707c55b5cffb81d7838ae1f4c7e86fdd801cba7e58eb8531dccd5030c7ebada0eed1f70d86e0c928dc5552388c5550e9eab46391664af54e61d2964f621ce9a87d64e102bae276ef68491744b7979c2feb13928dee2fac7ab8087e3fac1e467d8275c6df9d4ec883bd5c1f67535839faeb643fb57c3792885f9400a9925cee5316b1e5a534dac59a8d7e20923e9b6b3f214f259f099b6b87eec6ae0e1e8e799e21ef663072d9f5a1c71a73ad8be0e32f8e96a3bb47f10ce83300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330fbcdac79e8591562cd42bd82278ca4bb8a956768dea110146f71f30e078187635e8629eee1bcc321cba78223ee540773f510839faeb643fb87e03c08b3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3dfcc9a87dee94aac59a8d7ea0923e9aee6e5099fdb6a0d8ab7b8798743c0c3312fc3e46738ef70d8f2a9d51177aa83b97a98c14f57dba1fdc3701e8459985dcc9a87feaf0762cd42bd364f18497790956768feb42d28dee2fab1c3c0c3d1cf33c53decc7062c9fda1c71a73a98ab030c7ebada0eed0fc0792885f9400a9925ce12e7286689b3c4398a59e22c718e6296384b9ca39825ce12e7286689b3c4398a59e22c718e6296384b9ca39825ce12e728668973e5c459f3d0ff5145ac59a8d7ee0923e90eb1f2b486f30eed41f11637ef30003c1cf3324c710fe71d8e583eb53be24e75b07d1d61f0d3d57668ff089c87b1ce7c2085cc921ba3c32cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc5ec436e689e0e5326d62cd4ebf0849174877979c2f71e7404c55bdcba9d23c033c0101f263fc3753bd7583e7538e24e75b07d5dc3e0a7abedd0fe35701e8459985dcc9aa7d39489350bf53a3d6124dd002f4f210b3ed316d78f5d033c1cfd3c939f613f76d4f2a9d31177aa83b97a94c14f57dba1fda3701ed2c6ac79ba4c9958b350afcb1346d2e175b98b892767f1e41cb1385eb6f57eb729d799cf2cfcbd1b18b9fac32e8b91f631c7919778ba9978265b3c931db1385eb6b5ff2b4df904f39985bfaf0446ae9ceab61869df955393816725134fbdc553ef88c5f1b2ad63d163ca53cc6716fede038c5c39b5d262a47d574ed5034f0f134f549fd4330ab6a3dad768d88eca95d1b02d318f8e3943bb0bc7077a82e22deebe1aaf2d1c7d15939f79d7f5bbc7f209afdf788f7abcae4fc22ccc51cc4cf7b96d59cb36c527b07868bb863916a3f93bbbdbf2290dbfb3e3980fa49059e25c1eb3b67d2c79db6d59cb36c527b078683bc61c0b263fc3fe603070c798ece5a00ee6e920839f19b04bc7a6fd41380fa5301f4821b3c4b93c666dfbdac46d0fbd7f186d537c028b87b66b9963c1e3e7507f705de08e31d9cb411dccd3eb18fccc805d3a36ed5f07e74198855998855998855998855998855998855998855998855998855998855998fd66d6b6af4fdcf6d0f83ddaa6f804160f6dd733c782c7cfa1f1fb1b02778cc95e0eeae039bf81c1cf0cd8a563d3fe0d701e84599885599885599885599885599885599885599885599885599885599885d96f666dfbc6e46d87cfe3a06d8a4f60f1d07623732c98fc0cc7ef6f0adc31267b39a883e7fc26063f3360978e4dfb37c17910666176316bdb37276e7b683e0f6d537c028b87b69b9963c1e3e7507f704be08e31d9cb411d3ce7b730f89901bb746cdabf05ce4329cc0752c82c71963847314b9c25ce51cc1267897314b3c459e21cc52c71963847314b9c25ce51cc1267897314b3c459e21cc52c71963847314b9c2b27cedaf6ad89db6e0dc7efd136c527b07868bb9539163c7e0e8ddfdf16b8634cf6725007f3f436063f3360978e4dfbb7c17918ebcc0752c82cb9313acc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc921bc21cc52cb921cc51cc3ee486b67d7bf2b6c3e7d9d136c527b07868bb9d39164c7e86eb5fee08dc31267b39a883797a07839f19b04bc7a6fd3be03cdc21ccc2ec60d6b6ef4cde76216bd9a6f804160f6d7732c782c9cfb03fb82b70c798ece5a00e9ef3bb18fccc805d3a36eddf05e7216dcc78fe32c9d90ed76d928d2af3a9752f36e56ad0bdc4946b40f752531e07ba9799722de85e6ecae3417737f846ba5798f212d0bdd2947b40778f29af04ddab4cb91b74f79a7217e85e6dcad780ee35a67c1474f799f231d0bdd6940741f73a53be1674af37e5eb40f70653be1e746f34e51b40f72653be11746f36e59b40f71653be19746f35e55b40f73653be1574f79bf26da07bbb29df0eba7798f21da07bc0947780ee41539e08ba874c7912e8fe03caf4f94e53ae03ddbb4c3907ba779bf209a07b8f294f06dd7b4d790ae8de67caf5a07bd894a782eefda63c0d741f30e506d07dd094a783ee11539e01ba0f99f24cd07dd8946781ee23a63c1b741f35e539a0fb9829cf05ddc74d791ee83e61caf341f749535e00ba4f99f242d07dda94f1fc7ec694ef041df51777818efa8b17838efa8b97808efa8b97828efa8b97818efa8b97838efa8bbb414779f70ad051debd12749477f7808ef2ee55a0a3bcbb17749477af061de5dd6b404779771fe828ef5e0b3acabbd7818ef2eef5a0a3bc7b03e828efde083acabb37818ef2eecda0a3bc7b0be828efde0a3acabbb7818ef2ee7ed051debd1d749477ef001de5dd03a0a3bc7b10748da6fc10e84e3465ec3f4e32e57782ee64537e17e84e31e57783ee54537e0fe85e64caef05dd69a6fc3ed02d32e58741b7d894df0f3aba867d00744b4df983a05b66ca8f80aec9943f04bae5a6fc61d0359bf247409737e58f82aec5943f06ba82297f1c74ada6fc09d0b599f22741d76eca9f025d87297f1a749da64cfd876ea7babd91cf148f3af0afc9c14dba09c0dd1b247b0f46b6e8d8b45f00468a77cbe8331646ca98b718354f1b43cc3087688bfb8dd3063cad0c3c4c7e86bf71da2d9f0a964f39a8f322f0b39dc1cf0cd8a563d37e3bd8e638e7188b5a73dcd3ac58d4409d59e6e2a5af937171a463e8fc6d71f8c215c7bcc59377d8ee628e231d9bfac4ae51b0dd61d96eb66c63bf4f5b5cdbee00e64e06667ddceee48f1bb6ed15e65894cf64a7197c5a093148ca27b49d314276485f03e5e686e1ba548fe241d74e62d7ed88ce25b2dbdf6bb3be97833a5d0eff7b8364fdefb678ba2d66fd3be19486610e86f610e64097c541fbcd10bbee88d87541eca80e5e7bf34cb1ebb478683f0f3c747fd50e3aba4f217ebc9f6b1a056ebbdf6b777093ae0318f30ec6e6e4190b71d7856660245d27f07430c5cc3ed7a759f1c17b82f1561dfa6e0dd45907d7e5aca3ae6e77f332c37ed1effa678364fbf45a8678e1984300f109ac18d2460c1382e1718924792605c3e30ec706078eeeb9a2ff9cfe3d7d1940abb130f133e370a30a7458ae76e882a0787805876f697805876fabacb0e0b00ed5d73fe3b45b3484d17fe8c0e0f987fb0fef3b7ae391c1febe6d035720f5388b1e49a33c4052d4d13621181e08ea0d929db8a9b56cc525cf04f81c9f3c4f0b939fe1456fa2e553ade5530eea8c83bf4d64f0330376e9d8b43fd1613bc18e288cc5a411c462928367d228c70207d349872d95fe8e132d55962fd8a2d1273bcf1375880c9e0ac7cf1838fd37ddd8c71967c607c3279b7a4f7d47ab4f821e85d5572d3dcaaa47557517a4474df5054d8f8aea51503deaa94739f5a8a61ec5d4a3967a94528f4aea51483dead8180c8d2aea51443d6a780a703d0aacfad7bcbe3aea513f3dcaa747f5f45d95feb5a6ef44f49db7be2bd07703faae4fffa2d5a31bfa4aabef62f4155a5f55f55da2be3bd477f3faee56cf66ad52b25ac9e926d66b949ca164ad92754ad62bd9a064a3924d4a362bd9a264ab926d4ab62b3953c959c1d068fdd94ace5172ae92f3949cafe40225172ab948c94e25172bb944c9a54a7629b94cc9e54a762bd9a364af927d4afa94f42bd9afe40a255706432b78ae5272b592834a0e2939ac6440c9916068464dcfa0e919333d43a667c4f40c989ef1d2335c7a464bcf60e9192b3d43a567a4f40c949e71ba23189a5dd0b3097af640cf16e8d9013d1ba047ffef0e8646f7f568fe3dc1d068bd1e9dd7a3f17af45d8fb6ebd1753d9aae47cff568b91e1dd7a3e17af45b8f76ebd16d3d9aad47aff568b51e9dd6a3d17af4f9a1606874598f26ebd1633d5aac4787f568b01efd7d38181addd5a3b97af4568fd6ead1593d1aab475ff568ab1e5dd5a3a97af4548f96ead1513d1aaa473f3fabe4734a3eafe40b4abea8e44b4abeace42b4abeaae46b4abeaee41b4abea9e45b4abeade43bc1504e7e57c9f7947c5fc90f94fc50c98f94fc58c94f94fc54c9cf94fc5cc92f94fc52c9af94fc5ac96f943ca6e4b74a7ea7e4f74a1e57f207257f54f284922795fc49c99f95fc45c9534afeaae46f4a9e56f27725ff50f24f25cf04c3b324d871d498de8646ecf70c0ef61f3a32d83838d078e8da8383078e1cbcb1f1fa038357360e5cd77f74ffc181ebf1cb5f375fa6e98835478feeb9b1f1c0e1befe1b1a07ae1d6c1cd8dfb877e0dac37dc7f04b4f982fcd7baec53d7d7dd1c6fef38590fe779946ab4d3f48133d9be37dabad2e232075e57c295f5d9e437973a5a1d9f97387ee701b8f1d1c186ccc371e56ffee39a8bed3dfb7bc11ff764c05f9d860e3b1c13d47071bf71f1d38d4d8b21c8f3ba5ae0c27663694f1a5b50d23f73cf87f8198955c01d50300", "privateFunctions": [ { "selector": { @@ -44,8 +44,8 @@ exports[`ContractClass creates a contract class from a contract compilation arti "isInternal": false } ], - "id": "0x276edcc83dc452bc2d3bd4457a5506225202ee36e965f84be0aa15fde303f70e", + "id": "0x193b0f08a5af3b2286bff06aaddcf074dd2e467e6fb1e412d747d2b64648866f", "privateFunctionsRoot": "0x05fa82a96814b6294d557d507151f7ccc12f70522ec4d9d0395a90e87e8087c6", - "publicBytecodeCommitment": "0x2a160a6170dde3f0c38aebd58d2140e68854f62996f2d364934df5ff18a11d55" + "publicBytecodeCommitment": "0x0de7228dd1c2821b5dcb66dc4e0de48e1f6a88e2830be489ad6c7006fcd868b8" }" `; diff --git a/yarn-project/circuits.js/src/hints/build_hints.test.ts b/yarn-project/circuits.js/src/hints/build_hints.test.ts index 6cf3572bbf7..9db861afe55 100644 --- a/yarn-project/circuits.js/src/hints/build_hints.test.ts +++ b/yarn-project/circuits.js/src/hints/build_hints.test.ts @@ -1,34 +1,35 @@ +import { makeTuple } from '@aztec/foundation/array'; +import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { padArrayEnd } from '@aztec/foundation/collection'; +import { Fr } from '@aztec/foundation/fields'; +import { Tuple } from '@aztec/foundation/serialize'; + +import { MAX_NEW_NULLIFIERS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX } from '../constants.gen.js'; +import { siloNullifier } from '../hash/index.js'; import { - AztecAddress, - Fr, - MAX_NEW_NULLIFIERS_PER_TX, - MAX_NULLIFIER_READ_REQUESTS_PER_TX, - NullifierReadRequestResetHints, - NullifierReadRequestResetHintsBuilder, + NullifierNonExistentReadRequestHintsBuilder, + NullifierReadRequestHints, + NullifierReadRequestHintsBuilder, PendingReadHint, ReadRequestContext, ReadRequestState, ReadRequestStatus, SettledReadHint, SideEffectLinkedToNoteHash, -} from '@aztec/circuits.js'; -import { siloNullifier } from '@aztec/circuits.js/hash'; -import { makeTuple } from '@aztec/foundation/array'; -import { Tuple } from '@aztec/foundation/serialize'; - -import { HintsBuildingDataOracle, buildNullifierReadRequestResetHints } from './build_hints.js'; +} from '../structs/index.js'; +import { buildNullifierNonExistentReadRequestHints, buildNullifierReadRequestHints } from './build_hints.js'; -describe('buildNullifierReadRequestResetHints', () => { +describe('buildNullifierReadRequestHints', () => { const contractAddress = AztecAddress.random(); const settledNullifierInnerValue = 99999; const settledNullifierValue = makeNullifier(settledNullifierInnerValue).value; - const oracle: HintsBuildingDataOracle = { - getNullifierMembershipWitness: value => + const oracle = { + getNullifierMembershipWitness: (value: Fr) => value.equals(settledNullifierValue) ? ({ membershipWitness: {}, leafPreimage: {} } as any) : undefined, }; let nullifierReadRequests: Tuple; let nullifiers: Tuple; - let expectedHints: NullifierReadRequestResetHints; + let expectedHints: NullifierReadRequestHints; let numReadRequests = 0; let numPendingReads = 0; let numSettledReads = 0; @@ -73,12 +74,12 @@ describe('buildNullifierReadRequestResetHints', () => { numSettledReads++; }; - const buildHints = () => buildNullifierReadRequestResetHints(oracle, nullifierReadRequests, nullifiers); + const buildHints = () => buildNullifierReadRequestHints(oracle, nullifierReadRequests, nullifiers); beforeEach(() => { nullifierReadRequests = makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext.empty); nullifiers = makeTuple(MAX_NEW_NULLIFIERS_PER_TX, i => makeNullifier(innerNullifier(i))); - expectedHints = NullifierReadRequestResetHintsBuilder.empty(); + expectedHints = NullifierReadRequestHintsBuilder.empty(); numReadRequests = 0; numPendingReads = 0; numSettledReads = 0; @@ -118,3 +119,125 @@ describe('buildNullifierReadRequestResetHints', () => { await expect(buildHints()).rejects.toThrow('Read request is reading an unknown nullifier value.'); }); }); + +describe('buildNullifierNonExistentReadRequestHints', () => { + const contractAddress = AztecAddress.random(); + const oracle = { + getLowNullifierMembershipWitness: () => ({ membershipWitness: {}, leafPreimage: {} } as any), + }; + const nonExistentReadRequests = makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext.empty); + let nullifiers = makeTuple(MAX_NEW_NULLIFIERS_PER_TX, SideEffectLinkedToNoteHash.empty); + + const innerNullifier = (index: number) => index + 1; + + const makeReadRequest = (value: number, counter = 2) => + new ReadRequestContext(new Fr(value), counter, contractAddress); + + const makeNullifier = (value: number, counter = 1) => { + const siloedValue = siloNullifier(contractAddress, new Fr(value)); + return new SideEffectLinkedToNoteHash(siloedValue, new Fr(0), new Fr(counter)); + }; + + interface TestNullifier { + value: number; + siloedValue: Fr; + } + + const populateNullifiers = (numNullifiers = MAX_NEW_NULLIFIERS_PER_TX) => { + nullifiers = makeTuple(MAX_NEW_NULLIFIERS_PER_TX, i => + i < numNullifiers ? makeNullifier(innerNullifier(i)) : SideEffectLinkedToNoteHash.empty(), + ); + }; + + const generateSortedNullifiers = (numNullifiers: number) => { + const nullifiers: TestNullifier[] = []; + for (let i = 0; i < numNullifiers; ++i) { + const value = i; + nullifiers.push({ + value, + siloedValue: siloNullifier(contractAddress, new Fr(value)), + }); + } + return nullifiers.sort((a, b) => (b.siloedValue.lt(a.siloedValue) ? 1 : -1)); + }; + + const buildHints = () => buildNullifierNonExistentReadRequestHints(oracle, nonExistentReadRequests, nullifiers); + + it('builds empty hints', async () => { + const hints = await buildHints(); + const emptyHints = NullifierNonExistentReadRequestHintsBuilder.empty(); + expect(hints).toEqual(emptyHints); + }); + + it('builds hints for full sorted nullifiers', async () => { + populateNullifiers(); + + const hints = await buildHints(); + const { sortedPendingValues, sortedPendingValueHints } = hints; + for (let i = 0; i < sortedPendingValues.length - 1; ++i) { + expect(sortedPendingValues[i].value.lt(sortedPendingValues[i + 1].value)).toBe(true); + } + for (let i = 0; i < nullifiers.length; ++i) { + const index = sortedPendingValueHints[i]; + expect(nullifiers[i].value.equals(sortedPendingValues[index].value)).toBe(true); + } + }); + + it('builds hints for half-full sorted nullifiers', async () => { + const numNonEmptyNullifiers = MAX_NEW_NULLIFIERS_PER_TX / 2; + populateNullifiers(numNonEmptyNullifiers); + + const hints = await buildHints(); + const { sortedPendingValues, sortedPendingValueHints } = hints; + + // The first half contains sorted values. + for (let i = 0; i < numNonEmptyNullifiers - 1; ++i) { + expect(sortedPendingValues[i]).not.toEqual(SideEffectLinkedToNoteHash.empty()); + expect(sortedPendingValues[i].value.lt(sortedPendingValues[i + 1].value)).toBe(true); + } + for (let i = 0; i < numNonEmptyNullifiers; ++i) { + const index = sortedPendingValueHints[i]; + expect(nullifiers[i].value.equals(sortedPendingValues[index].value)).toBe(true); + } + + // The second half is empty. + for (let i = numNonEmptyNullifiers; i < sortedPendingValues.length; ++i) { + expect(sortedPendingValues[i]).toEqual(SideEffectLinkedToNoteHash.empty()); + } + for (let i = numNonEmptyNullifiers; i < sortedPendingValueHints.length; ++i) { + expect(sortedPendingValueHints[i]).toBe(0); + } + }); + + it('builds hints for read requests', async () => { + const numNonEmptyNullifiers = MAX_NEW_NULLIFIERS_PER_TX / 2; + expect(numNonEmptyNullifiers > 1).toBe(true); // Need at least 2 nullifiers to test a value in the middle. + + const sortedNullifiers = generateSortedNullifiers(numNonEmptyNullifiers + 3); + const minNullifier = sortedNullifiers.splice(0, 1)[0]; + const maxNullifier = sortedNullifiers.pop()!; + const midIndex = Math.floor(numNonEmptyNullifiers / 2); + const midNullifier = sortedNullifiers.splice(midIndex, 1)[0]; + + nonExistentReadRequests[0] = makeReadRequest(midNullifier.value); + nonExistentReadRequests[1] = makeReadRequest(maxNullifier.value); + nonExistentReadRequests[2] = makeReadRequest(minNullifier.value); + nullifiers = padArrayEnd( + sortedNullifiers.map(n => makeNullifier(n.value)), + SideEffectLinkedToNoteHash.empty(), + MAX_NEW_NULLIFIERS_PER_TX, + ); + + const hints = await buildHints(); + const { nextPendingValueIndices } = hints; + expect(nextPendingValueIndices.slice(0, 3)).toEqual([midIndex, numNonEmptyNullifiers, 0]); + }); + + it('throws if reading existing value', async () => { + populateNullifiers(); + + nonExistentReadRequests[0] = makeReadRequest(innerNullifier(2)); + + await expect(() => buildHints()).rejects.toThrow('Nullifier exists in the pending set.'); + }); +}); diff --git a/yarn-project/circuits.js/src/hints/build_hints.ts b/yarn-project/circuits.js/src/hints/build_hints.ts index d80f4bd5c37..c22df8b15fe 100644 --- a/yarn-project/circuits.js/src/hints/build_hints.ts +++ b/yarn-project/circuits.js/src/hints/build_hints.ts @@ -1,34 +1,35 @@ +import { padArrayEnd } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/fields'; import { Tuple } from '@aztec/foundation/serialize'; +import { IndexedTreeLeafPreimage } from '@aztec/foundation/trees'; import { MAX_NEW_NULLIFIERS_PER_TX, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT, } from '../constants.gen.js'; import { siloNullifier } from '../hash/index.js'; import { MembershipWitness } from '../structs/membership_witness.js'; +import { NullifierNonExistentReadRequestHintsBuilder } from '../structs/non_existent_read_request_hints.js'; import { ReadRequestContext } from '../structs/read_request.js'; -import { NullifierReadRequestResetHintsBuilder } from '../structs/read_request_reset_hints.js'; -import { NullifierLeafPreimage } from '../structs/rollup/nullifier_leaf/index.js'; +import { NullifierReadRequestHintsBuilder } from '../structs/read_request_hints.js'; import { SideEffectLinkedToNoteHash } from '../structs/side_effects.js'; import { countAccumulatedItems } from './utils.js'; export interface NullifierMembershipWitnessWithPreimage { membershipWitness: MembershipWitness; - leafPreimage: NullifierLeafPreimage; + leafPreimage: IndexedTreeLeafPreimage; } -export interface HintsBuildingDataOracle { - getNullifierMembershipWitness(nullifier: Fr): Promise; -} - -export async function buildNullifierReadRequestResetHints( - oracle: HintsBuildingDataOracle, +export async function buildNullifierReadRequestHints( + oracle: { + getNullifierMembershipWitness(nullifier: Fr): Promise; + }, nullifierReadRequests: Tuple, nullifiers: Tuple, ) { - const builder = new NullifierReadRequestResetHintsBuilder(); + const builder = new NullifierReadRequestHintsBuilder(); const numReadRequests = countAccumulatedItems(nullifierReadRequests); @@ -58,3 +59,64 @@ export async function buildNullifierReadRequestResetHints( } return builder.toHints(); } + +interface SortedResult { + sortedValues: Tuple; + sortedIndexHints: Tuple; +} + +function sortNullifiersByValues( + nullifiers: Tuple, +): SortedResult { + const numNullifiers = countAccumulatedItems(nullifiers); + const sorted = nullifiers + .slice(0, numNullifiers) + .map((nullifier, originalIndex) => ({ nullifier, originalIndex })) + .sort((a, b) => (b.nullifier.value.lt(a.nullifier.value) ? 1 : -1)); + + const sortedIndexHints: number[] = []; + for (let i = 0; i < numNullifiers; ++i) { + sortedIndexHints[sorted[i].originalIndex] = i; + } + + return { + sortedValues: padArrayEnd( + sorted.map(s => s.nullifier), + SideEffectLinkedToNoteHash.empty(), + MAX_NEW_NULLIFIERS_PER_TX, + ), + sortedIndexHints: padArrayEnd(sortedIndexHints, 0, MAX_NEW_NULLIFIERS_PER_TX), + }; +} + +export async function buildNullifierNonExistentReadRequestHints( + oracle: { + getLowNullifierMembershipWitness(nullifier: Fr): Promise; + }, + nullifierNonExistentReadRequests: Tuple, + pendingNullifiers: Tuple, +) { + const { sortedValues, sortedIndexHints } = sortNullifiersByValues(pendingNullifiers); + + const builder = new NullifierNonExistentReadRequestHintsBuilder(sortedValues, sortedIndexHints); + + const numPendingNullifiers = countAccumulatedItems(pendingNullifiers); + const numReadRequests = countAccumulatedItems(nullifierNonExistentReadRequests); + for (let i = 0; i < numReadRequests; ++i) { + const readRequest = nullifierNonExistentReadRequests[i]; + const siloedValue = siloNullifier(readRequest.contractAddress, readRequest.value); + + const { membershipWitness, leafPreimage } = await oracle.getLowNullifierMembershipWitness(siloedValue); + + let nextPendingValueIndex = sortedValues.findIndex(v => !v.value.lt(siloedValue)); + if (nextPendingValueIndex == -1) { + nextPendingValueIndex = numPendingNullifiers; + } else if (sortedValues[nextPendingValueIndex].value.equals(siloedValue)) { + throw new Error('Nullifier exists in the pending set.'); + } + + builder.addHint(membershipWitness, leafPreimage, nextPendingValueIndex); + } + + return builder.toHints(); +} diff --git a/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap b/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap index 4f72eacc283..f48aceba2c8 100644 --- a/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap +++ b/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PublicCallStackItem Computes a callstack item hash 1`] = `"0x086b4890110c751f01df5eb163b250f10c90a4f38e73e07e3b5a58685456eaa9"`; +exports[`PublicCallStackItem Computes a callstack item hash 1`] = `"0x187836686ed01f12180ef08c419e4ac8514d9c60e6a38b4a56d893fa90c83a5d"`; -exports[`PublicCallStackItem Computes a callstack item request hash 1`] = `"0x09cb16dc10b48bb544bd5f4293cfd2dee539bd281aa468c0c69a9352df17a307"`; +exports[`PublicCallStackItem Computes a callstack item request hash 1`] = `"0x1a1194c14f229b72d31669b06e3984d6f0f5edd4d5204ceda0ff30f25e910e83"`; -exports[`PublicCallStackItem computes hash 1`] = `Fr<0x198bebc3ae39ac7041b6f6cf91cf2055e577494f8f2145d81601b192f71e762a>`; +exports[`PublicCallStackItem computes hash 1`] = `Fr<0x0ef0cbf32ad96d5f6c7577b023a3b4f9a9cd5d53a8c9eb268324183aaa1437ff>`; diff --git a/yarn-project/circuits.js/src/structs/__snapshots__/public_circuit_public_inputs.test.ts.snap b/yarn-project/circuits.js/src/structs/__snapshots__/public_circuit_public_inputs.test.ts.snap index f42e5c120b2..201d5c667eb 100644 --- a/yarn-project/circuits.js/src/structs/__snapshots__/public_circuit_public_inputs.test.ts.snap +++ b/yarn-project/circuits.js/src/structs/__snapshots__/public_circuit_public_inputs.test.ts.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PublicCircuitPublicInputs computes empty item hash 1`] = `Fr<0x153eea640dd0a53eaa029301381962507fb89e348d42d6f3335107644c6541b9>`; +exports[`PublicCircuitPublicInputs computes empty item hash 1`] = `Fr<0x1c9942cee14a4f84b3e606f553b2ab3151c395822ee7ffd51759d5822375d6c9>`; -exports[`PublicCircuitPublicInputs hash matches snapshot 1`] = `Fr<0x2ae2a860d511acb274dca33de7a64693fe2948275ed149e2db832dd6ce21fc36>`; +exports[`PublicCircuitPublicInputs hash matches snapshot 1`] = `Fr<0x1135d901dacdffe956b9cd85c976a2c5fe311018164a3ec612ff8ed89f8d56cb>`; diff --git a/yarn-project/circuits.js/src/structs/index.ts b/yarn-project/circuits.js/src/structs/index.ts index f18f723e1e0..2a1b8079c16 100644 --- a/yarn-project/circuits.js/src/structs/index.ts +++ b/yarn-project/circuits.js/src/structs/index.ts @@ -39,7 +39,8 @@ export * from './public_call_stack_item.js'; export * from './public_circuit_public_inputs.js'; export * from './read_request.js'; export * from './note_hash_read_request_membership_witness.js'; -export * from './read_request_reset_hints.js'; +export * from './read_request_hints.js'; +export * from './non_existent_read_request_hints.js'; export * from './rollup/append_only_tree_snapshot.js'; export * from './rollup/base_or_merge_rollup_public_inputs.js'; export * from './rollup/base_rollup.js'; diff --git a/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts b/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts index 1e05e125508..cc281831036 100644 --- a/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts +++ b/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts @@ -18,6 +18,7 @@ import { MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NOTE_HASH_READ_REQUESTS_PER_TX, MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX, MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, @@ -743,6 +744,13 @@ export class PublicAccumulatedNonRevertibleData { * The nullifier read requests made in this transaction. */ public nullifierReadRequests: Tuple, + /** + * The nullifier read requests made in this transaction. + */ + public nullifierNonExistentReadRequests: Tuple< + ReadRequestContext, + typeof MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX + >, /** * The new non-revertible commitments made in this transaction. */ @@ -771,6 +779,7 @@ export class PublicAccumulatedNonRevertibleData { toBuffer() { return serializeToBuffer( this.nullifierReadRequests, + this.nullifierNonExistentReadRequests, this.newNoteHashes, this.newNullifiers, this.publicCallStack, @@ -783,6 +792,7 @@ export class PublicAccumulatedNonRevertibleData { const reader = BufferReader.asReader(buffer); return new this( reader.readArray(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext), + reader.readArray(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, ReadRequestContext), reader.readArray(MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, SideEffect), reader.readArray(MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, SideEffectLinkedToNoteHash), reader.readArray(MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, CallRequest), @@ -802,6 +812,7 @@ export class PublicAccumulatedNonRevertibleData { static empty() { return new this( makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext.empty), + makeTuple(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, ReadRequestContext.empty), makeTuple(MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, SideEffect.empty), makeTuple(MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, SideEffectLinkedToNoteHash.empty), makeTuple(MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, CallRequest.empty), @@ -813,6 +824,7 @@ export class PublicAccumulatedNonRevertibleData { static fromPrivateAccumulatedNonRevertibleData(data: PrivateAccumulatedNonRevertibleData) { return new this( makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext.empty), + makeTuple(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, ReadRequestContext.empty), data.newNoteHashes, data.newNullifiers, data.publicCallStack, diff --git a/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_private_inputs.ts b/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_private_inputs.ts index da7f5cd14e1..2f90462d3bd 100644 --- a/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_private_inputs.ts +++ b/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_private_inputs.ts @@ -8,10 +8,7 @@ import { } from '../../constants.gen.js'; import { GrumpkinPrivateKey } from '../../index.js'; import { Fr, GrumpkinScalar } from '../index.js'; -import { - NullifierReadRequestResetHints, - nullifierReadRequestResetHintsFromBuffer, -} from '../read_request_reset_hints.js'; +import { NullifierReadRequestHints, nullifierReadRequestHintsFromBuffer } from '../read_request_hints.js'; import { SideEffect, SideEffectLinkedToNoteHash } from '../side_effects.js'; import { PrivateKernelInnerData } from './private_kernel_inner_data.js'; @@ -47,7 +44,7 @@ export class PrivateKernelTailCircuitPrivateInputs { /** * Contains hints for the nullifier read requests to locate corresponding pending or settled nullifiers. */ - public nullifierReadRequestResetHints: NullifierReadRequestResetHints, + public nullifierReadRequestHints: NullifierReadRequestHints, /** * Contains hints for the transient nullifiers to localize corresponding commitments. */ @@ -70,7 +67,7 @@ export class PrivateKernelTailCircuitPrivateInputs { this.readCommitmentHints, this.sortedNewNullifiers, this.sortedNewNullifiersIndexes, - this.nullifierReadRequestResetHints, + this.nullifierReadRequestHints, this.nullifierCommitmentHints, this.masterNullifierSecretKeys, ); @@ -90,7 +87,7 @@ export class PrivateKernelTailCircuitPrivateInputs { reader.readArray(MAX_NOTE_HASH_READ_REQUESTS_PER_TX, Fr), reader.readArray(MAX_NEW_NULLIFIERS_PER_TX, SideEffectLinkedToNoteHash), reader.readNumbers(MAX_NEW_NULLIFIERS_PER_TX), - reader.readObject({ fromBuffer: nullifierReadRequestResetHintsFromBuffer }), + reader.readObject({ fromBuffer: nullifierReadRequestHintsFromBuffer }), reader.readArray(MAX_NEW_NULLIFIERS_PER_TX, Fr), reader.readArray(MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX, GrumpkinScalar), ); diff --git a/yarn-project/circuits.js/src/structs/kernel/public_kernel_tail_circuit_private_inputs.ts b/yarn-project/circuits.js/src/structs/kernel/public_kernel_tail_circuit_private_inputs.ts index 37d79805d11..77f3bc4cf11 100644 --- a/yarn-project/circuits.js/src/structs/kernel/public_kernel_tail_circuit_private_inputs.ts +++ b/yarn-project/circuits.js/src/structs/kernel/public_kernel_tail_circuit_private_inputs.ts @@ -1,6 +1,7 @@ import { serializeToBuffer } from '@aztec/foundation/serialize'; -import { NullifierReadRequestResetHints } from '../read_request_reset_hints.js'; +import { NullifierNonExistentReadRequestHints } from '../non_existent_read_request_hints.js'; +import { NullifierReadRequestHints } from '../read_request_hints.js'; import { PublicKernelData } from './public_kernel_data.js'; /** @@ -15,10 +16,18 @@ export class PublicKernelTailCircuitPrivateInputs { /** * Contains hints for the nullifier read requests to locate corresponding pending or settled nullifiers. */ - public nullifierReadRequestResetHints: NullifierReadRequestResetHints, + public readonly nullifierReadRequestHints: NullifierReadRequestHints, + /** + * Contains hints for the nullifier non existent read requests. + */ + public readonly nullifierNonExistentReadRequestHints: NullifierNonExistentReadRequestHints, ) {} toBuffer() { - return serializeToBuffer(this.previousKernel, this.nullifierReadRequestResetHints); + return serializeToBuffer( + this.previousKernel, + this.nullifierReadRequestHints, + this.nullifierNonExistentReadRequestHints, + ); } } diff --git a/yarn-project/circuits.js/src/structs/non_existent_read_request_hints.ts b/yarn-project/circuits.js/src/structs/non_existent_read_request_hints.ts new file mode 100644 index 00000000000..9cd254bc0b9 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/non_existent_read_request_hints.ts @@ -0,0 +1,152 @@ +import { makeTuple } from '@aztec/foundation/array'; +import { BufferReader, Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; +import { IndexedTreeLeafPreimage } from '@aztec/foundation/trees'; + +import { + MAX_NEW_NULLIFIERS_PER_TX, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, + NULLIFIER_TREE_HEIGHT, +} from '../constants.gen.js'; +import { MembershipWitness } from './membership_witness.js'; +import { NullifierLeafPreimage } from './rollup/nullifier_leaf/index.js'; +import { SideEffectLinkedToNoteHash, SideEffectType } from './side_effects.js'; + +export class NonMembershipHint { + constructor(public membershipWitness: MembershipWitness, public leafPreimage: LEAF_PREIMAGE) {} + + static empty( + treeHeight: TREE_HEIGHT, + makeEmptyLeafPreimage: () => LEAF_PREIMAGE, + ) { + return new NonMembershipHint(MembershipWitness.empty(treeHeight, 0n), makeEmptyLeafPreimage()); + } + + static fromBuffer( + buffer: Buffer | BufferReader, + treeHeight: TREE_HEIGHT, + leafPreimageFromBuffer: { fromBuffer: (buffer: BufferReader) => LEAF_PREIMAGE }, + ): NonMembershipHint { + const reader = BufferReader.asReader(buffer); + return new NonMembershipHint( + MembershipWitness.fromBuffer(reader, treeHeight), + reader.readObject(leafPreimageFromBuffer), + ); + } + + toBuffer() { + return serializeToBuffer(this.membershipWitness, this.leafPreimage); + } +} + +export class NonExistentReadRequestHints< + READ_REQUEST_LEN extends number, + TREE_HEIGHT extends number, + LEAF_PREIMAGE extends IndexedTreeLeafPreimage, + PENDING_VALUE_LEN extends number, + PENDING_VALUE extends SideEffectType, +> { + constructor( + /** + * The hints for the low leaves of the read requests. + */ + public nonMembershipHints: Tuple, READ_REQUEST_LEN>, + /** + * Indices of the smallest pending values greater than the read requests. + */ + public nextPendingValueIndices: Tuple, + public sortedPendingValues: Tuple, + public sortedPendingValueHints: Tuple, + ) {} + + static fromBuffer< + READ_REQUEST_LEN extends number, + TREE_HEIGHT extends number, + LEAF_PREIMAGE extends IndexedTreeLeafPreimage, + PENDING_VALUE_LEN extends number, + PENDING_VALUE extends SideEffectType, + >( + buffer: Buffer | BufferReader, + readRequestLen: READ_REQUEST_LEN, + treeHeight: TREE_HEIGHT, + leafPreimageFromBuffer: { fromBuffer: (buffer: BufferReader) => LEAF_PREIMAGE }, + pendingValueLen: PENDING_VALUE_LEN, + orderedValueFromBuffer: { fromBuffer: (buffer: BufferReader) => PENDING_VALUE }, + ): NonExistentReadRequestHints { + const reader = BufferReader.asReader(buffer); + return new NonExistentReadRequestHints( + reader.readArray(readRequestLen, { + fromBuffer: buf => NonMembershipHint.fromBuffer(buf, treeHeight, leafPreimageFromBuffer), + }), + reader.readNumbers(readRequestLen), + reader.readArray(pendingValueLen, orderedValueFromBuffer), + reader.readNumbers(pendingValueLen), + ); + } + + toBuffer() { + return serializeToBuffer(this.nonMembershipHints, this.nextPendingValueIndices); + } +} + +export type NullifierNonExistentReadRequestHints = NonExistentReadRequestHints< + typeof MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, + typeof NULLIFIER_TREE_HEIGHT, + IndexedTreeLeafPreimage, + typeof MAX_NEW_NULLIFIERS_PER_TX, + SideEffectLinkedToNoteHash +>; + +export function nullifierNonExistentReadRequestHintsFromBuffer( + buffer: Buffer | BufferReader, +): NullifierNonExistentReadRequestHints { + return NonExistentReadRequestHints.fromBuffer( + buffer, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, + NULLIFIER_TREE_HEIGHT, + NullifierLeafPreimage, + MAX_NEW_NULLIFIERS_PER_TX, + SideEffectLinkedToNoteHash, + ); +} + +export class NullifierNonExistentReadRequestHintsBuilder { + private hints: NullifierNonExistentReadRequestHints; + private readRequestIndex = 0; + + constructor( + sortedPendingNullifiers: Tuple, + sortedPendingNullifierIndexHints: Tuple, + ) { + this.hints = new NonExistentReadRequestHints( + makeTuple(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, () => + NonMembershipHint.empty(NULLIFIER_TREE_HEIGHT, NullifierLeafPreimage.empty), + ), + makeTuple(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, () => 0), + sortedPendingNullifiers, + sortedPendingNullifierIndexHints, + ); + } + + static empty() { + const emptySortedPendingNullifiers = makeTuple(MAX_NEW_NULLIFIERS_PER_TX, SideEffectLinkedToNoteHash.empty); + const emptySortedPendingNullifierIndexHints = makeTuple(MAX_NEW_NULLIFIERS_PER_TX, () => 0); + return new NullifierNonExistentReadRequestHintsBuilder( + emptySortedPendingNullifiers, + emptySortedPendingNullifierIndexHints, + ).toHints(); + } + + addHint( + membershipWitness: MembershipWitness, + lowLeafPreimage: IndexedTreeLeafPreimage, + nextPendingValueIndex: number, + ) { + this.hints.nonMembershipHints[this.readRequestIndex] = new NonMembershipHint(membershipWitness, lowLeafPreimage); + this.hints.nextPendingValueIndices[this.readRequestIndex] = nextPendingValueIndex; + this.readRequestIndex++; + } + + toHints() { + return this.hints; + } +} diff --git a/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts b/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts index f4a23e63ac4..8ee7a9375cb 100644 --- a/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts @@ -11,6 +11,7 @@ import { MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NOTE_HASHES_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL, @@ -48,6 +49,13 @@ export class PublicCircuitPublicInputs { * Nullifier read requests executed during the call. */ public nullifierReadRequests: Tuple, + /** + * Nullifier non existent read requests executed during the call. + */ + public nullifierNonExistentReadRequests: Tuple< + ReadRequest, + typeof MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL + >, /** * Contract storage update requests executed during the call. */ @@ -119,6 +127,7 @@ export class PublicCircuitPublicInputs { Fr.ZERO, makeTuple(RETURN_VALUES_LENGTH, Fr.zero), makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_CALL, ReadRequest.empty), + makeTuple(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, ReadRequest.empty), makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, ContractStorageUpdateRequest.empty), makeTuple(MAX_PUBLIC_DATA_READS_PER_CALL, ContractStorageRead.empty), makeTuple(MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, Fr.zero), @@ -143,6 +152,7 @@ export class PublicCircuitPublicInputs { this.argsHash.isZero() && isFrArrayEmpty(this.returnValues) && isArrayEmpty(this.nullifierReadRequests, item => item.isEmpty()) && + isArrayEmpty(this.nullifierNonExistentReadRequests, item => item.isEmpty()) && isArrayEmpty(this.contractStorageUpdateRequests, item => item.isEmpty()) && isArrayEmpty(this.contractStorageReads, item => item.isEmpty()) && isFrArrayEmpty(this.publicCallStackHashes) && @@ -168,6 +178,7 @@ export class PublicCircuitPublicInputs { fields.argsHash, fields.returnValues, fields.nullifierReadRequests, + fields.nullifierNonExistentReadRequests, fields.contractStorageUpdateRequests, fields.contractStorageReads, fields.publicCallStackHashes, @@ -212,6 +223,7 @@ export class PublicCircuitPublicInputs { reader.readObject(Fr), reader.readArray(RETURN_VALUES_LENGTH, Fr), reader.readArray(MAX_NULLIFIER_READ_REQUESTS_PER_CALL, ReadRequest), + reader.readArray(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, ReadRequest), reader.readArray(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, ContractStorageUpdateRequest), reader.readArray(MAX_PUBLIC_DATA_READS_PER_CALL, ContractStorageRead), reader.readArray(MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, Fr), @@ -234,6 +246,7 @@ export class PublicCircuitPublicInputs { reader.readField(), reader.readFieldArray(RETURN_VALUES_LENGTH), reader.readArray(MAX_NULLIFIER_READ_REQUESTS_PER_CALL, ReadRequest), + reader.readArray(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, ReadRequest), reader.readArray(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, ContractStorageUpdateRequest), reader.readArray(MAX_PUBLIC_DATA_READS_PER_CALL, ContractStorageRead), reader.readFieldArray(MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL), diff --git a/yarn-project/circuits.js/src/structs/read_request_reset_hints.ts b/yarn-project/circuits.js/src/structs/read_request_hints.ts similarity index 82% rename from yarn-project/circuits.js/src/structs/read_request_reset_hints.ts rename to yarn-project/circuits.js/src/structs/read_request_hints.ts index ef43419940a..880b71a4467 100644 --- a/yarn-project/circuits.js/src/structs/read_request_reset_hints.ts +++ b/yarn-project/circuits.js/src/structs/read_request_hints.ts @@ -1,5 +1,6 @@ import { makeTuple } from '@aztec/foundation/array'; import { BufferReader, Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; +import { TreeLeafPreimage } from '@aztec/foundation/trees'; import { MAX_NULLIFIER_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT } from '../constants.gen.js'; import { MembershipWitness } from './membership_witness.js'; @@ -45,35 +46,31 @@ export class PendingReadHint { } } -interface LeafPreimageHint { - toBuffer(): Buffer; -} - -export class SettledReadHint { +export class SettledReadHint { constructor( public readRequestIndex: number, public membershipWitness: MembershipWitness, - public leafPreimage: LEAF_PREIMAGE_HINT, + public leafPreimage: LEAF_PREIMAGE, ) {} - static nada( + static nada( readRequestLen: number, treeHeight: TREE_HEIGHT, - emptyLeafPreimage: () => LEAF_PREIMAGE_HINT, + emptyLeafPreimage: () => LEAF_PREIMAGE, ) { return new SettledReadHint(readRequestLen, MembershipWitness.empty(treeHeight, 0n), emptyLeafPreimage()); } - static fromBuffer( + static fromBuffer( buffer: Buffer | BufferReader, treeHeight: TREE_HEIGHT, - leafPreimageFromBuffer: { fromBuffer: (buffer: BufferReader) => LEAF_PREIMAGE_HINT }, - ): SettledReadHint { + leafPreimage: { fromBuffer(buffer: BufferReader): LEAF_PREIMAGE }, + ): SettledReadHint { const reader = BufferReader.asReader(buffer); return new SettledReadHint( reader.readNumber(), MembershipWitness.fromBuffer(reader, treeHeight), - reader.readObject(leafPreimageFromBuffer), + reader.readObject(leafPreimage), ); } @@ -90,7 +87,7 @@ export class ReadRequestResetHints< NUM_PENDING_READS extends number, NUM_SETTLED_READS extends number, TREE_HEIGHT extends number, - LEAF_PREIMAGE_HINT extends LeafPreimageHint, + LEAF_PREIMAGE extends TreeLeafPreimage, > { constructor( public readRequestStatuses: Tuple, @@ -101,7 +98,7 @@ export class ReadRequestResetHints< /** * The hints for read requests reading settled values. */ - public settledReadHints: Tuple, NUM_SETTLED_READS>, + public settledReadHints: Tuple, NUM_SETTLED_READS>, ) {} /** @@ -114,15 +111,15 @@ export class ReadRequestResetHints< NUM_PENDING_READS extends number, NUM_SETTLED_READS extends number, TREE_HEIGHT extends number, - LEAF_PREIMAGE_HINT extends LeafPreimageHint, + LEAF_PREIMAGE extends TreeLeafPreimage, >( buffer: Buffer | BufferReader, readRequestLen: READ_REQUEST_LEN, numPendingReads: NUM_PENDING_READS, numSettledReads: NUM_SETTLED_READS, treeHeight: TREE_HEIGHT, - leafPreimageFromBuffer: { fromBuffer: (buffer: BufferReader) => LEAF_PREIMAGE_HINT }, - ): ReadRequestResetHints { + leafPreimageFromBuffer: { fromBuffer: (buffer: BufferReader) => LEAF_PREIMAGE }, + ): ReadRequestResetHints { const reader = BufferReader.asReader(buffer); return new ReadRequestResetHints( reader.readArray(readRequestLen, ReadRequestStatus), @@ -138,17 +135,15 @@ export class ReadRequestResetHints< } } -export type NullifierReadRequestResetHints = ReadRequestResetHints< +export type NullifierReadRequestHints = ReadRequestResetHints< typeof MAX_NULLIFIER_READ_REQUESTS_PER_TX, typeof MAX_NULLIFIER_READ_REQUESTS_PER_TX, typeof MAX_NULLIFIER_READ_REQUESTS_PER_TX, typeof NULLIFIER_TREE_HEIGHT, - NullifierLeafPreimage + TreeLeafPreimage >; -export function nullifierReadRequestResetHintsFromBuffer( - buffer: Buffer | BufferReader, -): NullifierReadRequestResetHints { +export function nullifierReadRequestHintsFromBuffer(buffer: Buffer | BufferReader): NullifierReadRequestHints { return ReadRequestResetHints.fromBuffer( buffer, MAX_NULLIFIER_READ_REQUESTS_PER_TX, @@ -159,8 +154,8 @@ export function nullifierReadRequestResetHintsFromBuffer( ); } -export class NullifierReadRequestResetHintsBuilder { - private hints: NullifierReadRequestResetHints; +export class NullifierReadRequestHintsBuilder { + private hints: NullifierReadRequestHints; private numPendingReadHints = 0; private numSettledReadHints = 0; @@ -175,7 +170,7 @@ export class NullifierReadRequestResetHintsBuilder { } static empty() { - return new NullifierReadRequestResetHintsBuilder().toHints(); + return new NullifierReadRequestHintsBuilder().toHints(); } addPendingReadRequest(readRequestIndex: number, nullifierIndex: number) { @@ -190,7 +185,7 @@ export class NullifierReadRequestResetHintsBuilder { addSettledReadRequest( readRequestIndex: number, membershipWitness: MembershipWitness, - leafPreimage: NullifierLeafPreimage, + leafPreimage: TreeLeafPreimage, ) { this.hints.readRequestStatuses[readRequestIndex] = new ReadRequestStatus( ReadRequestState.SETTLED, diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index 8b9e7822f70..4a08435fb34 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -47,6 +47,8 @@ import { MAX_NOTE_HASH_READ_REQUESTS_PER_TX, MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL, MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_PRIVATE_CALL_STACK_LENGTH_PER_CALL, @@ -357,6 +359,7 @@ export function makeCombinedAccumulatedNonRevertibleData(seed = 1, full = false) return new PublicAccumulatedNonRevertibleData( tupleGenerator(MAX_NULLIFIER_READ_REQUESTS_PER_TX, makeReadRequestContext, seed + 0x91), + tupleGenerator(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, makeReadRequestContext, seed + 0x95), tupleGenerator(MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, sideEffectFromNumber, seed + 0x101), tupleGenerator(MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, sideEffectLinkedFromNumber, seed + 0x201), tupleGenerator(MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, makeCallRequest, seed + 0x501), @@ -415,6 +418,7 @@ export function makePublicCircuitPublicInputs( fr(seed + 0x100), tupleGenerator(RETURN_VALUES_LENGTH, fr, seed + 0x200), tupleGenerator(MAX_NULLIFIER_READ_REQUESTS_PER_CALL, makeReadRequest, seed + 0x400), + tupleGenerator(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, makeReadRequest, seed + 0x420), tupleGenerator(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, makeContractStorageUpdateRequest, seed + 0x400), tupleGenerator(MAX_PUBLIC_DATA_READS_PER_CALL, makeContractStorageRead, seed + 0x500), tupleGenerator(MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, fr, seed + 0x600), diff --git a/yarn-project/cli/CHANGELOG.md b/yarn-project/cli/CHANGELOG.md index 21a4fae14ff..3a14034116f 100644 --- a/yarn-project/cli/CHANGELOG.md +++ b/yarn-project/cli/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.27.0](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.26.6...aztec-cli-v0.27.0) (2024-03-12) + + +### Miscellaneous + +* Remove old contract deployment flow ([#4970](https://github.com/AztecProtocol/aztec-packages/issues/4970)) ([6d15947](https://github.com/AztecProtocol/aztec-packages/commit/6d1594736e96cd744ea691a239fcd3a46bdade60)) + ## [0.26.6](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.26.5...aztec-cli-v0.26.6) (2024-03-08) diff --git a/yarn-project/cli/package.json b/yarn-project/cli/package.json index f786232ddb8..2e2f46b5124 100644 --- a/yarn-project/cli/package.json +++ b/yarn-project/cli/package.json @@ -1,6 +1,6 @@ { "name": "@aztec/cli", - "version": "0.26.6", + "version": "0.27.0", "type": "module", "main": "./dest/index.js", "bin": { diff --git a/yarn-project/foundation/src/json-rpc/server/json_rpc_server.ts b/yarn-project/foundation/src/json-rpc/server/json_rpc_server.ts index 08e0533aff4..bd2d537bc54 100644 --- a/yarn-project/foundation/src/json-rpc/server/json_rpc_server.ts +++ b/yarn-project/foundation/src/json-rpc/server/json_rpc_server.ts @@ -73,7 +73,7 @@ export class JsonRpcServer { app.use(compress({ br: false } as any)); app.use( bodyParser({ - jsonLimit: '10mb', + jsonLimit: '50mb', enableTypes: ['json'], detectJSON: () => true, }), diff --git a/yarn-project/foundation/src/trees/index.ts b/yarn-project/foundation/src/trees/index.ts index 195877aec3e..60b025a9e13 100644 --- a/yarn-project/foundation/src/trees/index.ts +++ b/yarn-project/foundation/src/trees/index.ts @@ -23,22 +23,13 @@ export interface IndexedTreeLeaf { } /** - * Preimage of an indexed merkle tree leaf. + * Preimage of a merkle tree leaf. */ -export interface IndexedTreeLeafPreimage { +export interface TreeLeafPreimage { /** * Returns key of the leaf corresponding to this preimage. */ getKey(): bigint; - /** - * Returns the key of the next leaf. - */ - getNextKey(): bigint; - /** - * Returns the index of the next leaf. - */ - getNextIndex(): bigint; - /** * Returns the preimage as a leaf. */ @@ -52,3 +43,14 @@ export interface IndexedTreeLeafPreimage { */ toHashInputs(): Buffer[]; } + +/** + * Preimage of an indexed merkle tree leaf. + */ +export interface IndexedTreeLeafPreimage extends TreeLeafPreimage { + getNextKey(): bigint; + /** + * Returns the index of the next leaf. + */ + getNextIndex(): bigint; +} diff --git a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts index a589a7a071f..fef5e799bb2 100644 --- a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts +++ b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts @@ -34,6 +34,7 @@ import { MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NOTE_HASH_READ_REQUESTS_PER_TX, MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX, MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, @@ -48,11 +49,13 @@ import { MergeRollupInputs, NULLIFIER_TREE_HEIGHT, NUM_FIELDS_PER_SHA256, + NonMembershipHint, NoteHashReadRequestMembershipWitness, NullifierKeyValidationRequest, NullifierKeyValidationRequestContext, NullifierLeafPreimage, - NullifierReadRequestResetHints, + NullifierNonExistentReadRequestHints, + NullifierReadRequestHints, PUBLIC_DATA_TREE_HEIGHT, PartialStateReference, PendingReadHint, @@ -136,7 +139,7 @@ import { PrivateKernelInnerData as PrivateKernelInnerDataNoir, } from './types/private_kernel_inner_types.js'; import { - NullifierReadRequestResetHints as NullifierReadRequestResetHintsNoir, + NullifierReadRequestHints as NullifierReadRequestHintsNoir, NullifierSettledReadHint as NullifierSettledReadHintNoir, PendingReadHint as PendingReadHintNoir, PrivateAccumulatedNonRevertibleData as PrivateAccumulatedNonRevertibleDataNoir, @@ -159,7 +162,11 @@ import { StorageRead as StorageReadNoir, StorageUpdateRequest as StorageUpdateRequestNoir, } from './types/public_kernel_setup_types.js'; -import { PublicKernelTailCircuitPrivateInputs as PublicKernelTailCircuitPrivateInputsNoir } from './types/public_kernel_tail_types.js'; +import { + NullifierNonExistentReadRequestHints as NullifierNonExistentReadRequestHintsNoir, + NullifierNonMembershipHint as NullifierNonMembershipHintNoir, + PublicKernelTailCircuitPrivateInputs as PublicKernelTailCircuitPrivateInputsNoir, +} from './types/public_kernel_tail_types.js'; import { ArchiveRootMembershipWitness as ArchiveRootMembershipWitnessNoir, BaseRollupInputs as BaseRollupInputsNoir, @@ -876,9 +883,7 @@ function mapNullifierSettledReadHintToNoir( }; } -function mapNullifierReadRequestResetHintsToNoir( - hints: NullifierReadRequestResetHints, -): NullifierReadRequestResetHintsNoir { +function mapNullifierReadRequestHintsToNoir(hints: NullifierReadRequestHints): NullifierReadRequestHintsNoir { return { read_request_statuses: mapTuple(hints.readRequestStatuses, mapReadRequestStatusToNoir), pending_read_hints: mapTuple(hints.pendingReadHints, mapPendingReadHintToNoir), @@ -886,6 +891,26 @@ function mapNullifierReadRequestResetHintsToNoir( }; } +function mapNullifierNonMembershipHintToNoir( + hint: NonMembershipHint, +): NullifierNonMembershipHintNoir { + return { + low_leaf_preimage: mapNullifierLeafPreimageToNoir(hint.leafPreimage), + membership_witness: mapNullifierMembershipWitnessToNoir(hint.membershipWitness), + }; +} + +function mapNullifierNonExistentReadRequestHintsToNoir( + hints: NullifierNonExistentReadRequestHints, +): NullifierNonExistentReadRequestHintsNoir { + return { + non_membership_hints: mapTuple(hints.nonMembershipHints, mapNullifierNonMembershipHintToNoir), + sorted_pending_values: mapTuple(hints.sortedPendingValues, mapSideEffectLinkedToNoir), + sorted_pending_value_index_hints: mapTuple(hints.sortedPendingValueHints, mapNumberToNoir), + next_pending_value_indices: mapTuple(hints.nextPendingValueIndices, mapNumberToNoir), + }; +} + /** * Maps combined accumulated data from noir to the parsed type. * @param combinedAccumulatedData - The noir combined accumulated data. @@ -1151,6 +1176,7 @@ export function mapPublicAccumulatedNonRevertibleDataToNoir( ): PublicAccumulatedNonRevertibleDataNoir { return { nullifier_read_requests: mapTuple(data.nullifierReadRequests, mapReadRequestContextToNoir), + nullifier_non_existent_read_requests: mapTuple(data.nullifierNonExistentReadRequests, mapReadRequestContextToNoir), new_note_hashes: mapTuple(data.newNoteHashes, mapSideEffectToNoir), new_nullifiers: mapTuple(data.newNullifiers, mapSideEffectLinkedToNoir), public_call_stack: mapTuple(data.publicCallStack, mapCallRequestToNoir), @@ -1303,7 +1329,7 @@ export function mapPrivateKernelTailCircuitPrivateInputsToNoir( read_commitment_hints: mapTuple(inputs.readCommitmentHints, mapFieldToNoir), sorted_new_nullifiers: mapTuple(inputs.sortedNewNullifiers, mapSideEffectLinkedToNoir), sorted_new_nullifiers_indexes: mapTuple(inputs.sortedNewNullifiersIndexes, mapNumberToNoir), - nullifier_read_request_reset_hints: mapNullifierReadRequestResetHintsToNoir(inputs.nullifierReadRequestResetHints), + nullifier_read_request_hints: mapNullifierReadRequestHintsToNoir(inputs.nullifierReadRequestHints), nullifier_commitment_hints: mapTuple(inputs.nullifierCommitmentHints, mapFieldToNoir), master_nullifier_secret_keys: mapTuple(inputs.masterNullifierSecretKeys, mapGrumpkinPrivateKeyToNoir), }; @@ -1323,7 +1349,10 @@ export function mapPublicKernelTailCircuitPrivateInputsToNoir( ): PublicKernelTailCircuitPrivateInputsNoir { return { previous_kernel: mapPublicKernelDataToNoir(inputs.previousKernel), - nullifier_read_request_reset_hints: mapNullifierReadRequestResetHintsToNoir(inputs.nullifierReadRequestResetHints), + nullifier_read_request_hints: mapNullifierReadRequestHintsToNoir(inputs.nullifierReadRequestHints), + nullifier_non_existent_read_request_hints: mapNullifierNonExistentReadRequestHintsToNoir( + inputs.nullifierNonExistentReadRequestHints, + ), }; } @@ -1347,6 +1376,11 @@ export function mapPublicAccumulatedNonRevertibleDataFromNoir( ): PublicAccumulatedNonRevertibleData { return new PublicAccumulatedNonRevertibleData( mapTupleFromNoir(data.nullifier_read_requests, MAX_NULLIFIER_READ_REQUESTS_PER_TX, mapReadRequestContextFromNoir), + mapTupleFromNoir( + data.nullifier_non_existent_read_requests, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, + mapReadRequestContextFromNoir, + ), mapTupleFromNoir(data.new_note_hashes, MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, mapSideEffectFromNoir), mapTupleFromNoir(data.new_nullifiers, MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, mapSideEffectLinkedFromNoir), mapTupleFromNoir( @@ -1477,6 +1511,7 @@ export function mapPublicCircuitPublicInputsToNoir( args_hash: mapFieldToNoir(publicInputs.argsHash), return_values: mapTuple(publicInputs.returnValues, mapFieldToNoir), nullifier_read_requests: mapTuple(publicInputs.nullifierReadRequests, mapReadRequestToNoir), + nullifier_non_existent_read_requests: mapTuple(publicInputs.nullifierNonExistentReadRequests, mapReadRequestToNoir), contract_storage_update_requests: mapTuple( publicInputs.contractStorageUpdateRequests, mapStorageUpdateRequestToNoir, diff --git a/yarn-project/protocol-contracts/src/gas-token/__snapshots__/index.test.ts.snap b/yarn-project/protocol-contracts/src/gas-token/__snapshots__/index.test.ts.snap index e755a54ac91..41071066dc1 100644 --- a/yarn-project/protocol-contracts/src/gas-token/__snapshots__/index.test.ts.snap +++ b/yarn-project/protocol-contracts/src/gas-token/__snapshots__/index.test.ts.snap @@ -2,10 +2,10 @@ exports[`GasToken returns canonical protocol contract 1`] = ` { - "address": AztecAddress<0x118cb39d39b362ecc396e3a975e876feec0efdab065f3ec6441c88cc5060b5d0>, + "address": AztecAddress<0x2b3035985f1a4ed792a70cde88b7fee0f90f361b9e7f5842343fe67530906597>, "instance": { - "address": AztecAddress<0x118cb39d39b362ecc396e3a975e876feec0efdab065f3ec6441c88cc5060b5d0>, - "contractClassId": Fr<0x05598afe7d2ea501cebc711b2264cc4d61a725310870368b0fb6ab6fd9b2c1f2>, + "address": AztecAddress<0x2b3035985f1a4ed792a70cde88b7fee0f90f361b9e7f5842343fe67530906597>, + "contractClassId": Fr<0x065cf6dd52fb349293d583cfc8ac8f4553770e7aa48ed37a5adf679fdbee7be4>, "initializationHash": Fr<0x0bf6e812f14bb029f7cb9c8da8367dd97c068e788d4f21007fd97014eba8cf9f>, "portalContractAddress": EthAddress<0x0000000000000000000000000000000000000000>, "publicKeysHash": Fr<0x27b1d0839a5b23baf12a8d195b18ac288fcf401afb2f70b8a4b529ede5fa9fed>, @@ -18,7 +18,7 @@ exports[`GasToken returns canonical protocol contract 1`] = ` exports[`GasToken returns canonical protocol contract 2`] = ` { "artifactHash": Fr<0x076fb6d7493b075a880eeed90fec7c4c01e0a24d442522449e4d56c26357205f>, - "id": Fr<0x05598afe7d2ea501cebc711b2264cc4d61a725310870368b0fb6ab6fd9b2c1f2>, + "id": Fr<0x065cf6dd52fb349293d583cfc8ac8f4553770e7aa48ed37a5adf679fdbee7be4>, "privateFunctions": [ { "isInternal": false, @@ -27,7 +27,7 @@ exports[`GasToken returns canonical protocol contract 2`] = ` }, ], "privateFunctionsRoot": Fr<0x13b29c3f4a96eb14d5d3539a6308ff9736ad5d67e3f61ffbb7da908e14980828>, - "publicBytecodeCommitment": Fr<0x1a5504cb055534f209253ec7959c014a5f6637ac1be575d3a2cf06bf596f843a>, + "publicBytecodeCommitment": Fr<0x18485f6c7fc11c817ccb544ba30e0c4f9c83742b3d6318d68037290e4e8c1c51>, "version": 1, } `; diff --git a/yarn-project/pxe/src/kernel_prover/hints_builder.ts b/yarn-project/pxe/src/kernel_prover/hints_builder.ts index 7f5dfd881e7..eb7ee451963 100644 --- a/yarn-project/pxe/src/kernel_prover/hints_builder.ts +++ b/yarn-project/pxe/src/kernel_prover/hints_builder.ts @@ -13,7 +13,7 @@ import { SideEffect, SideEffectLinkedToNoteHash, SideEffectType, - buildNullifierReadRequestResetHints, + buildNullifierReadRequestHints, } from '@aztec/circuits.js'; import { makeTuple } from '@aztec/foundation/array'; import { Tuple } from '@aztec/foundation/serialize'; @@ -74,11 +74,11 @@ export class HintsBuilder { return hints; } - getNullifierReadRequestResetHints( + getNullifierReadRequestHints( nullifierReadRequests: Tuple, nullifiers: Tuple, ) { - return buildNullifierReadRequestResetHints(this, nullifierReadRequests, nullifiers); + return buildNullifierReadRequestHints(this, nullifierReadRequests, nullifiers); } async getNullifierMembershipWitness(nullifier: Fr) { diff --git a/yarn-project/pxe/src/kernel_prover/kernel_prover.ts b/yarn-project/pxe/src/kernel_prover/kernel_prover.ts index 5ecb98cfba7..b761f93855b 100644 --- a/yarn-project/pxe/src/kernel_prover/kernel_prover.ts +++ b/yarn-project/pxe/src/kernel_prover/kernel_prover.ts @@ -189,7 +189,7 @@ export class KernelProver { sortedNoteHashes, ); - const nullifierReadRequestResetHints = await this.hintsBuilder.getNullifierReadRequestResetHints( + const nullifierReadRequestHints = await this.hintsBuilder.getNullifierReadRequestHints( output.publicInputs.end.nullifierReadRequests, output.publicInputs.end.newNullifiers, ); @@ -214,7 +214,7 @@ export class KernelProver { readNoteHashHints, sortedNullifiers, sortedNullifiersIndexes, - nullifierReadRequestResetHints, + nullifierReadRequestHints, nullifierNoteHashHints, masterNullifierSecretKeys, ); diff --git a/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts b/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts index 063ba0b88fd..86f39110bcc 100644 --- a/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts +++ b/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts @@ -13,6 +13,7 @@ import { MAX_NEW_NULLIFIERS_PER_CALL, MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL, @@ -264,13 +265,22 @@ export abstract class AbstractPhaseManager { if (this.phase === PublicKernelPhase.TAIL) { const { endNonRevertibleData, end } = previousOutput; - const nullifierReadRequestResetHints = await this.hintsBuilder.getNullifierReadRequestResetHints( + const nullifierReadRequestHints = await this.hintsBuilder.getNullifierReadRequestHints( endNonRevertibleData.nullifierReadRequests, end.nullifierReadRequests, endNonRevertibleData.newNullifiers, end.newNullifiers, ); - const inputs = new PublicKernelTailCircuitPrivateInputs(previousKernel, nullifierReadRequestResetHints); + const nullifierNonExistentReadRequestHints = await this.hintsBuilder.getNullifierNonExistentReadRequestHints( + endNonRevertibleData.nullifierNonExistentReadRequests, + endNonRevertibleData.newNullifiers, + end.newNullifiers, + ); + const inputs = new PublicKernelTailCircuitPrivateInputs( + previousKernel, + nullifierReadRequestHints, + nullifierNonExistentReadRequestHints, + ); return this.publicKernel.publicKernelCircuitTail(inputs); } @@ -329,6 +339,11 @@ export abstract class AbstractPhaseManager { ReadRequest.empty(), MAX_NULLIFIER_READ_REQUESTS_PER_CALL, ), + nullifierNonExistentReadRequests: padArrayEnd( + result.nullifierNonExistentReadRequests, + ReadRequest.empty(), + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL, + ), contractStorageReads: padArrayEnd( result.contractStorageReads, ContractStorageRead.empty(), diff --git a/yarn-project/sequencer-client/src/sequencer/hints_builder.ts b/yarn-project/sequencer-client/src/sequencer/hints_builder.ts index d7abe9fa0d9..1f5b17c7cf2 100644 --- a/yarn-project/sequencer-client/src/sequencer/hints_builder.ts +++ b/yarn-project/sequencer-client/src/sequencer/hints_builder.ts @@ -3,14 +3,15 @@ import { Fr, MAX_NEW_NULLIFIERS_PER_TX, MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, + MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_REVERTIBLE_NULLIFIERS_PER_TX, MembershipWitness, NULLIFIER_TREE_HEIGHT, - NullifierLeafPreimage, ReadRequestContext, SideEffectLinkedToNoteHash, - buildNullifierReadRequestResetHints, + buildNullifierNonExistentReadRequestHints, + buildNullifierReadRequestHints, concatAccumulatedData, mergeAccumulatedData, } from '@aztec/circuits.js'; @@ -20,13 +21,13 @@ import { MerkleTreeOperations } from '@aztec/world-state'; export class HintsBuilder { constructor(private db: MerkleTreeOperations) {} - getNullifierReadRequestResetHints( + getNullifierReadRequestHints( nullifierReadRequestsNonRevertible: Tuple, nullifierReadRequestsRevertible: Tuple, nullifiersNonRevertible: Tuple, nullifiersRevertible: Tuple, ) { - return buildNullifierReadRequestResetHints( + return buildNullifierReadRequestHints( this, mergeAccumulatedData( MAX_NULLIFIER_READ_REQUESTS_PER_TX, @@ -37,19 +38,54 @@ export class HintsBuilder { ); } + getNullifierNonExistentReadRequestHints( + nullifierNonExistentReadRequests: Tuple, + nullifiersNonRevertible: Tuple, + nullifiersRevertible: Tuple, + ) { + const pendingNullifiers = concatAccumulatedData( + MAX_NEW_NULLIFIERS_PER_TX, + nullifiersNonRevertible, + nullifiersRevertible, + ); + return buildNullifierNonExistentReadRequestHints(this, nullifierNonExistentReadRequests, pendingNullifiers); + } + async getNullifierMembershipWitness(nullifier: Fr) { const index = await this.db.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer()); if (index === undefined) { return; } - const siblingPath = await this.db.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, index); + return this.getNullifierMembershipWitnessWithPreimage(index); + } + + async getLowNullifierMembershipWitness(nullifier: Fr) { + const res = await this.db.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt()); + if (res === undefined) { + throw new Error(`Cannot find the low leaf for nullifier ${nullifier.toBigInt()}.`); + } + + const { index, alreadyPresent } = res; + if (alreadyPresent) { + throw new Error(`Nullifier ${nullifier.toBigInt()} already exists in the tree.`); + } + + return this.getNullifierMembershipWitnessWithPreimage(index); + } + + private async getNullifierMembershipWitnessWithPreimage(index: bigint) { + const siblingPath = await this.db.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, index); const membershipWitness = new MembershipWitness( NULLIFIER_TREE_HEIGHT, index, siblingPath.toTuple(), ); - const leafPreimage = (await this.db.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index))! as NullifierLeafPreimage; + + const leafPreimage = await this.db.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index); + if (!leafPreimage) { + throw new Error(`Cannot find the leaf preimage at index ${index}.`); + } return { membershipWitness, leafPreimage }; } diff --git a/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts b/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts index c1371204787..1e98b368f0e 100644 --- a/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/public_processor.test.ts @@ -597,6 +597,7 @@ class PublicExecutionResultBuilder { execution: this._execution, nestedExecutions: this._nestedExecutions, nullifierReadRequests: [], + nullifierNonExistentReadRequests: [], contractStorageUpdateRequests: this._contractStorageUpdateRequests, returnValues: this._returnValues, newNoteHashes: [], diff --git a/yarn-project/simulator/src/avm/temporary_executor_migration.ts b/yarn-project/simulator/src/avm/temporary_executor_migration.ts index 999f97fbb69..f33205ae850 100644 --- a/yarn-project/simulator/src/avm/temporary_executor_migration.ts +++ b/yarn-project/simulator/src/avm/temporary_executor_migration.ts @@ -94,6 +94,7 @@ export function temporaryConvertAvmResults( // Disabled. const nestedExecutions: PublicExecutionResult[] = []; const nullifierReadRequests: ReadRequest[] = []; + const nullifierNonExistentReadRequests: ReadRequest[] = []; const newNullifiers: SideEffectLinkedToNoteHash[] = []; const unencryptedLogs = FunctionL2Logs.empty(); const newL2ToL1Messages = newWorldState.newL1Messages.map(() => L2ToL1Message.empty()); @@ -101,6 +102,7 @@ export function temporaryConvertAvmResults( return { execution, nullifierReadRequests, + nullifierNonExistentReadRequests, newNoteHashes, newL2ToL1Messages, newNullifiers, diff --git a/yarn-project/simulator/src/public/execution.ts b/yarn-project/simulator/src/public/execution.ts index cb9c42df332..31d306f3952 100644 --- a/yarn-project/simulator/src/public/execution.ts +++ b/yarn-project/simulator/src/public/execution.ts @@ -30,6 +30,8 @@ export interface PublicExecutionResult { newNullifiers: SideEffectLinkedToNoteHash[]; /** The nullifier read requests emitted in this call. */ nullifierReadRequests: ReadRequest[]; + /** The nullifier non existent read requests emitted in this call. */ + nullifierNonExistentReadRequests: ReadRequest[]; /** The contract storage reads performed by the function. */ contractStorageReads: ContractStorageRead[]; /** The contract storage update requests performed by the function. */ diff --git a/yarn-project/simulator/src/public/executor.ts b/yarn-project/simulator/src/public/executor.ts index 5e307e3a45f..a513baf77e9 100644 --- a/yarn-project/simulator/src/public/executor.ts +++ b/yarn-project/simulator/src/public/executor.ts @@ -85,6 +85,7 @@ export async function executePublicFunction( newL2ToL1Messages: [], newNullifiers: [], nullifierReadRequests: [], + nullifierNonExistentReadRequests: [], contractStorageReads: [], contractStorageUpdateRequests: [], nestedExecutions: [], @@ -102,12 +103,14 @@ export async function executePublicFunction( const { returnValues, nullifierReadRequests: nullifierReadRequestsPadded, + nullifierNonExistentReadRequests: nullifierNonExistentReadRequestsPadded, newL2ToL1Msgs, newNoteHashes: newNoteHashesPadded, newNullifiers: newNullifiersPadded, } = PublicCircuitPublicInputs.fromFields(returnWitness); const nullifierReadRequests = nullifierReadRequestsPadded.filter(v => !v.isEmpty()); + const nullifierNonExistentReadRequests = nullifierNonExistentReadRequestsPadded.filter(v => !v.isEmpty()); const newL2ToL1Messages = newL2ToL1Msgs.filter(v => !v.isEmpty()); const newNoteHashes = newNoteHashesPadded.filter(v => !v.isEmpty()); const newNullifiers = newNullifiersPadded.filter(v => !v.isEmpty()); @@ -134,6 +137,7 @@ export async function executePublicFunction( newL2ToL1Messages, newNullifiers, nullifierReadRequests, + nullifierNonExistentReadRequests, contractStorageReads, contractStorageUpdateRequests, returnValues, diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 543a2887af7..ae278e45d44 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -2787,7 +2787,7 @@ __metadata: version: 0.0.0-use.local resolution: "@noir-lang/noirc_abi@portal:../noir/packages/noirc_abi::locator=%40aztec%2Faztec3-packages%40workspace%3A." dependencies: - "@noir-lang/types": 0.24.0 + "@noir-lang/types": 0.25.0 languageName: node linkType: soft