From 722ec5c3dfdc2a5e467528ed94a25677f8800087 Mon Sep 17 00:00:00 2001 From: Zachary James Williamson Date: Wed, 30 Oct 2024 14:54:29 -0700 Subject: [PATCH 01/21] feat: faster square roots (#2694) We use the Tonelli-Shanks square root algorithm to perform square roots, required for deriving generators on the Grumpkin curve. Our existing implementation uses a slow algorithm that requires ~1,000 field multiplications per square root. This PR implements a newer algorithm by Bernstein that uses precomputed lookup tables to increase performance https://cr.yp.to/papers/sqroot-20011123-retypeset20220327.pdf --- .../ecc/curves/secp256k1/secp256k1.test.cpp | 11 + .../ecc/fields/field_declarations.hpp | 6 +- .../barretenberg/ecc/fields/field_impl.hpp | 236 ++++++++++++------ .../barretenberg_wasm_main/index.ts | 2 +- .../foundation/src/wasm/wasm_module.ts | 2 +- 5 files changed, 178 insertions(+), 79 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.test.cpp index 60d0f24af2a..0132643ced5 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/secp256k1/secp256k1.test.cpp @@ -134,6 +134,17 @@ TEST(secp256k1, TestSqr) } } +TEST(secp256k1, SqrtRandom) +{ + size_t n = 1; + for (size_t i = 0; i < n; ++i) { + secp256k1::fq input = secp256k1::fq::random_element().sqr(); + auto [is_sqr, root] = input.sqrt(); + secp256k1::fq root_test = root.sqr(); + EXPECT_EQ(root_test, input); + } +} + TEST(secp256k1, TestArithmetic) { secp256k1::fq a = secp256k1::fq::random_element(); diff --git a/barretenberg/cpp/src/barretenberg/ecc/fields/field_declarations.hpp b/barretenberg/cpp/src/barretenberg/ecc/fields/field_declarations.hpp index 17f0113101d..af1643bdc1b 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/fields/field_declarations.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/fields/field_declarations.hpp @@ -330,8 +330,10 @@ template struct alignas(32) field { * * @return if the element is a quadratic remainder, if it's not */ - constexpr std::pair sqrt() const noexcept; - + constexpr std::pair sqrt() const noexcept + requires((Params_::modulus_0 & 0x3UL) == 0x3UL); + constexpr std::pair sqrt() const noexcept + requires((Params_::modulus_0 & 0x3UL) != 0x3UL); BB_INLINE constexpr void self_neg() & noexcept; BB_INLINE constexpr void self_to_montgomery_form() & noexcept; diff --git a/barretenberg/cpp/src/barretenberg/ecc/fields/field_impl.hpp b/barretenberg/cpp/src/barretenberg/ecc/fields/field_impl.hpp index 7f92fd299c1..4ab221fc719 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/fields/field_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/fields/field_impl.hpp @@ -452,102 +452,187 @@ template void field::batch_invert(std::span coeffs) noexcept } } +/** + * @brief Implements an optimised variant of Tonelli-Shanks via lookup tables. + * Algorithm taken from https://cr.yp.to/papers/sqroot-20011123-retypeset20220327.pdf + * "FASTER SQUARE ROOTS IN ANNOYING FINITE FIELDS" by D. Bernstein + * Page 5 "Accelerated Discrete Logarithm" + * @tparam T + * @return constexpr field + */ template constexpr field field::tonelli_shanks_sqrt() const noexcept { BB_OP_COUNT_TRACK_NAME("fr::tonelli_shanks_sqrt"); // Tonelli-shanks algorithm begins by finding a field element Q and integer S, // such that (p - 1) = Q.2^{s} - - // We can compute the square root of a, by considering a^{(Q + 1) / 2} = R - // Once we have found such an R, we have - // R^{2} = a^{Q + 1} = a^{Q}a - // If a^{Q} = 1, we have found our square root. - // Otherwise, we have a^{Q} = t, where t is a 2^{s-1}'th root of unity. - // This is because t^{2^{s-1}} = a^{Q.2^{s-1}}. - // We know that (p - 1) = Q.w^{s}, therefore t^{2^{s-1}} = a^{(p - 1) / 2} - // From Euler's criterion, if a is a quadratic residue, a^{(p - 1) / 2} = 1 - // i.e. t^{2^{s-1}} = 1 - - // To proceed with computing our square root, we want to transform t into a smaller subgroup, - // specifically, the (s-2)'th roots of unity. - // We do this by finding some value b,such that - // (t.b^2)^{2^{s-2}} = 1 and R' = R.b - // Finding such a b is trivial, because from Euler's criterion, we know that, - // for any quadratic non-residue z, z^{(p - 1) / 2} = -1 - // i.e. z^{Q.2^{s-1}} = -1 - // => z^Q is a 2^{s-1}'th root of -1 - // => z^{Q^2} is a 2^{s-2}'th root of -1 - // Since t^{2^{s-1}} = 1, we know that t^{2^{s - 2}} = -1 - // => t.z^{Q^2} is a 2^{s - 2}'th root of unity. - - // We can iteratively transform t into ever smaller subgroups, until t = 1. - // At each iteration, we need to find a new value for b, which we can obtain - // by repeatedly squaring z^{Q} - constexpr uint256_t Q = (modulus - 1) >> static_cast(primitive_root_log_size() - 1); - constexpr uint256_t Q_minus_one_over_two = (Q - 1) >> 2; - - // __to_montgomery_form(Q_minus_one_over_two, Q_minus_one_over_two); - field z = coset_generator(0); // the generator is a non-residue - field b = pow(Q_minus_one_over_two); - field r = operator*(b); // r = a^{(Q + 1) / 2} - field t = r * b; // t = a^{(Q - 1) / 2 + (Q + 1) / 2} = a^{Q} + // We can determine s by counting the least significant set bit of `p - 1` + // We pick elements `r, g` such that g = r^Q and r is not a square. + // (the coset generators are all nonresidues and satisfy this condition) + // + // To find the square root of `u`, consider `v = u^(Q - 1 / 2)` + // There exists an integer `e` where uv^2 = g^e (see Theorem 3.1 in paper). + // If `u` is a square, `e` is even and (uvg^{−e/2})^2 = u^2v^2g^e = u^{Q+1}g^{-e} = u + // + // The goal of the algorithm is two fold: + // 1. find `e` given `u` + // 2. compute `sqrt(u) = uvg^{−e/2}` + constexpr uint256_t Q = (modulus - 1) >> static_cast(primitive_root_log_size()); + constexpr uint256_t Q_minus_one_over_two = (Q - 1) >> 1; + field v = pow(Q_minus_one_over_two); + field uv = operator*(v); // uv = u^{(Q + 1) / 2} + // uvv = g^e for some unknown e. Goal is to find e. + field uvv = uv * v; // uvv = u^{(Q - 1) / 2 + (Q + 1) / 2} = u^{Q} // check if t is a square with euler's criterion // if not, we don't have a quadratic residue and a has no square root! - field check = t; + field check = uvv; for (size_t i = 0; i < primitive_root_log_size() - 1; ++i) { check.self_sqr(); } - if (check != one()) { - return zero(); + if (check != 1) { + return 0; } - field t1 = z.pow(Q_minus_one_over_two); - field t2 = t1 * z; - field c = t2 * t1; // z^Q - size_t m = primitive_root_log_size(); + constexpr field g = coset_generator(0).pow(Q); + constexpr field g_inv = coset_generator(0).pow(modulus - 1 - Q); + constexpr size_t root_bits = primitive_root_log_size(); + constexpr size_t table_bits = 6; + constexpr size_t num_tables = root_bits / table_bits + (root_bits % table_bits != 0 ? 1 : 0); + constexpr size_t num_offset_tables = num_tables - 1; + constexpr size_t table_size = static_cast(1UL) << table_bits; + + using GTable = std::array; + constexpr auto get_g_table = [&](const field& h) { + GTable result; + result[0] = 1; + for (size_t i = 1; i < table_size; ++i) { + result[i] = result[i - 1] * h; + } + return result; + }; + constexpr std::array g_tables = [&]() { + field working_base = g_inv; + std::array result; + for (size_t i = 0; i < num_tables; ++i) { + result[i] = get_g_table(working_base); + for (size_t j = 0; j < table_bits; ++j) { + working_base.self_sqr(); + } + } + return result; + }(); + constexpr std::array offset_g_tables = [&]() { + field working_base = g_inv; + for (size_t i = 0; i < root_bits % table_bits; ++i) { + working_base.self_sqr(); + } + std::array result; + for (size_t i = 0; i < num_offset_tables; ++i) { + result[i] = get_g_table(working_base); + for (size_t j = 0; j < table_bits; ++j) { + working_base.self_sqr(); + } + } + return result; + }(); + + constexpr GTable root_table_a = get_g_table(g.pow(1UL << ((num_tables - 1) * table_bits))); + constexpr GTable root_table_b = get_g_table(g.pow(1UL << (root_bits - table_bits))); + // compute uvv^{2^table_bits}, uvv^{2^{table_bits*2}}, ..., uvv^{2^{table_bits*num_tables}} + std::array uvv_powers; + field base = uvv; + for (size_t i = 0; i < num_tables - 1; ++i) { + uvv_powers[i] = base; + for (size_t j = 0; j < table_bits; ++j) { + base.self_sqr(); + } + } + uvv_powers[num_tables - 1] = base; + std::array e_slices; + for (size_t i = 0; i < num_tables; ++i) { + size_t table_index = num_tables - 1 - i; + field target = uvv_powers[table_index]; + for (size_t j = 0; j < i; ++j) { + size_t e_idx = num_tables - 1 - (i - 1) + j; + size_t g_idx = num_tables - 2 - j; + + field g_lookup; + if (j != i - 1) { + g_lookup = offset_g_tables[g_idx - 1][e_slices[e_idx]]; // e1 + } else { + g_lookup = g_tables[g_idx][e_slices[e_idx]]; + } + target *= g_lookup; + } + size_t count = 0; + + if (i == 0) { + for (auto& x : root_table_a) { + if (x == target) { + break; + } + count += 1; + } + } else { + for (auto& x : root_table_b) { + if (x == target) { + break; + } + count += 1; + } + } - while (t != one()) { - size_t i = 0; - field t2m = t; + ASSERT(count != table_size); + e_slices[table_index] = count; + } - // find the smallest value of m, such that t^{2^m} = 1 - while (t2m != one()) { - t2m.self_sqr(); - i += 1; + // We want to compute g^{-e/2} which requires computing `e/2` via our slice representation + for (size_t i = 0; i < num_tables; ++i) { + auto& e_slice = e_slices[num_tables - 1 - i]; + // e_slices[num_tables - 1] is always even. + // From theorem 3.1 (https://cr.yp.to/papers/sqroot-20011123-retypeset20220327.pdf) + // if slice is odd, propagate the downshifted bit into previous slice value + if ((e_slice & 1UL) == 1UL) { + size_t borrow_value = (i == 1) ? 1UL << ((root_bits % table_bits) - 1) : (1UL << (table_bits - 1)); + e_slices[num_tables - i] += borrow_value; } + e_slice >>= 1; + } - size_t j = m - i - 1; - b = c; - while (j > 0) { - b.self_sqr(); - --j; - } // b = z^2^(m-i-1) - - c = b.sqr(); - t = t * c; - r = r * b; - m = i; + field g_pow_minus_e_over_2 = 1; + for (size_t i = 0; i < num_tables; ++i) { + if (i == 0) { + g_pow_minus_e_over_2 *= g_tables[i][e_slices[num_tables - 1 - i]]; + } else { + g_pow_minus_e_over_2 *= offset_g_tables[i - 1][e_slices[num_tables - 1 - i]]; + } } - return r; + return uv * g_pow_minus_e_over_2; } -template constexpr std::pair> field::sqrt() const noexcept +template +constexpr std::pair> field::sqrt() const noexcept + requires((T::modulus_0 & 0x3UL) == 0x3UL) { BB_OP_COUNT_TRACK_NAME("fr::sqrt"); - field root; - if constexpr ((T::modulus_0 & 0x3UL) == 0x3UL) { - constexpr uint256_t sqrt_exponent = (modulus + uint256_t(1)) >> 2; - root = pow(sqrt_exponent); - } else { - root = tonelli_shanks_sqrt(); - } + constexpr uint256_t sqrt_exponent = (modulus + uint256_t(1)) >> 2; + field root = pow(sqrt_exponent); if ((root * root) == (*this)) { return std::pair(true, root); } return std::pair(false, field::zero()); +} -} // namespace bb; +template +constexpr std::pair> field::sqrt() const noexcept + requires((T::modulus_0 & 0x3UL) != 0x3UL) +{ + field root = tonelli_shanks_sqrt(); + if ((root * root) == (*this)) { + return std::pair(true, root); + } + return std::pair(false, field::zero()); +} template constexpr field field::operator/(const field& other) const noexcept { @@ -634,8 +719,8 @@ constexpr std::array, field::COSET_GENERATOR_SIZE> field::compute size_t count = 1; while (count < n) { - // work_variable contains a new field element, and we need to test that, for all previous vector elements, - // result[i] / work_variable is not a member of our subgroup + // work_variable contains a new field element, and we need to test that, for all previous vector + // elements, result[i] / work_variable is not a member of our subgroup field work_inverse = work_variable.invert(); bool valid = true; for (size_t j = 0; j < count; ++j) { @@ -674,8 +759,9 @@ template void field::msgpack_pack(auto& packer) const // The field is first converted from Montgomery form, similar to how the old format did it. auto adjusted = from_montgomery_form(); - // The data is then converted to big endian format using htonll, which stands for "host to network long long". - // This is necessary because the data will be written to a raw msgpack buffer, which requires big endian format. + // The data is then converted to big endian format using htonll, which stands for "host to network long + // long". This is necessary because the data will be written to a raw msgpack buffer, which requires big + // endian format. uint64_t bin_data[4] = { htonll(adjusted.data[3]), htonll(adjusted.data[2]), htonll(adjusted.data[1]), htonll(adjusted.data[0]) }; @@ -693,8 +779,8 @@ template void field::msgpack_unpack(auto o) // The binary data is first extracted from the msgpack object. std::array raw_data = o; - // The binary data is then read as big endian uint64_t's. This is done by casting the raw data to uint64_t* and then - // using ntohll ("network to host long long") to correct the endianness to the host's endianness. + // The binary data is then read as big endian uint64_t's. This is done by casting the raw data to uint64_t* + // and then using ntohll ("network to host long long") to correct the endianness to the host's endianness. uint64_t* cast_data = (uint64_t*)&raw_data[0]; // NOLINT uint64_t reversed[] = { ntohll(cast_data[3]), ntohll(cast_data[2]), ntohll(cast_data[1]), ntohll(cast_data[0]) }; diff --git a/barretenberg/ts/src/barretenberg_wasm/barretenberg_wasm_main/index.ts b/barretenberg/ts/src/barretenberg_wasm/barretenberg_wasm_main/index.ts index 1be981669ae..6dc26bc0097 100644 --- a/barretenberg/ts/src/barretenberg_wasm/barretenberg_wasm_main/index.ts +++ b/barretenberg/ts/src/barretenberg_wasm/barretenberg_wasm_main/index.ts @@ -32,7 +32,7 @@ export class BarretenbergWasmMain extends BarretenbergWasmBase { module: WebAssembly.Module, threads = Math.min(getNumCpu(), BarretenbergWasmMain.MAX_THREADS), logger: (msg: string) => void = debug, - initial = 30, + initial = 31, maximum = 2 ** 16, ) { this.logger = logger; diff --git a/yarn-project/foundation/src/wasm/wasm_module.ts b/yarn-project/foundation/src/wasm/wasm_module.ts index f0c3a15debe..0b597f4b7bf 100644 --- a/yarn-project/foundation/src/wasm/wasm_module.ts +++ b/yarn-project/foundation/src/wasm/wasm_module.ts @@ -79,7 +79,7 @@ export class WasmModule implements IWasmModule { * @param initMethod - Defaults to calling '_initialize'. * @param maximum - 8192 maximum by default. 512mb. */ - public async init(initial = 30, maximum = 8192, initMethod: string | null = '_initialize') { + public async init(initial = 31, maximum = 8192, initMethod: string | null = '_initialize') { this.debug( `initial mem: ${initial} pages, ${(initial * 2 ** 16) / (1024 * 1024)}mb. max mem: ${maximum} pages, ${ (maximum * 2 ** 16) / (1024 * 1024) From ed1deb9afbc7746fe1668fe35978cb159a02dedf Mon Sep 17 00:00:00 2001 From: ludamad Date: Wed, 30 Oct 2024 18:19:50 -0400 Subject: [PATCH 02/21] fix: e2e labels (#9609) These use hyphens while internally things now use underscores --- scripts/ci/get_bench_jobs.sh | 3 ++- scripts/ci/get_e2e_jobs.sh | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/ci/get_bench_jobs.sh b/scripts/ci/get_bench_jobs.sh index a93ef001f9a..d71a266ab4a 100755 --- a/scripts/ci/get_bench_jobs.sh +++ b/scripts/ci/get_bench_jobs.sh @@ -5,7 +5,8 @@ set -eu cd "$(dirname "$0")"/../.. BRANCH=$1 -LABELS=$2 +# support labels with hyphens for backwards compatibility: +LABELS=$(echo $2 | sed 's/-/_/g') # Define the allow_list allow_list=() diff --git a/scripts/ci/get_e2e_jobs.sh b/scripts/ci/get_e2e_jobs.sh index cc8468935ba..2d33dd12f37 100755 --- a/scripts/ci/get_e2e_jobs.sh +++ b/scripts/ci/get_e2e_jobs.sh @@ -5,7 +5,8 @@ set -eu cd "$(dirname "$0")"/../.. BRANCH=$1 -LABELS=$2 +# support labels with hyphens for backwards compatibility: +LABELS=$(echo $2 | sed 's/-/_/g') # Function to parse YAML and extract test names get_test_names() { From 747bff1acbca3d263a765db82baa6ef9ca58c372 Mon Sep 17 00:00:00 2001 From: ludamad Date: Wed, 30 Oct 2024 20:13:29 -0400 Subject: [PATCH 03/21] chore: bb sanitizers on master (#9564) This is free QA, though sanitizers likely aren't clean. Let's enable this and turn off what breaks --- .github/workflows/bb-msan.yml | 25 ----- .github/workflows/bb-sanitizers.yml | 98 ++++++++++++++++++++ .github/workflows/publish-aztec-packages.yml | 1 - barretenberg/cpp/Earthfile | 10 +- 4 files changed, 107 insertions(+), 27 deletions(-) delete mode 100644 .github/workflows/bb-msan.yml create mode 100644 .github/workflows/bb-sanitizers.yml diff --git a/.github/workflows/bb-msan.yml b/.github/workflows/bb-msan.yml deleted file mode 100644 index fe30b2193a4..00000000000 --- a/.github/workflows/bb-msan.yml +++ /dev/null @@ -1,25 +0,0 @@ -# Checks bb prover with memory sanitizer -name: BB MSAN -on: - pull_request: - types: [opened, synchronize, reopened, labeled] - paths: - - 'barretenberg/**' - workflow_dispatch: - inputs: {} -# unlike most jobs, we have no concurrency limits - note that we should have spare capacity (of total 1000 concurrency) -# to run a good number of these, but we should still be mindful that we don't queue, say, dozens of these. -jobs: - bb-msan-check: - if: github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'bb-msan-check') - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - uses: earthly/actions-setup@v1 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - - name: "Barretenberg Prover MSAN Check" - timeout-minutes: 720 # 12 hour timeout. Yes, it's slow, and we just want to use the github runner. - run: earthly ./barretenberg/cpp/+preset-msan-check diff --git a/.github/workflows/bb-sanitizers.yml b/.github/workflows/bb-sanitizers.yml new file mode 100644 index 00000000000..84b2c61d767 --- /dev/null +++ b/.github/workflows/bb-sanitizers.yml @@ -0,0 +1,98 @@ +# Checks bb (barretenberg prover library) prover with sanitizers +# Unlike most jobs uses free 4 core github runners of which we have lots of capacity (of total 1000 concurrency). +name: BB MSAN +on: + push: + branches: + - master + - "*/bb-sanitizers*" + pull_request: + types: [opened, synchronize, reopened, labeled] + paths: + - 'barretenberg/**' + workflow_dispatch: + inputs: {} + +concurrency: + # force parallelism in master + group: ci-${{ github.ref_name == 'master' && github.run_id || github.ref_name }} + cancel-in-progress: true +jobs: + # acts as prover performance baseline + bb-baseline: + if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'bb-msan-check') + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + - uses: earthly/actions-setup@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: "Barretenberg Baseline Performance Check" + run: earthly --no-cache ./barretenberg/cpp/+preset-check --preset=clang16 + + # memory sanitzer for prover + bb-msan-check: + if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'bb-msan-check') + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + - uses: earthly/actions-setup@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: "Barretenberg Prover MSAN Check" + + run: earthly --no-cache ./barretenberg/cpp/+preset-msan-check + + # address sanitzer for prover + bb-asan-check: + if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'bb-asan-check') + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + - uses: earthly/actions-setup@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: "Barretenberg Prover ASAN Check" + timeout-minutes: 720 # 12 hour timeout (proving) + run: earthly --no-cache ./barretenberg/cpp/+preset-check --preset=asan + + # address sanitzer for prover + bb-tsan-check: + if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'bb-tsan-check') + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + - uses: earthly/actions-setup@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: "Barretenberg Prover TSAN Check" + timeout-minutes: 720 # 12 hour timeout (proving) + run: earthly --no-cache ./barretenberg/cpp/+preset-check --preset=tsan + + # undefined behavior sanitzer for prover + bb-ubsan-check: + if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'bb-ubsan-check') + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + - uses: earthly/actions-setup@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: "Barretenberg Prover TSAN Check" + timeout-minutes: 720 # 12 hour timeout (proving) + run: earthly --no-cache ./barretenberg/cpp/+preset-check --preset=ubsan diff --git a/.github/workflows/publish-aztec-packages.yml b/.github/workflows/publish-aztec-packages.yml index 9bc5f3e88fd..1fb170f4f99 100644 --- a/.github/workflows/publish-aztec-packages.yml +++ b/.github/workflows/publish-aztec-packages.yml @@ -5,7 +5,6 @@ on: branches: - master - "*/release-master*" - - ludamad-patch-2 workflow_dispatch: inputs: tag: diff --git a/barretenberg/cpp/Earthfile b/barretenberg/cpp/Earthfile index 72b3fb7f218..390a7af6544 100644 --- a/barretenberg/cpp/Earthfile +++ b/barretenberg/cpp/Earthfile @@ -107,7 +107,15 @@ preset-msan-check: # install SRS needed for proving COPY --dir ./srs_db/+build/. srs_db RUN echo "Warning: If ./bin/client_ivc_tests is not found, there may be build failures above." - RUN cd build && ./bin/client_ivc_tests --gtest_also_run_disabled_tests + RUN cd build && ./bin/client_ivc_tests --gtest_filter="*BasicStructured" + +preset-check: + ARG preset + FROM +source + RUN cmake --preset $preset -Bbuild && cmake --build build --target client_ivc_tests + # install SRS needed for proving + COPY --dir ./srs_db/+build/. srs_db + RUN cd build && ./bin/client_ivc_tests --gtest_filter="*BasicStructured" preset-wasm: ARG TARGETARCH From 39cda86c3576c5cb94a7beb123b875a2ba37c26b Mon Sep 17 00:00:00 2001 From: ludamad Date: Wed, 30 Oct 2024 21:17:42 -0400 Subject: [PATCH 04/21] fix: force bb-sanitizers true (#9614) --- .github/workflows/bb-sanitizers.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/bb-sanitizers.yml b/.github/workflows/bb-sanitizers.yml index 84b2c61d767..1c85c3cb961 100644 --- a/.github/workflows/bb-sanitizers.yml +++ b/.github/workflows/bb-sanitizers.yml @@ -47,7 +47,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} - name: "Barretenberg Prover MSAN Check" - run: earthly --no-cache ./barretenberg/cpp/+preset-msan-check + run: earthly --no-cache ./barretenberg/cpp/+preset-msan-check || true # address sanitzer for prover bb-asan-check: @@ -79,7 +79,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} - name: "Barretenberg Prover TSAN Check" timeout-minutes: 720 # 12 hour timeout (proving) - run: earthly --no-cache ./barretenberg/cpp/+preset-check --preset=tsan + run: earthly --no-cache ./barretenberg/cpp/+preset-check --preset=tsan || true # undefined behavior sanitzer for prover bb-ubsan-check: From c834cc5c41ee2c5f4af47fbe7f982757025aa5b9 Mon Sep 17 00:00:00 2001 From: AztecBot Date: Thu, 31 Oct 2024 02:26:10 +0000 Subject: [PATCH 05/21] git subrepo push --branch=master barretenberg subrepo: subdir: "barretenberg" merged: "b7f1c6ad51" upstream: origin: "https://github.com/AztecProtocol/barretenberg" branch: "master" commit: "b7f1c6ad51" git-subrepo: version: "0.4.6" origin: "???" commit: "???" [skip ci] --- barretenberg/.gitrepo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/barretenberg/.gitrepo b/barretenberg/.gitrepo index 599eac197a0..d8d16b146b7 100644 --- a/barretenberg/.gitrepo +++ b/barretenberg/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/AztecProtocol/barretenberg branch = master - commit = 8b4fa8cbcb9e3bf94962569aa3f9857fe8452a7a - parent = 9d66c1abca1af9ddb0715627fad87c2efc612a1d + commit = b7f1c6ad51722d30e60f93035d675098d69da8eb + parent = 39cda86c3576c5cb94a7beb123b875a2ba37c26b method = merge cmdver = 0.4.6 From 288099b89dea0911adb04dd48af531c7a56daf21 Mon Sep 17 00:00:00 2001 From: AztecBot Date: Thu, 31 Oct 2024 02:26:43 +0000 Subject: [PATCH 06/21] chore: replace relative paths to noir-protocol-circuits --- noir-projects/aztec-nr/aztec/Nargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noir-projects/aztec-nr/aztec/Nargo.toml b/noir-projects/aztec-nr/aztec/Nargo.toml index 7a1f1af5863..97f3e91e438 100644 --- a/noir-projects/aztec-nr/aztec/Nargo.toml +++ b/noir-projects/aztec-nr/aztec/Nargo.toml @@ -5,4 +5,4 @@ compiler_version = ">=0.18.0" type = "lib" [dependencies] -protocol_types = { path = "../../noir-protocol-circuits/crates/types" } +protocol_types = { git="https://github.com/AztecProtocol/aztec-packages", tag="aztec-packages-v0.61.0", directory="noir-projects/noir-protocol-circuits/crates/types" } From c2050e6b05986bfab99152ba9d64f8c3f0a7bc22 Mon Sep 17 00:00:00 2001 From: AztecBot Date: Thu, 31 Oct 2024 02:26:43 +0000 Subject: [PATCH 07/21] git_subrepo.sh: Fix parent in .gitrepo file. [skip ci] --- noir-projects/aztec-nr/.gitrepo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noir-projects/aztec-nr/.gitrepo b/noir-projects/aztec-nr/.gitrepo index 98e5f67280c..618694bd158 100644 --- a/noir-projects/aztec-nr/.gitrepo +++ b/noir-projects/aztec-nr/.gitrepo @@ -9,4 +9,4 @@ remote = https://github.com/AztecProtocol/aztec-nr commit = cf243a618874253bffea679ad13507809b807e69 method = merge cmdver = 0.4.6 - parent = 7a33b40716ab9bef91c5f6a5c27cec32e4b461d1 + parent = cae0708a61b6ee114dc639cd4ae1fe91338c5d90 From 054fd6782a8c790332e440868db1c364618a2c69 Mon Sep 17 00:00:00 2001 From: AztecBot Date: Thu, 31 Oct 2024 02:26:46 +0000 Subject: [PATCH 08/21] git subrepo push --branch=master noir-projects/aztec-nr subrepo: subdir: "noir-projects/aztec-nr" merged: "45b57166e1" upstream: origin: "https://github.com/AztecProtocol/aztec-nr" branch: "master" commit: "45b57166e1" git-subrepo: version: "0.4.6" origin: "???" commit: "???" [skip ci] --- noir-projects/aztec-nr/.gitrepo | 4 ++-- noir-projects/aztec-nr/aztec/Nargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/noir-projects/aztec-nr/.gitrepo b/noir-projects/aztec-nr/.gitrepo index 618694bd158..da56b3ac6f9 100644 --- a/noir-projects/aztec-nr/.gitrepo +++ b/noir-projects/aztec-nr/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/AztecProtocol/aztec-nr branch = master - commit = cf243a618874253bffea679ad13507809b807e69 + commit = 45b57166e123cf56ca4ca070d4067e214e3d9d35 method = merge cmdver = 0.4.6 - parent = cae0708a61b6ee114dc639cd4ae1fe91338c5d90 + parent = d01165d719fa715d3f31d17ec637c19acdb92387 diff --git a/noir-projects/aztec-nr/aztec/Nargo.toml b/noir-projects/aztec-nr/aztec/Nargo.toml index 97f3e91e438..7a1f1af5863 100644 --- a/noir-projects/aztec-nr/aztec/Nargo.toml +++ b/noir-projects/aztec-nr/aztec/Nargo.toml @@ -5,4 +5,4 @@ compiler_version = ">=0.18.0" type = "lib" [dependencies] -protocol_types = { git="https://github.com/AztecProtocol/aztec-packages", tag="aztec-packages-v0.61.0", directory="noir-projects/noir-protocol-circuits/crates/types" } +protocol_types = { path = "../../noir-protocol-circuits/crates/types" } From e8fadc799d015046016b16eeadbb55be929d20c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bene=C5=A1?= Date: Wed, 30 Oct 2024 20:32:34 -0600 Subject: [PATCH 09/21] feat: token private mint optimization (#9606) Impl. of token `finalize_mint_to_private` --- .../contracts/nft_contract/src/main.nr | 4 +- .../token_bridge_contract/src/main.nr | 2 +- .../contracts/token_contract/src/main.nr | 74 ++++++++++++++++++- yarn-project/aztec/src/examples/token.ts | 12 +-- .../end-to-end/src/fixtures/token_utils.ts | 12 +-- .../writing_an_account_contract.test.ts | 10 +-- .../end-to-end/src/sample-dapp/index.mjs | 12 +-- yarn-project/end-to-end/src/shared/browser.ts | 5 +- .../src/protocol_contract_data.ts | 8 +- 9 files changed, 88 insertions(+), 51 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr index f058088abc7..b9b7b610e6d 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr @@ -256,7 +256,7 @@ contract NFT { fn _finalize_transfer_to_private( from: AztecAddress, token_id: Field, - note_transient_storage_slot: Field, + hiding_point_slot: Field, context: &mut PublicContext, storage: Storage<&mut PublicContext>, ) { @@ -268,7 +268,7 @@ contract NFT { // Finalize the partial note with the `token_id` let finalization_payload = - NFTNote::finalization_payload().new(context, note_transient_storage_slot, token_id); + NFTNote::finalization_payload().new(context, hiding_point_slot, token_id); // At last we emit the note hash and the final log finalization_payload.emit(); diff --git a/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr index 8443c54e0f1..4ef9953c938 100644 --- a/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr @@ -158,7 +158,7 @@ contract TokenBridge { #[public] #[internal] fn _call_mint_on_token(amount: Field, secret_hash: Field) { - Token::at(storage.token.read()).mint_private(amount, secret_hash).call(&mut context); + Token::at(storage.token.read()).mint_private_old(amount, secret_hash).call(&mut context); } // docs:end:call_mint_on_token diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index 2e764587bbc..7392f85b3a8 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -208,8 +208,9 @@ contract Token { } // docs:end:mint_public // docs:start:mint_private + // TODO(benesjan): To be nuked in a followup PR. #[public] - fn mint_private(amount: Field, secret_hash: Field) { + fn mint_private_old(amount: Field, secret_hash: Field) { assert(storage.minters.at(context.msg_sender()).read(), "caller is not minter"); let pending_shields = storage.pending_shields; let mut note = TransparentNote::new(amount, secret_hash); @@ -485,13 +486,15 @@ contract Token { // Transfers token `amount` from public balance of message sender to a private balance of `to`. #[private] fn transfer_to_private(to: AztecAddress, amount: Field) { + // We check the minter permissions in the enqueued call as that allows us to avoid the need for `SharedMutable` + // which is less efficient. let from = context.msg_sender(); let token = Token::at(context.this_address()); // We prepare the transfer. let hiding_point_slot = _prepare_transfer_to_private(to, &mut context, storage); - // At last we finalize the transfer. Usafe of the `unsafe` method here is safe because we set the `from` + // At last we finalize the transfer. Usage of the `unsafe` method here is safe because we set the `from` // function argument to a message sender, guaranteeing that he can transfer only his own tokens. token._finalize_transfer_to_private_unsafe(from, amount, hiding_point_slot).enqueue( &mut context, @@ -578,7 +581,7 @@ contract Token { fn _finalize_transfer_to_private( from: AztecAddress, amount: Field, - note_transient_storage_slot: Field, + hiding_point_slot: Field, context: &mut PublicContext, storage: Storage<&mut PublicContext>, ) { @@ -591,7 +594,70 @@ contract Token { // Then we finalize the partial note with the `amount` let finalization_payload = - UintNote::finalization_payload().new(context, note_transient_storage_slot, amount); + UintNote::finalization_payload().new(context, hiding_point_slot, amount); + + // At last we emit the note hash and the final log + finalization_payload.emit(); + } + + /// Mints token `amount` to a private balance of `to`. Message sender has to have minter permissions (checked + /// in the enqueud call). + #[private] + fn mint_to_private(to: AztecAddress, amount: Field) { + let from = context.msg_sender(); + let token = Token::at(context.this_address()); + + // We prepare the transfer. + let hiding_point_slot = _prepare_transfer_to_private(to, &mut context, storage); + + // At last we finalize the mint. Usage of the `unsafe` method here is safe because we set the `from` + // function argument to a message sender, guaranteeing that only a message sender with minter permissions + // can successfully execute the function. + token._finalize_mint_to_private_unsafe(from, amount, hiding_point_slot).enqueue(&mut context); + } + + /// Finalizes a mint of token `amount` to a private balance of `to`. The mint must be prepared by calling + /// `prepare_transfer_to_private` first and the resulting + /// `hiding_point_slot` must be passed as an argument to this function. + /// + /// Note: This function is only an optimization as it could be replaced by a combination of `mint_public` + /// and `finalize_transfer_to_private`. It is however used very commonly so it makes sense to optimize it + /// (e.g. used during token bridging, in AMM liquidity token etc.). + #[public] + fn finalize_mint_to_private(amount: Field, hiding_point_slot: Field) { + assert(storage.minters.at(context.msg_sender()).read(), "caller is not minter"); + + _finalize_mint_to_private(amount, hiding_point_slot, &mut context, storage); + } + + #[public] + #[internal] + fn _finalize_mint_to_private_unsafe( + from: AztecAddress, + amount: Field, + hiding_point_slot: Field, + ) { + // We check the minter permissions as it was not done in `mint_to_private` function. + assert(storage.minters.at(from).read(), "caller is not minter"); + _finalize_mint_to_private(amount, hiding_point_slot, &mut context, storage); + } + + #[contract_library_method] + fn _finalize_mint_to_private( + amount: Field, + hiding_point_slot: Field, + context: &mut PublicContext, + storage: Storage<&mut PublicContext>, + ) { + let amount = U128::from_integer(amount); + + // First we increase the total supply by the `amount` + let supply = storage.total_supply.read().add(amount); + storage.total_supply.write(supply); + + // Then we finalize the partial note with the `amount` + let finalization_payload = + UintNote::finalization_payload().new(context, hiding_point_slot, amount); // At last we emit the note hash and the final log finalization_payload.emit(); diff --git a/yarn-project/aztec/src/examples/token.ts b/yarn-project/aztec/src/examples/token.ts index 488377f79d7..754640d49e2 100644 --- a/yarn-project/aztec/src/examples/token.ts +++ b/yarn-project/aztec/src/examples/token.ts @@ -1,5 +1,5 @@ import { getSingleKeyAccount } from '@aztec/accounts/single_key'; -import { type AccountWallet, BatchCall, Fr, createPXEClient } from '@aztec/aztec.js'; +import { type AccountWallet, Fr, createPXEClient } from '@aztec/aztec.js'; import { createDebugLogger } from '@aztec/foundation/log'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; @@ -41,15 +41,7 @@ async function main() { // Mint tokens to Alice logger.info(`Minting ${ALICE_MINT_BALANCE} more coins to Alice...`); - - // We don't have the functionality to mint to private so we mint to the Alice's address in public and transfer - // the tokens to private. We use BatchCall to speed the process up. - await new BatchCall(aliceWallet, [ - token.methods.mint_public(aliceWallet.getAddress(), ALICE_MINT_BALANCE).request(), - token.methods.transfer_to_private(aliceWallet.getAddress(), ALICE_MINT_BALANCE).request(), - ]) - .send() - .wait(); + await tokenAlice.methods.mint_to_private(aliceWallet.getAddress(), ALICE_MINT_BALANCE).send().wait(); logger.info(`${ALICE_MINT_BALANCE} tokens were successfully minted by Alice and transferred to private`); diff --git a/yarn-project/end-to-end/src/fixtures/token_utils.ts b/yarn-project/end-to-end/src/fixtures/token_utils.ts index a0c570648a2..d557bbea2ff 100644 --- a/yarn-project/end-to-end/src/fixtures/token_utils.ts +++ b/yarn-project/end-to-end/src/fixtures/token_utils.ts @@ -1,4 +1,4 @@ -import { type AztecAddress, BatchCall, type DebugLogger, type Wallet, retryUntil } from '@aztec/aztec.js'; +import { type AztecAddress, type DebugLogger, type Wallet, retryUntil } from '@aztec/aztec.js'; import { TokenContract } from '@aztec/noir-contracts.js'; export async function deployToken(adminWallet: Wallet, initialAdminBalance: bigint, logger: DebugLogger) { @@ -23,14 +23,8 @@ export async function mintTokensToPrivate( recipient: AztecAddress, amount: bigint, ) { - // We don't have the functionality to mint to private so we mint to the minter address in public and transfer - // the tokens to the recipient in private. We use BatchCall to speed the process up. - await new BatchCall(minterWallet, [ - token.methods.mint_public(minterWallet.getAddress(), amount).request(), - token.methods.transfer_to_private(recipient, amount).request(), - ]) - .send() - .wait(); + const tokenAsMinter = await TokenContract.at(token.address, minterWallet); + await tokenAsMinter.methods.mint_to_private(recipient, amount).send().wait(); } const awaitUserSynchronized = async (wallet: Wallet, owner: AztecAddress) => { diff --git a/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts b/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts index a73e8fbd0b8..e78d1973d38 100644 --- a/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts +++ b/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts @@ -3,7 +3,6 @@ import { AccountManager, AuthWitness, type AuthWitnessProvider, - BatchCall, type CompleteAddress, Fr, GrumpkinScalar, @@ -64,15 +63,8 @@ describe('guides/writing_an_account_contract', () => { const token = await TokenContract.deploy(wallet, address, 'TokenName', 'TokenSymbol', 18).send().deployed(); logger.info(`Deployed token contract at ${token.address}`); - // We don't have the functionality to mint to private so we mint to the minter address in public and transfer - // the tokens to the recipient in private. We use BatchCall to speed the process up. const mintAmount = 50n; - await new BatchCall(wallet, [ - token.methods.mint_public(address, mintAmount).request(), - token.methods.transfer_to_private(address, mintAmount).request(), - ]) - .send() - .wait(); + await token.methods.mint_to_private(address, mintAmount).send().wait(); const balance = await token.methods.balance_of_private(address).simulate(); logger.info(`Balance of wallet is now ${balance}`); diff --git a/yarn-project/end-to-end/src/sample-dapp/index.mjs b/yarn-project/end-to-end/src/sample-dapp/index.mjs index 5b0d6822a4c..62f43ba68d0 100644 --- a/yarn-project/end-to-end/src/sample-dapp/index.mjs +++ b/yarn-project/end-to-end/src/sample-dapp/index.mjs @@ -1,6 +1,6 @@ // docs:start:imports import { getInitialTestAccountsWallets } from '@aztec/accounts/testing'; -import { BatchCall, createPXEClient, waitForPXE } from '@aztec/aztec.js'; +import { createPXEClient, waitForPXE } from '@aztec/aztec.js'; import { fileURLToPath } from '@aztec/foundation/url'; import { getToken } from './contracts.mjs'; @@ -38,15 +38,9 @@ async function mintPrivateFunds(pxe) { await showPrivateBalances(pxe); + // We mint tokens to the owner const mintAmount = 20n; - // We don't have the functionality to mint to private so we mint to the owner address in public and transfer - // the tokens to the recipient in private. We use BatchCall to speed the process up. - await new BatchCall(ownerWallet, [ - token.methods.mint_public(ownerWallet.getAddress(), mintAmount).request(), - token.methods.transfer_to_private(ownerWallet.getAddress(), mintAmount).request(), - ]) - .send() - .wait(); + await token.methods.mint_to_private(ownerWallet.getAddress(), mintAmount).send().wait(); await showPrivateBalances(pxe); } diff --git a/yarn-project/end-to-end/src/shared/browser.ts b/yarn-project/end-to-end/src/shared/browser.ts index d0d7363ca85..75e1019196e 100644 --- a/yarn-project/end-to-end/src/shared/browser.ts +++ b/yarn-project/end-to-end/src/shared/browser.ts @@ -251,9 +251,8 @@ export const browserTestSuite = ( console.log(`Contract Deployed: ${token.address}`); - // We don't use the `mintTokensToPrivate` util as it is not available here - await token.methods.mint_public(owner.getAddress(), initialBalance).send().wait(); - await token.methods.transfer_to_private(owner.getAddress(), initialBalance).send().wait(); + // We mint tokens to the owner + await token.methods.mint_to_private(owner.getAddress(), initialBalance).send().wait(); return [txHash.toString(), token.address.toString()]; }, diff --git a/yarn-project/protocol-contracts/src/protocol_contract_data.ts b/yarn-project/protocol-contracts/src/protocol_contract_data.ts index 12ee3e96237..d7f5390acb2 100644 --- a/yarn-project/protocol-contracts/src/protocol_contract_data.ts +++ b/yarn-project/protocol-contracts/src/protocol_contract_data.ts @@ -50,14 +50,14 @@ export const ProtocolContractAddress: Record }; export const ProtocolContractLeaf = { - AuthRegistry: Fr.fromString('0x16f00633c07cb18f29a819d16a8b5140dea24787ca2a030dc54379a8e6896e3e'), + AuthRegistry: Fr.fromString('0x2c8f0ae77bbed1244767430f7bf1badf98219c40a1dfc1bba1409caf1a9a01cd'), ContractInstanceDeployer: Fr.fromString('0x04a661c9d4d295fc485a7e0f3de40c09b35366343bce8ad229106a8ef4076fe5'), ContractClassRegisterer: Fr.fromString('0x147ba3294403576dbad10f86d3ffd4eb83fb230ffbcd5c8b153dd02942d0611f'), MultiCallEntrypoint: Fr.fromString('0x154b701b41d6cf6da7204fef36b2ee9578b449d21b3792a9287bf45eba48fd26'), - FeeJuice: Fr.fromString('0x2422b0101aba5f5050e8c086a6bdd62b185b188acfba58c77b0016769b96efd6'), - Router: Fr.fromString('0x1cedd0ce59239cb4d55408257d8942bdbd1ac6f0ddab9980157255391dbaa596'), + FeeJuice: Fr.fromString('0x14c807b45f74c73f4b7c7aa85162fc1ae78fafa81ece6c22426fec928edd9471'), + Router: Fr.fromString('0x2cf76c7258a26c77ec56a5b4903996b1e0a21313f78e166f408e955393144665'), }; export const protocolContractTreeRoot = Fr.fromString( - '0x21fb5ab0a0a9b4f282d47d379ec7b5fdf59457a19a593c17f7c29954e1e88dec', + '0x27d6a49f48a8f07db1170672df19cec71fcb71d97588503150628483943e2a14', ); From 247e9eb28e931874e98781addebe9a343ba7afe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bene=C5=A1?= Date: Wed, 30 Oct 2024 20:33:47 -0600 Subject: [PATCH 10/21] refactor: replacing unshield naming with transfer_to_public (#9608) Just renaming unshield as transfer_to_public everywhere. --- cspell.json | 3 - .../how_to_compile_contract.md | 2 +- .../writing_contracts/authwit.md | 4 +- .../common_patterns/index.md | 2 +- .../gas-and-fees/tx-setup-and-teardown.md | 2 +- .../contract_tutorials/token_contract.md | 17 +-- .../tutorials/examples/uniswap/l2_contract.md | 6 +- .../contracts/fpc_contract/src/main.nr | 6 +- .../contracts/lending_contract/src/main.nr | 2 +- .../contracts/token_contract/src/main.nr | 6 +- .../contracts/token_contract/src/test.nr | 2 +- .../src/test/transfer_to_public.nr | 139 ++++++++++++++++++ .../token_contract/src/test/unshielding.nr | 115 --------------- .../contracts/uniswap_contract/src/main.nr | 8 +- .../src/fee/private_fee_payment_method.ts | 4 +- .../unshielding.test.ts | 4 +- .../token_bridge_public_to_private.test.ts | 4 +- .../src/e2e_fees/account_init.test.ts | 2 +- .../src/e2e_fees/dapp_subscription.test.ts | 2 +- .../src/e2e_lending_contract.test.ts | 14 +- ...ing.test.ts => transfer_to_public.test.ts} | 24 +-- .../src/shared/cross_chain_test_harness.ts | 6 +- .../end-to-end/src/shared/uniswap_l1_l2.ts | 49 +++--- .../src/simulators/lending_simulator.ts | 2 +- .../src/simulators/token_simulator.ts | 2 +- 25 files changed, 231 insertions(+), 196 deletions(-) create mode 100644 noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_to_public.nr delete mode 100644 noir-projects/noir-contracts/contracts/token_contract/src/test/unshielding.nr rename yarn-project/end-to-end/src/e2e_token_contract/{unshielding.test.ts => transfer_to_public.test.ts} (81%) diff --git a/cspell.json b/cspell.json index dd5586ddbc8..42e5efb8715 100644 --- a/cspell.json +++ b/cspell.json @@ -270,9 +270,6 @@ "unexcluded", "unfinalised", "unprefixed", - "unshield", - "unshielding", - "unshields", "unzipit", "updateable", "upperfirst", diff --git a/docs/docs/guides/developer_guides/smart_contracts/how_to_compile_contract.md b/docs/docs/guides/developer_guides/smart_contracts/how_to_compile_contract.md index 414fc543bf4..c356ddb1544 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/how_to_compile_contract.md +++ b/docs/docs/guides/developer_guides/smart_contracts/how_to_compile_contract.md @@ -246,7 +246,7 @@ contract FPC { #[private] fn fee_entrypoint_private(amount: Field, asset: AztecAddress, secret_hash: Field, nonce: Field) { assert(asset == storage.other_asset.read_private()); - Token::at(asset).unshield(context.msg_sender(), context.this_address(), amount, nonce).call(&mut context); + Token::at(asset).transfer_to_public(context.msg_sender(), context.this_address(), amount, nonce).call(&mut context); FPC::at(context.this_address()).pay_fee_with_shielded_rebate(amount, asset, secret_hash).enqueue(&mut context); } diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/authwit.md b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/authwit.md index 71c085f3ca6..dce64ae625e 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/authwit.md +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/authwit.md @@ -175,7 +175,7 @@ In the snippet below, this is done as a separate contract call, but can also be We have cases where we need a non-wallet contract to approve an action to be executed by another contract. One of the cases could be when making more complex defi where funds are passed along. When doing so, we need the intermediate contracts to support approving of actions on their behalf. -This is fairly straight forward to do using the `auth` library which includes logic for updating values in the public auth registry. Namely, you can prepare the `message_hash` using `compute_authwit_message_hash_from_call` and then simply feed it into the `set_authorized` function (both are in `auth` library) to update the value. +This is fairly straight forward to do using the `auth` library which includes logic for updating values in the public auth registry. Namely, you can prepare the `message_hash` using `compute_authwit_message_hash_from_call` and then simply feed it into the `set_authorized` function (both are in `auth` library) to update the value. When another contract later is consuming the authwit using `assert_current_call_valid_authwit_public` it will be calling the registry, and spend that authwit. @@ -197,7 +197,7 @@ sequenceDiagram activate AC; AC->>CC: Swap 1000 token A to B activate CC; - CC->>T: unshield 1000 tokens from Alice Account to CCS + CC->>T: Transfer to public 1000 tokens from Alice Account to CCS activate T; T->>AC: Have you approved this?? AC-->>A: Please give me an AuthWit diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/common_patterns/index.md b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/common_patterns/index.md index e4a9a3e5eb7..848fffa0e1f 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/common_patterns/index.md +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/common_patterns/index.md @@ -131,7 +131,7 @@ To send a note to someone, they need to have a key which we can encrypt the note There are several patterns here: 1. Give the contract a key and share it amongst all participants. This leaks privacy, as anyone can see all the notes in the contract. -2. `Unshield` funds into the contract - this is used in the [Uniswap smart contract example where a user sends private funds into a Uniswap Portal contract which eventually withdraws to L1 to swap on L1 Uniswap](../../../../../tutorials/examples/uniswap/index.md). This works like Ethereum - to achieve contract composability, you move funds into the public domain. This way the contract doesn't even need keys. +2. `transfer_to_public` funds into the contract - this is used in the [Uniswap smart contract example where a user sends private funds into a Uniswap Portal contract which eventually withdraws to L1 to swap on L1 Uniswap](../../../../../tutorials/examples/uniswap/index.md). This works like Ethereum - to achieve contract composability, you move funds into the public domain. This way the contract doesn't even need keys. There are several other designs we are discussing through [in this discourse post](https://discourse.aztec.network/t/how-to-handle-private-escrows-between-two-parties/2440) but they need some changes in the protocol or in our demo contract. If you are interested in this discussion, please participate in the discourse post! diff --git a/docs/docs/protocol-specs/gas-and-fees/tx-setup-and-teardown.md b/docs/docs/protocol-specs/gas-and-fees/tx-setup-and-teardown.md index 6ea9057ecbb..b5b4783462c 100644 --- a/docs/docs/protocol-specs/gas-and-fees/tx-setup-and-teardown.md +++ b/docs/docs/protocol-specs/gas-and-fees/tx-setup-and-teardown.md @@ -37,7 +37,7 @@ Consider a user, Alice, who does not have FPA but wishes to interact with the ne Suppose there is a Fee Payment Contract (FPC) that has been deployed by another user to the network. Alice can structure her transaction as follows: -0. Before the transaction, Alice creates a private authwit in her wallet, allowing the FPC to unshield a specified amount of BananaCoin from Alice's private balance to the FPC's public balance. +0. Before the transaction, Alice creates a private authwit in her wallet, allowing the FPC to transfer to public a specified amount of BananaCoin from Alice's private balance to the FPC's public balance. 1. Private setup: - Alice calls a private function on the FPC which is exposed for public fee payment in BananaCoin. - The FPC checks that the amount of teardown gas Alice has allocated is sufficient to cover the gas associated with the teardown function it will use to provide a refund to Alice. diff --git a/docs/docs/tutorials/codealong/contract_tutorials/token_contract.md b/docs/docs/tutorials/codealong/contract_tutorials/token_contract.md index 9638102a0d6..efb6fda0342 100644 --- a/docs/docs/tutorials/codealong/contract_tutorials/token_contract.md +++ b/docs/docs/tutorials/codealong/contract_tutorials/token_contract.md @@ -77,9 +77,9 @@ These are functions that have transparent logic, will execute in a publicly veri These are functions that have private logic and will be executed on user devices to maintain privacy. The only data that is submitted to the network is a proof of correct execution, new data commitments and nullifiers, so users will not reveal which contract they are interacting with or which function they are executing. The only information that will be revealed publicly is that someone executed a private transaction on Aztec. - `redeem_shield` enables accounts to claim tokens that have been made private via `mint_private` or `shield` by providing the secret -- `unshield` enables an account to send tokens from their private balance to any other account's public balance +- `transfer_to_public` enables an account to send tokens from their private balance to any other account's public balance - `transfer` enables an account to send tokens from their private balance to another account's private balance -- `transferFrom` enables an account to send tokens from another account's private balance to another account's private balance +- `transfer_from` enables an account to send tokens from another account's private balance to another account's private balance - `cancel_authwit` enables an account to cancel an authorization to spend tokens - `burn` enables tokens to be burned privately @@ -87,7 +87,7 @@ These are functions that have private logic and will be executed on user devices Internal functions are functions that can only be called by the contract itself. These can be used when the contract needs to call one of it's public functions from one of it's private functions. -- `_increase_public_balance` increases the public balance of an account when `unshield` is called +- `_increase_public_balance` increases the public balance of an account when `transfer_to_public` is called - `_reduce_total_supply` reduces the total supply of tokens when a token is privately burned To clarify, let's review some details of the Aztec transaction lifecycle, particularly how a transaction "moves through" these contexts. @@ -133,7 +133,6 @@ We are importing: - `CompressedString` to hold the token symbol - Types from `aztec::prelude` -- `compute_secret_hash` that will help with the shielding and unshielding, allowing someone to claim a token from private to public - Types for storing note types ### Types files @@ -206,7 +205,7 @@ First, storage is initialized. Then the function checks that the `msg_sender` is This public function allows an account approved in the public `minters` mapping to create new private tokens that can be claimed by anyone that has the pre-image to the `secret_hash`. -First, public storage is initialized. Then it checks that the `msg_sender` is an approved minter. Then a new `TransparentNote` is created with the specified `amount` and `secret_hash`. You can read the details of the `TransparentNote` in the `types.nr` file [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/noir-contracts/contracts/token_contract/src/types.nr#L61). The `amount` is added to the existing public `total_supply` and the storage value is updated. Then the new `TransparentNote` is added to the `pending_shields` using the `insert_from_public` function, which is accessible on the `PrivateSet` type. Then it's ready to be claimed by anyone with the `secret_hash` pre-image using the `redeem_shield` function. +First, public storage is initialized. Then it checks that the `msg_sender` is an approved minter. Then a new `TransparentNote` is created with the specified `amount` and `secret_hash`. You can read the details of the `TransparentNote` in the `types.nr` file [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/noir-contracts/contracts/token_contract/src/types.nr#L61). The `amount` is added to the existing public `total_supply` and the storage value is updated. Then the new `TransparentNote` is added to the `pending_shields` using the `insert_from_public` function, which is accessible on the `PrivateSet` type. Then it's ready to be claimed by anyone with the `secret_hash` pre-image using the `redeem_shield` function. #include_code mint_private /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust @@ -251,7 +250,7 @@ Private functions are declared with the `#[private]` macro above the function na fn redeem_shield( ``` -As described in the [execution contexts section above](#execution-contexts), private function logic and transaction information is hidden from the world and is executed on user devices. Private functions update private state, but can pass data to the public execution context (e.g. see the [`unshield`](#unshield) function). +As described in the [execution contexts section above](#execution-contexts), private function logic and transaction information is hidden from the world and is executed on user devices. Private functions update private state, but can pass data to the public execution context (e.g. see the [`transfer_to_public`](#transfer_to_public) function). Storage is referenced as `storage.variable`. @@ -265,7 +264,7 @@ The function returns `1` to indicate successful execution. #include_code redeem_shield /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust -#### `unshield` +#### `transfer_to_public` This private function enables un-shielding of private `UintNote`s stored in `balances` to any Aztec account's `public_balance`. @@ -273,7 +272,7 @@ After initializing storage, the function checks that the `msg_sender` is authori The function returns `1` to indicate successful execution. -#include_code unshield /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust +#include_code transfer_to_public /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust #### `transfer` @@ -303,7 +302,7 @@ Internal functions are functions that can only be called by this contract. The f #### `_increase_public_balance` -This function is called from [`unshield`](#unshield). The account's private balance is decremented in `shield` and the public balance is increased in this function. +This function is called from [`transfer_to_public`](#transfer_to_public). The account's private balance is decremented in `shield` and the public balance is increased in this function. #include_code increase_public_balance /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust diff --git a/docs/docs/tutorials/examples/uniswap/l2_contract.md b/docs/docs/tutorials/examples/uniswap/l2_contract.md index da656ec2b2c..b8f9101e7a1 100644 --- a/docs/docs/tutorials/examples/uniswap/l2_contract.md +++ b/docs/docs/tutorials/examples/uniswap/l2_contract.md @@ -3,7 +3,7 @@ title: L2 Contracts (Aztec) sidebar_position: 1 --- -This page goes over the code in the L2 contract for Uniswap, which works alongside a [token bridge (codealong tutorial)](../../codealong/contract_tutorials/advanced/token_bridge/index.md). +This page goes over the code in the L2 contract for Uniswap, which works alongside a [token bridge (codealong tutorial)](../../codealong/contract_tutorials/advanced/token_bridge/index.md). ## Main.nr @@ -47,8 +47,8 @@ This uses a util function `compute_swap_private_content_hash()` - find that [her This flow works similarly to the public flow with a few notable changes: - Notice how in the `swap_private()`, user has to pass in `token` address which they didn't in the public flow? Since `swap_private()` is a private method, it can't read what token is publicly stored on the token bridge, so instead the user passes a token address, and `_assert_token_is_same()` checks that this user provided address is same as the one in storage. Note that because public functions are executed by the sequencer while private methods are executed locally, all public calls are always done after all private calls are done. So first the burn would happen and only later the sequencer asserts that the token is same. Note that the sequencer just sees a request to `execute_assert_token_is_same` and therefore has no context on what the appropriate private method was. If the assertion fails, then the kernel circuit will fail to create a proof and hence the transaction will be dropped. -- In the public flow, the user calls `transfer_public()`. Here instead, the user calls `unshield()`. Why? The user can't directly transfer their private tokens (their notes) to the uniswap contract, because later the Uniswap contract has to approve the bridge to burn these notes and withdraw to L1. The authwit flow for the private domain requires a signature from the `sender`, which in this case would be the Uniswap contract. For the contract to sign, it would need a private key associated to it. But who would operate this key? -- To work around this, the user can unshield their private tokens into Uniswap L2 contract. Unshielding would convert user's private notes to public balance. It is a private method on the token contract that reduces a user’s private balance and then calls a public method to increase the recipient’s (ie Uniswap) public balance. **Remember that first all private methods are executed and then later all public methods will be - so the Uniswap contract won’t have the funds until public execution begins.** +- In the public flow, the user calls `transfer_public()`. Here instead, the user calls `transfer_to_public()`. Why? The user can't directly transfer their private tokens (their notes) to the uniswap contract, because later the Uniswap contract has to approve the bridge to burn these notes and withdraw to L1. The authwit flow for the private domain requires a signature from the `sender`, which in this case would be the Uniswap contract. For the contract to sign, it would need a private key associated to it. But who would operate this key? +- To work around this, the user can transfer to public their private tokens into Uniswap L2 contract. Transferring to public would convert user's private notes to public balance. It is a private method on the token contract that reduces a user’s private balance and then calls a public method to increase the recipient’s (ie Uniswap) public balance. **Remember that first all private methods are executed and then later all public methods will be - so the Uniswap contract won’t have the funds until public execution begins.** - Now uniswap has public balance (like with the public flow). Hence, `swap_private()` calls the internal public method which approves the input token bridge to burn Uniswap’s tokens and calls `exit_to_l1_public` to create an L2 → L1 message to exit to L1. - Constructing the message content for swapping works exactly as the public flow except instead of specifying who would be the Aztec address that receives the swapped funds, we specify a secret hash (`secret_hash_for_redeeming_minted_notes`). Only those who know the preimage to the secret can later redeem the minted notes to themselves. diff --git a/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr b/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr index 708f24fe0b8..05118734198 100644 --- a/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr @@ -31,9 +31,9 @@ contract FPC { nonce: Field, ) { assert(asset == storage.other_asset.read_private()); - Token::at(asset).unshield(context.msg_sender(), context.this_address(), amount, nonce).call( - &mut context, - ); + Token::at(asset) + .transfer_to_public(context.msg_sender(), context.this_address(), amount, nonce) + .call(&mut context); context.set_as_fee_payer(); // Would like to get back to // FPC::at(context.this_address()).pay_refund_with_shielded_rebate(amount, asset, secret_hash).set_public_teardown_function(&mut context); diff --git a/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr b/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr index 077be6fea40..938d403c803 100644 --- a/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr @@ -112,7 +112,7 @@ contract Lending { let on_behalf_of = compute_identifier(secret, on_behalf_of, context.msg_sender().to_field()); let _res = Token::at(collateral_asset) - .unshield(from, context.this_address(), amount, nonce) + .transfer_to_public(from, context.this_address(), amount, nonce) .call(&mut context); // docs:start:enqueue_public Lending::at(context.this_address()) diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index 7392f85b3a8..cca2c1111dc 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -314,9 +314,9 @@ contract Token { )); } // docs:end:redeem_shield - // docs:start:unshield + // docs:start:transfer_to_public #[private] - fn unshield(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { + fn transfer_to_public(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { assert_current_call_valid_authwit(&mut context, from); } else { @@ -331,7 +331,7 @@ contract Token { ); Token::at(context.this_address())._increase_public_balance(to, amount).enqueue(&mut context); } - // docs:end:unshield + // docs:end:transfer_to_public // docs:start:transfer #[private] fn transfer(to: AztecAddress, amount: Field) { diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr index a02821b3d5b..35a4c1f1aff 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr @@ -4,8 +4,8 @@ mod utils; mod transfer_public; mod transfer_private; mod transfer_to_private; +mod transfer_to_public; mod refunds; -mod unshielding; mod minting; mod reading_constants; mod shielding; diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_to_public.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_to_public.nr new file mode 100644 index 00000000000..63f84481c74 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_to_public.nr @@ -0,0 +1,139 @@ +use crate::test::utils; +use crate::Token; +use dep::authwit::cheatcodes as authwit_cheatcodes; +use dep::aztec::oracle::random::random; + +#[test] +unconstrained fn transfer_to_public_on_behalf_of_self() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, _, mint_amount) = + utils::setup_and_mint_private(/* with_account_contracts */ false); + + let transfer_to_public_amount = mint_amount / 10; + Token::at(token_contract_address) + .transfer_to_public(owner, owner, transfer_to_public_amount, 0) + .call(&mut env.private()); + utils::check_private_balance( + token_contract_address, + owner, + mint_amount - transfer_to_public_amount, + ); + utils::check_public_balance(token_contract_address, owner, transfer_to_public_amount); +} + +#[test] +unconstrained fn transfer_to_public_on_behalf_of_other() { + let (env, token_contract_address, owner, recipient, mint_amount) = + utils::setup_and_mint_private(/* with_account_contracts */ true); + + let transfer_to_public_amount = mint_amount / 10; + let transfer_to_public_call_interface = Token::at(token_contract_address).transfer_to_public( + owner, + recipient, + transfer_to_public_amount, + 0, + ); + authwit_cheatcodes::add_private_authwit_from_call_interface( + owner, + recipient, + transfer_to_public_call_interface, + ); + // Impersonate recipient + env.impersonate(recipient); + // transfer_to_public tokens + transfer_to_public_call_interface.call(&mut env.private()); + utils::check_private_balance( + token_contract_address, + owner, + mint_amount - transfer_to_public_amount, + ); + utils::check_public_balance(token_contract_address, recipient, transfer_to_public_amount); +} + +#[test(should_fail_with = "Balance too low")] +unconstrained fn transfer_to_public_failure_more_than_balance() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, _, mint_amount) = + utils::setup_and_mint_private(/* with_account_contracts */ false); + + let transfer_to_public_amount = mint_amount + 1; + Token::at(token_contract_address) + .transfer_to_public(owner, owner, transfer_to_public_amount, 0) + .call(&mut env.private()); +} + +#[test(should_fail_with = "invalid nonce")] +unconstrained fn transfer_to_public_failure_on_behalf_of_self_non_zero_nonce() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, _, mint_amount) = + utils::setup_and_mint_private(/* with_account_contracts */ false); + + let transfer_to_public_amount = mint_amount + 1; + Token::at(token_contract_address) + .transfer_to_public(owner, owner, transfer_to_public_amount, random()) + .call(&mut env.private()); +} + +#[test(should_fail_with = "Balance too low")] +unconstrained fn transfer_to_public_failure_on_behalf_of_other_more_than_balance() { + let (env, token_contract_address, owner, recipient, mint_amount) = + utils::setup_and_mint_private(/* with_account_contracts */ true); + + let transfer_to_public_amount = mint_amount + 1; + let transfer_to_public_call_interface = Token::at(token_contract_address).transfer_to_public( + owner, + recipient, + transfer_to_public_amount, + 0, + ); + authwit_cheatcodes::add_private_authwit_from_call_interface( + owner, + recipient, + transfer_to_public_call_interface, + ); + // Impersonate recipient + env.impersonate(recipient); + // transfer_to_public tokens + transfer_to_public_call_interface.call(&mut env.private()); +} + +#[test(should_fail_with = "Authorization not found for message hash")] +unconstrained fn transfer_to_public_failure_on_behalf_of_other_invalid_designated_caller() { + let (env, token_contract_address, owner, recipient, mint_amount) = + utils::setup_and_mint_private(/* with_account_contracts */ true); + + let transfer_to_public_amount = mint_amount + 1; + let transfer_to_public_call_interface = Token::at(token_contract_address).transfer_to_public( + owner, + recipient, + transfer_to_public_amount, + 0, + ); + authwit_cheatcodes::add_private_authwit_from_call_interface( + owner, + owner, + transfer_to_public_call_interface, + ); + // Impersonate recipient + env.impersonate(recipient); + // transfer_to_public tokens + transfer_to_public_call_interface.call(&mut env.private()); +} + +#[test(should_fail_with = "Authorization not found for message hash")] +unconstrained fn transfer_to_public_failure_on_behalf_of_other_no_approval() { + let (env, token_contract_address, owner, recipient, mint_amount) = + utils::setup_and_mint_private(/* with_account_contracts */ true); + + let transfer_to_public_amount = mint_amount + 1; + let transfer_to_public_call_interface = Token::at(token_contract_address).transfer_to_public( + owner, + recipient, + transfer_to_public_amount, + 0, + ); + // Impersonate recipient + env.impersonate(recipient); + // transfer_to_public tokens + transfer_to_public_call_interface.call(&mut env.private()); +} diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/unshielding.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/unshielding.nr deleted file mode 100644 index 9d7f4f23c1c..00000000000 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/unshielding.nr +++ /dev/null @@ -1,115 +0,0 @@ -use crate::test::utils; -use crate::Token; -use dep::authwit::cheatcodes as authwit_cheatcodes; -use dep::aztec::oracle::random::random; - -#[test] -unconstrained fn unshield_on_behalf_of_self() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, _, mint_amount) = - utils::setup_and_mint_private(/* with_account_contracts */ false); - - let unshield_amount = mint_amount / 10; - Token::at(token_contract_address).unshield(owner, owner, unshield_amount, 0).call( - &mut env.private(), - ); - utils::check_private_balance(token_contract_address, owner, mint_amount - unshield_amount); - utils::check_public_balance(token_contract_address, owner, unshield_amount); -} - -#[test] -unconstrained fn unshield_on_behalf_of_other() { - let (env, token_contract_address, owner, recipient, mint_amount) = - utils::setup_and_mint_private(/* with_account_contracts */ true); - - let unshield_amount = mint_amount / 10; - let unshield_call_interface = - Token::at(token_contract_address).unshield(owner, recipient, unshield_amount, 0); - authwit_cheatcodes::add_private_authwit_from_call_interface( - owner, - recipient, - unshield_call_interface, - ); - // Impersonate recipient - env.impersonate(recipient); - // Unshield tokens - unshield_call_interface.call(&mut env.private()); - utils::check_private_balance(token_contract_address, owner, mint_amount - unshield_amount); - utils::check_public_balance(token_contract_address, recipient, unshield_amount); -} - -#[test(should_fail_with = "Balance too low")] -unconstrained fn unshield_failure_more_than_balance() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, _, mint_amount) = - utils::setup_and_mint_private(/* with_account_contracts */ false); - - let unshield_amount = mint_amount + 1; - Token::at(token_contract_address).unshield(owner, owner, unshield_amount, 0).call( - &mut env.private(), - ); -} - -#[test(should_fail_with = "invalid nonce")] -unconstrained fn unshield_failure_on_behalf_of_self_non_zero_nonce() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, _, mint_amount) = - utils::setup_and_mint_private(/* with_account_contracts */ false); - - let unshield_amount = mint_amount + 1; - Token::at(token_contract_address).unshield(owner, owner, unshield_amount, random()).call( - &mut env.private(), - ); -} - -#[test(should_fail_with = "Balance too low")] -unconstrained fn unshield_failure_on_behalf_of_other_more_than_balance() { - let (env, token_contract_address, owner, recipient, mint_amount) = - utils::setup_and_mint_private(/* with_account_contracts */ true); - - let unshield_amount = mint_amount + 1; - let unshield_call_interface = - Token::at(token_contract_address).unshield(owner, recipient, unshield_amount, 0); - authwit_cheatcodes::add_private_authwit_from_call_interface( - owner, - recipient, - unshield_call_interface, - ); - // Impersonate recipient - env.impersonate(recipient); - // Unshield tokens - unshield_call_interface.call(&mut env.private()); -} - -#[test(should_fail_with = "Authorization not found for message hash")] -unconstrained fn unshield_failure_on_behalf_of_other_invalid_designated_caller() { - let (env, token_contract_address, owner, recipient, mint_amount) = - utils::setup_and_mint_private(/* with_account_contracts */ true); - - let unshield_amount = mint_amount + 1; - let unshield_call_interface = - Token::at(token_contract_address).unshield(owner, recipient, unshield_amount, 0); - authwit_cheatcodes::add_private_authwit_from_call_interface( - owner, - owner, - unshield_call_interface, - ); - // Impersonate recipient - env.impersonate(recipient); - // Unshield tokens - unshield_call_interface.call(&mut env.private()); -} - -#[test(should_fail_with = "Authorization not found for message hash")] -unconstrained fn unshield_failure_on_behalf_of_other_no_approval() { - let (env, token_contract_address, owner, recipient, mint_amount) = - utils::setup_and_mint_private(/* with_account_contracts */ true); - - let unshield_amount = mint_amount + 1; - let unshield_call_interface = - Token::at(token_contract_address).unshield(owner, recipient, unshield_amount, 0); - // Impersonate recipient - env.impersonate(recipient); - // Unshield tokens - unshield_call_interface.call(&mut env.private()); -} diff --git a/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr b/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr index f1625dc6119..a9219a45144 100644 --- a/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr @@ -115,8 +115,8 @@ contract Uniswap { input_asset_bridge: AztecAddress, input_amount: Field, output_asset_bridge: AztecAddress, - // params for using the unshield approval - nonce_for_unshield_approval: Field, + // params for using the transfer_to_public approval + nonce_for_transfer_to_public_approval: Field, // params for the swap uniswap_fee_tier: Field, // which uniswap tier to use (eg 3000 for 0.3% fee) minimum_output_amount: Field, // minimum output amount to receive (slippage protection for the swap) @@ -133,11 +133,11 @@ contract Uniswap { // Transfer funds to this contract Token::at(input_asset) - .unshield( + .transfer_to_public( context.msg_sender(), context.this_address(), input_amount, - nonce_for_unshield_approval, + nonce_for_transfer_to_public_approval, ) .call(&mut context); diff --git a/yarn-project/aztec.js/src/fee/private_fee_payment_method.ts b/yarn-project/aztec.js/src/fee/private_fee_payment_method.ts index 93618e26136..2c4f17dae49 100644 --- a/yarn-project/aztec.js/src/fee/private_fee_payment_method.ts +++ b/yarn-project/aztec.js/src/fee/private_fee_payment_method.ts @@ -57,9 +57,9 @@ export class PrivateFeePaymentMethod implements FeePaymentMethod { await this.wallet.createAuthWit({ caller: this.paymentContract, action: { - name: 'unshield', + name: 'transfer_to_public', args: [this.wallet.getCompleteAddress().address, this.paymentContract, maxFee, nonce], - selector: FunctionSelector.fromSignature('unshield((Field),(Field),Field,Field)'), + selector: FunctionSelector.fromSignature('transfer_to_public((Field),(Field),Field,Field)'), type: FunctionType.PRIVATE, isStatic: false, to: this.asset, diff --git a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/unshielding.test.ts b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/unshielding.test.ts index 40c9dcc4273..e23372a34c0 100644 --- a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/unshielding.test.ts +++ b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/unshielding.test.ts @@ -31,7 +31,7 @@ describe('e2e_blacklist_token_contract unshielding', () => { await asset.methods.unshield(wallets[0].getAddress(), wallets[0].getAddress(), amount, 0).send().wait(); - tokenSim.unshield(wallets[0].getAddress(), wallets[0].getAddress(), amount); + tokenSim.transferToPublic(wallets[0].getAddress(), wallets[0].getAddress(), amount); }); it('on behalf of other', async () => { @@ -54,7 +54,7 @@ describe('e2e_blacklist_token_contract unshielding', () => { wallets[1].setScopes([wallets[1].getAddress(), wallets[0].getAddress()]); await action.send().wait(); - tokenSim.unshield(wallets[0].getAddress(), wallets[1].getAddress(), amount); + tokenSim.transferToPublic(wallets[0].getAddress(), wallets[1].getAddress(), amount); // Perform the transfer again, should fail const txReplay = asset diff --git a/yarn-project/end-to-end/src/e2e_cross_chain_messaging/token_bridge_public_to_private.test.ts b/yarn-project/end-to-end/src/e2e_cross_chain_messaging/token_bridge_public_to_private.test.ts index 60e71effabe..8f95f9be159 100644 --- a/yarn-project/end-to-end/src/e2e_cross_chain_messaging/token_bridge_public_to_private.test.ts +++ b/yarn-project/end-to-end/src/e2e_cross_chain_messaging/token_bridge_public_to_private.test.ts @@ -51,8 +51,8 @@ describe('e2e_cross_chain_messaging token_bridge_public_to_private', () => { await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, bridgeAmount - shieldAmount); await crossChainTestHarness.expectPrivateBalanceOnL2(ownerAddress, shieldAmount); - // Unshield the tokens again, sending them to the same account, however this can be any account. - await crossChainTestHarness.unshieldTokensOnL2(shieldAmount); + // Transfer to public the tokens again, sending them to the same account, however this can be any account. + await crossChainTestHarness.transferToPublicOnL2(shieldAmount); await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, bridgeAmount); await crossChainTestHarness.expectPrivateBalanceOnL2(ownerAddress, 0n); }); diff --git a/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts b/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts index 53b0b18e510..11542650945 100644 --- a/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts @@ -125,7 +125,7 @@ describe('e2e_fees account_init', () => { // the new account should have paid the full fee to the FPC await expect(t.getBananaPrivateBalanceFn(bobsAddress)).resolves.toEqual([mintedBananas - maxFee]); - // the FPC got paid through "unshield", so it's got a new public balance + // the FPC got paid through "transfer_to_public", so it's got a new public balance await expect(t.getBananaPublicBalanceFn(bananaFPC.address)).resolves.toEqual([ fpcsInitialPublicBananas + actualFee, ]); diff --git a/yarn-project/end-to-end/src/e2e_fees/dapp_subscription.test.ts b/yarn-project/end-to-end/src/e2e_fees/dapp_subscription.test.ts index 681ef173277..3391e624cc5 100644 --- a/yarn-project/end-to-end/src/e2e_fees/dapp_subscription.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/dapp_subscription.test.ts @@ -99,7 +99,7 @@ describe('e2e_fees dapp_subscription', () => { it('should allow Alice to subscribe by paying privately with bananas', async () => { /** PRIVATE SETUP - we first unshield `MAX_FEE` BC from alice's private balance to the FPC's public balance + we first transfer `MAX_FEE` BC from alice's private balance to the FPC's public balance PUBLIC APP LOGIC we then privately transfer `SUBSCRIPTION_AMOUNT` BC from alice to bob's subscription contract diff --git a/yarn-project/end-to-end/src/e2e_lending_contract.test.ts b/yarn-project/end-to-end/src/e2e_lending_contract.test.ts index 1687554c577..4d1f37ccdc5 100644 --- a/yarn-project/end-to-end/src/e2e_lending_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_lending_contract.test.ts @@ -128,7 +128,12 @@ describe('e2e_lending_contract', () => { const nonce = Fr.random(); await wallet.createAuthWit({ caller: lendingContract.address, - action: collateralAsset.methods.unshield(lendingAccount.address, lendingContract.address, depositAmount, nonce), + action: collateralAsset.methods.transfer_to_public( + lendingAccount.address, + lendingContract.address, + depositAmount, + nonce, + ), }); await lendingSim.progressSlots(SLOT_JUMP); lendingSim.depositPrivate(lendingAccount.address, lendingAccount.key(), depositAmount); @@ -157,7 +162,12 @@ describe('e2e_lending_contract', () => { const nonce = Fr.random(); await wallet.createAuthWit({ caller: lendingContract.address, - action: collateralAsset.methods.unshield(lendingAccount.address, lendingContract.address, depositAmount, nonce), + action: collateralAsset.methods.transfer_to_public( + lendingAccount.address, + lendingContract.address, + depositAmount, + nonce, + ), }); await lendingSim.progressSlots(SLOT_JUMP); diff --git a/yarn-project/end-to-end/src/e2e_token_contract/unshielding.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/transfer_to_public.test.ts similarity index 81% rename from yarn-project/end-to-end/src/e2e_token_contract/unshielding.test.ts rename to yarn-project/end-to-end/src/e2e_token_contract/transfer_to_public.test.ts index da7e80485d5..dbecfab9212 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/unshielding.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/transfer_to_public.test.ts @@ -3,8 +3,8 @@ import { Fr, computeAuthWitMessageHash } from '@aztec/aztec.js'; import { DUPLICATE_NULLIFIER_ERROR } from '../fixtures/fixtures.js'; import { TokenContractTest } from './token_contract_test.js'; -describe('e2e_token_contract unshielding', () => { - const t = new TokenContractTest('unshielding'); +describe('e2e_token_contract transfer_to_public', () => { + const t = new TokenContractTest('transfer_to_public'); let { asset, accounts, tokenSim, wallets } = t; beforeAll(async () => { @@ -28,9 +28,9 @@ describe('e2e_token_contract unshielding', () => { const amount = balancePriv / 2n; expect(amount).toBeGreaterThan(0n); - await asset.methods.unshield(accounts[0].address, accounts[0].address, amount, 0).send().wait(); + await asset.methods.transfer_to_public(accounts[0].address, accounts[0].address, amount, 0).send().wait(); - tokenSim.unshield(accounts[0].address, accounts[0].address, amount); + tokenSim.transferToPublic(accounts[0].address, accounts[0].address, amount); }); it('on behalf of other', async () => { @@ -42,23 +42,23 @@ describe('e2e_token_contract unshielding', () => { // We need to compute the message we want to sign and add it to the wallet as approved const action = asset .withWallet(wallets[1]) - .methods.unshield(accounts[0].address, accounts[1].address, amount, nonce); + .methods.transfer_to_public(accounts[0].address, accounts[1].address, amount, nonce); // Both wallets are connected to same node and PXE so we could just insert directly // But doing it in two actions to show the flow. const witness = await wallets[0].createAuthWit({ caller: accounts[1].address, action }); await wallets[1].addAuthWitness(witness); - // We give wallets[1] access to wallets[0]'s notes to unshield the note. + // We give wallets[1] access to wallets[0]'s notes to transfer the tokens to public. wallets[1].setScopes([wallets[1].getAddress(), wallets[0].getAddress()]); await action.send().wait(); - tokenSim.unshield(accounts[0].address, accounts[1].address, amount); + tokenSim.transferToPublic(accounts[0].address, accounts[1].address, amount); // Perform the transfer again, should fail const txReplay = asset .withWallet(wallets[1]) - .methods.unshield(accounts[0].address, accounts[1].address, amount, nonce) + .methods.transfer_to_public(accounts[0].address, accounts[1].address, amount, nonce) .send(); await expect(txReplay.wait()).rejects.toThrow(DUPLICATE_NULLIFIER_ERROR); }); @@ -70,7 +70,7 @@ describe('e2e_token_contract unshielding', () => { expect(amount).toBeGreaterThan(0n); await expect( - asset.methods.unshield(accounts[0].address, accounts[0].address, amount, 0).simulate(), + asset.methods.transfer_to_public(accounts[0].address, accounts[0].address, amount, 0).simulate(), ).rejects.toThrow('Assertion failed: Balance too low'); }); @@ -80,7 +80,7 @@ describe('e2e_token_contract unshielding', () => { expect(amount).toBeGreaterThan(0n); await expect( - asset.methods.unshield(accounts[0].address, accounts[0].address, amount, 1).simulate(), + asset.methods.transfer_to_public(accounts[0].address, accounts[0].address, amount, 1).simulate(), ).rejects.toThrow('Assertion failed: invalid nonce'); }); @@ -93,7 +93,7 @@ describe('e2e_token_contract unshielding', () => { // We need to compute the message we want to sign and add it to the wallet as approved const action = asset .withWallet(wallets[1]) - .methods.unshield(accounts[0].address, accounts[1].address, amount, nonce); + .methods.transfer_to_public(accounts[0].address, accounts[1].address, amount, nonce); // Both wallets are connected to same node and PXE so we could just insert directly // But doing it in two actions to show the flow. @@ -112,7 +112,7 @@ describe('e2e_token_contract unshielding', () => { // We need to compute the message we want to sign and add it to the wallet as approved const action = asset .withWallet(wallets[2]) - .methods.unshield(accounts[0].address, accounts[1].address, amount, nonce); + .methods.transfer_to_public(accounts[0].address, accounts[1].address, amount, nonce); const expectedMessageHash = computeAuthWitMessageHash( { caller: accounts[2].address, action }, { chainId: wallets[0].getChainId(), version: wallets[0].getVersion() }, diff --git a/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts b/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts index c611c233e46..a32510fdb8a 100644 --- a/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts +++ b/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts @@ -369,9 +369,9 @@ export class CrossChainTestHarness { await this.l2Token.methods.redeem_shield(this.ownerAddress, shieldAmount, secret).send().wait(); } - async unshieldTokensOnL2(unshieldAmount: bigint, nonce = Fr.ZERO) { - this.logger.info('Unshielding tokens'); - await this.l2Token.methods.unshield(this.ownerAddress, this.ownerAddress, unshieldAmount, nonce).send().wait(); + async transferToPublicOnL2(amount: bigint, nonce = Fr.ZERO) { + this.logger.info('Transferring tokens to public'); + await this.l2Token.methods.transfer_to_public(this.ownerAddress, this.ownerAddress, amount, nonce).send().wait(); } /** diff --git a/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts b/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts index c3ef0f64e31..d33bcb1ccab 100644 --- a/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts +++ b/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts @@ -207,16 +207,16 @@ export const uniswapL1L2TestSuite = ( const wethL2BalanceBeforeSwap = await wethCrossChainHarness.getL2PrivateBalanceOf(ownerAddress); const daiL2BalanceBeforeSwap = await daiCrossChainHarness.getL2PrivateBalanceOf(ownerAddress); - // 3. Owner gives uniswap approval to unshield funds to self on its behalf - logger.info('Approving uniswap to unshield funds to self on my behalf'); - const nonceForWETHUnshieldApproval = new Fr(1n); + // 3. Owner gives uniswap approval to transfer the funds to public to self on its behalf + logger.info('Approving uniswap to transfer funds to public to self on my behalf'); + const nonceForWETHTransferToPublicApproval = new Fr(1n); await ownerWallet.createAuthWit({ caller: uniswapL2Contract.address, - action: wethCrossChainHarness.l2Token.methods.unshield( + action: wethCrossChainHarness.l2Token.methods.transfer_to_public( ownerAddress, uniswapL2Contract.address, wethAmountToBridge, - nonceForWETHUnshieldApproval, + nonceForWETHTransferToPublicApproval, ), }); @@ -231,7 +231,7 @@ export const uniswapL1L2TestSuite = ( wethCrossChainHarness.l2Bridge.address, wethAmountToBridge, daiCrossChainHarness.l2Bridge.address, - nonceForWETHUnshieldApproval, + nonceForWETHTransferToPublicApproval, uniswapFeeTier, minimumOutputAmount, secretHashForRedeemingDai, @@ -610,15 +610,20 @@ export const uniswapL1L2TestSuite = ( // Edge cases for the private flow: // note - tests for uniswapPortal.sol and minting asset on L2 are covered in other tests. - it('swap_private reverts without unshield approval', async () => { + it('swap_private reverts without transfer to public approval', async () => { // swap should fail since no withdraw approval to uniswap: - const nonceForWETHUnshieldApproval = new Fr(2n); + const nonceForWETHTransferToPublicApproval = new Fr(2n); const expectedMessageHash = computeAuthWitMessageHash( { caller: uniswapL2Contract.address, action: wethCrossChainHarness.l2Token.methods - .unshield(ownerAddress, uniswapL2Contract.address, wethAmountToBridge, nonceForWETHUnshieldApproval) + .transfer_to_public( + ownerAddress, + uniswapL2Contract.address, + wethAmountToBridge, + nonceForWETHTransferToPublicApproval, + ) .request(), }, { chainId: ownerWallet.getChainId(), version: ownerWallet.getVersion() }, @@ -631,7 +636,7 @@ export const uniswapL1L2TestSuite = ( wethCrossChainHarness.l2Bridge.address, wethAmountToBridge, daiCrossChainHarness.l2Bridge.address, - nonceForWETHUnshieldApproval, + nonceForWETHTransferToPublicApproval, uniswapFeeTier, minimumOutputAmount, Fr.random(), @@ -647,16 +652,16 @@ export const uniswapL1L2TestSuite = ( await wethCrossChainHarness.mintTokensPrivateOnL2(wethAmountToBridge); await wethCrossChainHarness.expectPrivateBalanceOnL2(ownerAddress, wethAmountToBridge); - // 2. owner gives uniswap approval to unshield funds: - logger.info('Approving uniswap to unshield funds to self on my behalf'); - const nonceForWETHUnshieldApproval = new Fr(3n); + // 2. owner gives uniswap approval to transfer the funds to public: + logger.info('Approving uniswap to transfer funds to public to self on my behalf'); + const nonceForWETHTransferToPublicApproval = new Fr(3n); await ownerWallet.createAuthWit({ caller: uniswapL2Contract.address, - action: wethCrossChainHarness.l2Token.methods.unshield( + action: wethCrossChainHarness.l2Token.methods.transfer_to_public( ownerAddress, uniswapL2Contract.address, wethAmountToBridge, - nonceForWETHUnshieldApproval, + nonceForWETHTransferToPublicApproval, ), }); @@ -669,7 +674,7 @@ export const uniswapL1L2TestSuite = ( daiCrossChainHarness.l2Bridge.address, // but dai bridge! wethAmountToBridge, daiCrossChainHarness.l2Bridge.address, - nonceForWETHUnshieldApproval, + nonceForWETHTransferToPublicApproval, uniswapFeeTier, minimumOutputAmount, Fr.random(), @@ -803,16 +808,16 @@ export const uniswapL1L2TestSuite = ( logger.info('minting weth on L2'); await wethCrossChainHarness.mintTokensPrivateOnL2(wethAmountToBridge); - // Owner gives uniswap approval to unshield funds to self on its behalf - logger.info('Approving uniswap to unshield funds to self on my behalf'); - const nonceForWETHUnshieldApproval = new Fr(4n); + // Owner gives uniswap approval to transfer the funds to public to self on its behalf + logger.info('Approving uniswap to transfer the funds to public to self on my behalf'); + const nonceForWETHTransferToPublicApproval = new Fr(4n); await ownerWallet.createAuthWit({ caller: uniswapL2Contract.address, - action: wethCrossChainHarness.l2Token.methods.unshield( + action: wethCrossChainHarness.l2Token.methods.transfer_to_public( ownerAddress, uniswapL2Contract.address, wethAmountToBridge, - nonceForWETHUnshieldApproval, + nonceForWETHTransferToPublicApproval, ), }); const wethL2BalanceBeforeSwap = await wethCrossChainHarness.getL2PrivateBalanceOf(ownerAddress); @@ -828,7 +833,7 @@ export const uniswapL1L2TestSuite = ( wethCrossChainHarness.l2Bridge.address, wethAmountToBridge, daiCrossChainHarness.l2Bridge.address, - nonceForWETHUnshieldApproval, + nonceForWETHTransferToPublicApproval, uniswapFeeTier, minimumOutputAmount, secretHashForRedeemingDai, diff --git a/yarn-project/end-to-end/src/simulators/lending_simulator.ts b/yarn-project/end-to-end/src/simulators/lending_simulator.ts index 557677b5da4..9340a165553 100644 --- a/yarn-project/end-to-end/src/simulators/lending_simulator.ts +++ b/yarn-project/end-to-end/src/simulators/lending_simulator.ts @@ -117,7 +117,7 @@ export class LendingSimulator { } depositPrivate(from: AztecAddress, onBehalfOf: Fr, amount: bigint) { - this.collateralAsset.unshield(from, this.lendingContract.address, amount); + this.collateralAsset.transferToPublic(from, this.lendingContract.address, amount); this.deposit(onBehalfOf, amount); } diff --git a/yarn-project/end-to-end/src/simulators/token_simulator.ts b/yarn-project/end-to-end/src/simulators/token_simulator.ts index c4addad4b21..03caf19ab16 100644 --- a/yarn-project/end-to-end/src/simulators/token_simulator.ts +++ b/yarn-project/end-to-end/src/simulators/token_simulator.ts @@ -65,7 +65,7 @@ export class TokenSimulator { this.balancesPrivate.set(to.toString(), toBalance + amount); } - public unshield(from: AztecAddress, to: AztecAddress, amount: bigint) { + public transferToPublic(from: AztecAddress, to: AztecAddress, amount: bigint) { const fromBalance = this.balancesPrivate.get(from.toString()) || 0n; const toBalance = this.balancePublic.get(to.toString()) || 0n; expect(fromBalance).toBeGreaterThanOrEqual(amount); From 9f52cbb5df8821f46d88116eedbe10a74f32e75e Mon Sep 17 00:00:00 2001 From: esau <152162806+sklppy88@users.noreply.github.com> Date: Thu, 31 Oct 2024 03:23:47 +0000 Subject: [PATCH 11/21] feat: removing register recipient in e2e tests as it is unnecessary now ! (#9499) We no longer need to register public keys to send notes ! Also dealing w/ some comments that have arisen from a previous PR in the stack's diffs showing up --- .../logs/l1_payload/encrypted_log_payload.ts | 2 +- .../src/benchmarks/bench_prover.test.ts | 2 -- .../src/composed/e2e_persistence.test.ts | 3 --- .../end-to-end/src/e2e_2_pxes.test.ts | 20 ------------------- .../end-to-end/src/e2e_event_logs.test.ts | 6 +----- .../src/e2e_fees/account_init.test.ts | 3 --- .../transfer_private.test.ts | 1 - .../note_processor/utils/produce_note_daos.ts | 2 +- .../pxe/src/synchronizer/synchronizer.ts | 10 +++++----- 9 files changed, 8 insertions(+), 41 deletions(-) diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_payload.ts b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_payload.ts index 02afcf98d37..13d777de888 100644 --- a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_payload.ts +++ b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_payload.ts @@ -94,7 +94,7 @@ export class EncryptedLogPayload { * Produces the same output as `decryptAsOutgoing`. * * @param ciphertext - The ciphertext for the log - * @param addressSecret - The incoming viewing secret key, used to decrypt the logs + * @param addressSecret - The address secret, used to decrypt the logs * @returns The decrypted log payload */ public static decryptAsIncoming( diff --git a/yarn-project/end-to-end/src/benchmarks/bench_prover.test.ts b/yarn-project/end-to-end/src/benchmarks/bench_prover.test.ts index 45e7ce812c0..2d8254319b0 100644 --- a/yarn-project/end-to-end/src/benchmarks/bench_prover.test.ts +++ b/yarn-project/end-to-end/src/benchmarks/bench_prover.test.ts @@ -153,8 +153,6 @@ describe('benchmarks/proving', () => { await pxe.registerContract(initialTestContract); await pxe.registerContract(initialFpContract); - await pxe.registerRecipient(recipient); - provingPxes.push(pxe); } /*TODO(post-honk): We wait 5 seconds for a race condition in setting up 4 nodes. diff --git a/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts b/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts index 783d5ca6fea..fabf8f8bae6 100644 --- a/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts +++ b/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts @@ -209,8 +209,6 @@ describe('Aztec persistence', () => { }); it('pxe does not know of the deployed contract', async () => { - await context.pxe.registerRecipient(ownerAddress); - const wallet = await getUnsafeSchnorrAccount(context.pxe, Fr.random(), Fr.ZERO).waitSetup(); await expect(TokenBlacklistContract.at(contractAddress, wallet)).rejects.toThrow(/has not been registered/); }); @@ -220,7 +218,6 @@ describe('Aztec persistence', () => { artifact: TokenBlacklistContract.artifact, instance: contractInstance, }); - await context.pxe.registerRecipient(ownerAddress); const wallet = await getUnsafeSchnorrAccount(context.pxe, Fr.random(), Fr.ZERO).waitSetup(); const contract = await TokenBlacklistContract.at(contractAddress, wallet); diff --git a/yarn-project/end-to-end/src/e2e_2_pxes.test.ts b/yarn-project/end-to-end/src/e2e_2_pxes.test.ts index d7dd074d5a0..d0c3c94695f 100644 --- a/yarn-project/end-to-end/src/e2e_2_pxes.test.ts +++ b/yarn-project/end-to-end/src/e2e_2_pxes.test.ts @@ -61,11 +61,6 @@ describe('e2e_2_pxes', () => { const token = await deployToken(walletA, initialBalance, logger); - // Add account B to wallet A - await pxeA.registerRecipient(walletB.getCompleteAddress()); - // Add account A to wallet B - await pxeB.registerRecipient(walletA.getCompleteAddress()); - // Add token to PXE B (PXE A already has it because it was deployed through it) await pxeB.registerContract(token); @@ -145,11 +140,6 @@ describe('e2e_2_pxes', () => { const token = await deployToken(walletA, userABalance, logger); - // Add account B to wallet A - await pxeA.registerRecipient(walletB.getCompleteAddress()); - // Add account A to wallet B - await pxeB.registerRecipient(walletA.getCompleteAddress()); - // Add token to PXE B (PXE A already has it because it was deployed through it) await pxeB.registerContract(token); @@ -194,11 +184,6 @@ describe('e2e_2_pxes', () => { const token = await deployToken(walletA, initialBalance, logger); - // Add account B to wallet A - await pxeA.registerRecipient(walletB.getCompleteAddress()); - // Add account A to wallet B - await pxeB.registerRecipient(walletA.getCompleteAddress()); - // Check initial balances are as expected await expectTokenBalance(walletA, token, walletA.getAddress(), initialBalance, logger); // don't check userB yet @@ -229,9 +214,6 @@ describe('e2e_2_pxes', () => { await sharedAccountOnB.register(); const sharedWalletOnB = await sharedAccountOnB.getWallet(); - // Register wallet B in the pxe of wallet A - await pxeA.registerRecipient(walletB.getCompleteAddress()); - // deploy the contract on PXE A const token = await deployToken(walletA, initialBalance, logger); @@ -304,8 +286,6 @@ describe('e2e_2_pxes', () => { // 4. Adds the nullified public key note to PXE B { - // We need to register the recipient to be able to obtain IvpkM for the note - await pxeB.registerRecipient(walletA.getCompleteAddress()); // We need to register the contract to be able to compute the note hash by calling compute_note_hash_and_optionally_a_nullifier(...) await pxeB.registerContract(testContract); await pxeB.addNullifiedNote(note); diff --git a/yarn-project/end-to-end/src/e2e_event_logs.test.ts b/yarn-project/end-to-end/src/e2e_event_logs.test.ts index 46a3eb44333..53e0c629575 100644 --- a/yarn-project/end-to-end/src/e2e_event_logs.test.ts +++ b/yarn-project/end-to-end/src/e2e_event_logs.test.ts @@ -5,7 +5,6 @@ import { EventType, Fr, L1EventPayload, - type PXE, } from '@aztec/aztec.js'; import { EventSelector } from '@aztec/foundation/abi'; import { makeTuple } from '@aztec/foundation/array'; @@ -24,18 +23,15 @@ describe('Logs', () => { let wallets: AccountWalletWithSecretKey[]; let node: AztecNode; - let pxe: PXE; let teardown: () => Promise; beforeAll(async () => { - ({ teardown, wallets, aztecNode: node, pxe } = await setup(2)); + ({ teardown, wallets, aztecNode: node } = await setup(2)); await ensureAccountsPubliclyDeployed(wallets[0], wallets.slice(0, 2)); testLogContract = await TestLogContract.deploy(wallets[0]).send().deployed(); - - await pxe.registerRecipient(wallets[1].getCompleteAddress()); }); afterAll(() => teardown()); diff --git a/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts b/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts index 11542650945..6569eff2b5e 100644 --- a/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts @@ -182,9 +182,6 @@ describe('e2e_fees account_init', () => { const bobsSigningPubKey = new Schnorr().computePublicKey(bobsPrivateSigningKey); const bobsInstance = bobsAccountManager.getInstance(); - // alice registers bobs keys in the pxe - await pxe.registerRecipient(bobsCompleteAddress); - // and deploys bob's account, paying the fee from her balance const paymentMethod = new FeeJuicePaymentMethod(aliceAddress); const tx = await SchnorrAccountContract.deployWithPublicKeys( diff --git a/yarn-project/end-to-end/src/e2e_token_contract/transfer_private.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/transfer_private.test.ts index ced087e5320..d1e9ffd31aa 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/transfer_private.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/transfer_private.test.ts @@ -61,7 +61,6 @@ describe('e2e_token_contract transfer private', () => { expect(amount).toBeGreaterThan(0n); const nonDeployed = CompleteAddress.random(); - await wallets[0].registerRecipient(nonDeployed); await asset.methods.transfer(nonDeployed.address, amount).send().wait(); diff --git a/yarn-project/pxe/src/note_processor/utils/produce_note_daos.ts b/yarn-project/pxe/src/note_processor/utils/produce_note_daos.ts index 1b86279eaf8..07a1a8a8433 100644 --- a/yarn-project/pxe/src/note_processor/utils/produce_note_daos.ts +++ b/yarn-project/pxe/src/note_processor/utils/produce_note_daos.ts @@ -17,7 +17,7 @@ import { produceNoteDaosForKey } from './produce_note_daos_for_key.js'; * * @param simulator - An instance of AcirSimulator. * @param db - An instance of PxeDatabase. - * @param addressPoint - The public counterpart to the secret key to be used in the decryption of incoming note logs. + * @param addressPoint - The public counterpart to the address secret, which is used in the decryption of incoming note logs. * @param ovpkM - The public counterpart to the secret key to be used in the decryption of outgoing note logs. * @param payload - An instance of l1NotePayload. * @param txHash - The hash of the transaction that created the note. Equivalent to the first nullifier of the transaction. diff --git a/yarn-project/pxe/src/synchronizer/synchronizer.ts b/yarn-project/pxe/src/synchronizer/synchronizer.ts index b2cfd71928e..dfef4ccd49e 100644 --- a/yarn-project/pxe/src/synchronizer/synchronizer.ts +++ b/yarn-project/pxe/src/synchronizer/synchronizer.ts @@ -371,15 +371,15 @@ export class Synchronizer { async #removeNullifiedNotes(notes: IncomingNoteDao[]) { // now group the decoded incoming notes by public key - const publicKeyToIncomingNotes: Map = new Map(); + const addressPointToIncomingNotes: Map = new Map(); for (const noteDao of notes) { - const notesForPublicKey = publicKeyToIncomingNotes.get(noteDao.addressPoint) ?? []; - notesForPublicKey.push(noteDao); - publicKeyToIncomingNotes.set(noteDao.addressPoint, notesForPublicKey); + const notesForAddressPoint = addressPointToIncomingNotes.get(noteDao.addressPoint) ?? []; + notesForAddressPoint.push(noteDao); + addressPointToIncomingNotes.set(noteDao.addressPoint, notesForAddressPoint); } // now for each group, look for the nullifiers in the nullifier tree - for (const [publicKey, notes] of publicKeyToIncomingNotes.entries()) { + for (const [publicKey, notes] of addressPointToIncomingNotes.entries()) { const nullifiers = notes.map(n => n.siloedNullifier); const relevantNullifiers: Fr[] = []; for (const nullifier of nullifiers) { From c473380026e2a8eafff91c4f57e9c2ec8f2718f0 Mon Sep 17 00:00:00 2001 From: esau <152162806+sklppy88@users.noreply.github.com> Date: Thu, 31 Oct 2024 06:42:53 +0000 Subject: [PATCH 12/21] refactor: remove outgoing tagging field in logs (#9502) We are removing the `outgoing_tagging` field in an encrypted log in this PR. We will be repurposing the `incoming_tagging` field as our `tag` field in a PR down the stack. --- .../encrypted_event_emission.nr | 6 +-- .../encrypted_logs/encrypted_note_emission.nr | 6 +-- .../aztec/src/encrypted_logs/payload.nr | 47 +++++++++---------- .../aztec-nr/aztec/src/macros/notes/mod.nr | 2 +- .../contracts/nft_contract/src/main.nr | 2 +- .../contracts/token_contract/src/main.nr | 2 +- .../l1_payload/encrypted_log_payload.test.ts | 6 +-- .../logs/l1_payload/encrypted_log_payload.ts | 33 ++++--------- .../src/note_processor/note_processor.test.ts | 2 +- 9 files changed, 44 insertions(+), 62 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_event_emission.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_event_emission.nr index 1ebb4b5e195..967bae1b570 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_event_emission.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_event_emission.nr @@ -12,7 +12,7 @@ fn compute_payload_and_hash( ovsk_app: Field, ovpk: OvpkM, recipient: AztecAddress, -) -> ([u8; 416 + N * 32], Field) +) -> ([u8; 384 + N * 32], Field) where Event: EventInterface, { @@ -20,7 +20,7 @@ where let plaintext = event.private_to_be_bytes(randomness); // For event logs we never include public values prefix as there are never any public values - let encrypted_log: [u8; 416 + N * 32] = compute_private_log_payload( + let encrypted_log: [u8; 384 + N * 32] = compute_private_log_payload( contract_address, ovsk_app, ovpk, @@ -38,7 +38,7 @@ unconstrained fn compute_payload_and_hash_unconstrained( randomness: Field, ovpk: OvpkM, recipient: AztecAddress, -) -> ([u8; 416 + N * 32], Field) +) -> ([u8; 384 + N * 32], Field) where Event: EventInterface, { diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_note_emission.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_note_emission.nr index 44f3791616b..0c280a9a257 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_note_emission.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_note_emission.nr @@ -15,7 +15,7 @@ fn compute_payload_and_hash( ovsk_app: Field, ovpk: OvpkM, recipient: AztecAddress, -) -> (u32, [u8; 417 + N * 32], Field) +) -> (u32, [u8; 385 + N * 32], Field) where Note: NoteInterface, { @@ -32,7 +32,7 @@ where let plaintext = note.to_be_bytes(storage_slot); // For note logs we always include public values prefix - let encrypted_log: [u8; 417 + N * 32] = + let encrypted_log: [u8; 385 + N * 32] = compute_private_log_payload(contract_address, ovsk_app, ovpk, recipient, plaintext, true); let log_hash = sha256_to_field(encrypted_log); @@ -44,7 +44,7 @@ unconstrained fn compute_payload_and_hash_unconstrained( note: Note, ovpk: OvpkM, recipient: AztecAddress, -) -> (u32, [u8; 417 + N * 32], Field) +) -> (u32, [u8; 385 + N * 32], Field) where Note: NoteInterface, { diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr index fc2b0c34507..27bdc617044 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr @@ -39,7 +39,7 @@ fn compute_private_log_payload( let mut encrypted_bytes: [u8; M] = [0; M]; // @todo We ignore the tags for now - offset += 64; + offset += 32; let eph_pk_bytes = point_to_bytes(eph_pk); for i in 0..32 { @@ -206,7 +206,7 @@ mod test { 0x25afb798ea6d0b8c1618e50fdeafa463059415013d3b7c75d46abf5e242be70c, ); - let log: [u8; 448] = compute_private_log_payload( + let log = compute_private_log_payload( contract_address, ovsk_app, ovpk_m, @@ -219,28 +219,27 @@ mod test { // --> Run the test with AZTEC_GENERATE_TEST_DATA=1 flag to update test data. let encrypted_log_from_typescript = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 141, 70, 12, 14, 67, 77, 132, 110, 193, 234, 40, 110, 64, 144, 235, - 86, 55, 111, 242, 123, 221, 193, 170, 202, 225, 216, 86, 84, 159, 112, 31, 167, 5, 119, - 121, 10, 234, 188, 194, 216, 30, 200, 208, 201, 158, 127, 93, 43, 242, 241, 69, 32, 37, - 220, 119, 122, 23, 132, 4, 248, 81, 217, 61, 232, 24, 146, 63, 133, 24, 120, 113, 217, - 155, 223, 149, 214, 149, 239, 240, 169, 224, 155, 161, 81, 83, 252, 155, 77, 34, 75, - 110, 30, 113, 223, 189, 202, 171, 6, 192, 157, 91, 60, 116, 155, 254, 190, 28, 4, 7, - 236, 205, 4, 245, 27, 187, 89, 20, 38, 128, 200, 160, 145, 185, 127, 198, 203, 207, 97, - 246, 194, 175, 155, 142, 188, 143, 120, 83, 122, 178, 63, 208, 197, 232, 24, 228, 212, - 45, 69, 157, 38, 90, 219, 119, 194, 239, 130, 155, 246, 143, 135, 242, 196, 123, 71, - 139, 181, 122, 231, 228, 26, 7, 100, 63, 101, 195, 83, 8, 61, 85, 123, 148, 227, 29, - 164, 162, 161, 49, 39, 73, 141, 46, 179, 240, 52, 109, 165, 238, 210, 233, 188, 36, 90, - 175, 2, 42, 149, 78, 208, 176, 145, 50, 180, 152, 245, 55, 112, 40, 153, 180, 78, 54, - 102, 119, 98, 56, 235, 246, 51, 179, 86, 45, 127, 18, 77, 187, 168, 41, 24, 232, 113, - 149, 138, 148, 33, 143, 215, 150, 188, 105, 131, 254, 236, 199, 206, 56, 44, 130, 134, - 29, 99, 254, 69, 153, 146, 68, 234, 148, 148, 178, 38, 221, 182, 148, 178, 100, 13, 206, - 0, 91, 71, 58, 207, 26, 227, 190, 21, 143, 85, 138, 209, 202, 34, 142, 159, 121, 61, 9, - 57, 2, 48, 162, 89, 126, 14, 83, 173, 40, 247, 170, 154, 112, 12, 204, 48, 38, 7, 173, - 108, 38, 234, 20, 16, 115, 91, 106, 140, 121, 63, 99, 23, 247, 0, 148, 9, 163, 145, 43, - 21, 238, 47, 40, 204, 241, 124, 246, 201, 75, 114, 3, 1, 229, 197, 130, 109, 227, 158, - 133, 188, 125, 179, 220, 51, 170, 121, 175, 202, 243, 37, 103, 13, 27, 53, 157, 8, 177, - 11, 208, 120, 64, 211, 148, 201, 240, 56, + 0, 0, 0, 141, 70, 12, 14, 67, 77, 132, 110, 193, 234, 40, 110, 64, 144, 235, 86, 55, + 111, 242, 123, 221, 193, 170, 202, 225, 216, 86, 84, 159, 112, 31, 167, 5, 119, 121, 10, + 234, 188, 194, 216, 30, 200, 208, 201, 158, 127, 93, 43, 242, 241, 69, 32, 37, 220, 119, + 122, 23, 132, 4, 248, 81, 217, 61, 232, 24, 146, 63, 133, 24, 120, 113, 217, 155, 223, + 149, 214, 149, 239, 240, 169, 224, 155, 161, 81, 83, 252, 155, 77, 34, 75, 110, 30, 113, + 223, 189, 202, 171, 6, 192, 157, 91, 60, 116, 155, 254, 190, 28, 4, 7, 236, 205, 4, 245, + 27, 187, 89, 20, 38, 128, 200, 160, 145, 185, 127, 198, 203, 207, 97, 246, 194, 175, + 155, 142, 188, 143, 120, 83, 122, 178, 63, 208, 197, 232, 24, 228, 212, 45, 69, 157, 38, + 90, 219, 119, 194, 239, 130, 155, 246, 143, 135, 242, 196, 123, 71, 139, 181, 122, 231, + 228, 26, 7, 100, 63, 101, 195, 83, 8, 61, 85, 123, 148, 227, 29, 164, 162, 161, 49, 39, + 73, 141, 46, 179, 240, 52, 109, 165, 238, 210, 233, 188, 36, 90, 175, 2, 42, 149, 78, + 208, 176, 145, 50, 180, 152, 245, 55, 112, 40, 153, 180, 78, 54, 102, 119, 98, 56, 235, + 246, 51, 179, 86, 45, 127, 18, 77, 187, 168, 41, 24, 232, 113, 149, 138, 148, 33, 143, + 215, 150, 188, 105, 131, 254, 236, 199, 206, 56, 44, 130, 134, 29, 99, 254, 69, 153, + 146, 68, 234, 148, 148, 178, 38, 221, 182, 148, 178, 100, 13, 206, 0, 91, 71, 58, 207, + 26, 227, 190, 21, 143, 85, 138, 209, 202, 34, 142, 159, 121, 61, 9, 57, 2, 48, 162, 89, + 126, 14, 83, 173, 40, 247, 170, 154, 112, 12, 204, 48, 38, 7, 173, 108, 38, 234, 20, 16, + 115, 91, 106, 140, 121, 63, 99, 23, 247, 0, 148, 9, 163, 145, 43, 21, 238, 47, 40, 204, + 241, 124, 246, 201, 75, 114, 3, 1, 229, 197, 130, 109, 227, 158, 133, 188, 125, 179, + 220, 51, 170, 121, 175, 202, 243, 37, 103, 13, 27, 53, 157, 8, 177, 11, 208, 120, 64, + 211, 148, 201, 240, 56, ]; assert_eq(encrypted_log_from_typescript, log); } diff --git a/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr index 15526c653ca..05fde8cfe25 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr @@ -8,7 +8,7 @@ use std::{ comptime global NOTE_HEADER_TYPE = type_of(NoteHeader::empty()); // The following is a fixed ciphertext overhead as defined by `compute_private_log_payload` -comptime global NOTE_CIPHERTEXT_OVERHEAD: u32 = 353; +comptime global NOTE_CIPHERTEXT_OVERHEAD: u32 = 321; /// A map from note type to (note_struct_definition, serialized_note_length, note_type_id, fields). /// `fields` is an array of tuples where each tuple contains the name of the field/struct member (e.g. `amount` diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr index b9b7b610e6d..114b7532ab9 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr @@ -227,7 +227,7 @@ contract NFT { fn _store_payload_in_transient_storage_unsafe( slot: Field, point: Point, - setup_log: [Field; 16], + setup_log: [Field; 15], ) { context.storage_write(slot, point); context.storage_write(slot + aztec::protocol_types::point::POINT_LENGTH as Field, setup_log); diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index cca2c1111dc..277b30864fa 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -786,7 +786,7 @@ contract Token { fn _store_payload_in_transient_storage_unsafe( slot: Field, point: Point, - setup_log: [Field; 16], + setup_log: [Field; 15], ) { context.storage_write(slot, point); context.storage_write(slot + aztec::protocol_types::point::POINT_LENGTH as Field, setup_log); diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_payload.test.ts b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_payload.test.ts index 1885b0ec749..04cc25cc25c 100644 --- a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_payload.test.ts +++ b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_payload.test.ts @@ -32,7 +32,7 @@ describe('EncryptedLogPayload', () => { beforeAll(() => { const incomingBodyPlaintext = randomBytes(128); const contract = AztecAddress.random(); - original = new EncryptedLogPayload(PLACEHOLDER_TAG, PLACEHOLDER_TAG, contract, incomingBodyPlaintext); + original = new EncryptedLogPayload(PLACEHOLDER_TAG, contract, incomingBodyPlaintext); const secretKey = Fr.random(); const partialAddress = Fr.random(); @@ -111,7 +111,7 @@ describe('EncryptedLogPayload', () => { '00000001301640ceea758391b2e161c92c0513f129020f4125256afdae2646ce31099f5c10f48cd9eff7ae5b209c557c70de2e657ee79166868676b787e9417e19260e040fe46be583b71f4ab5b70c2657ff1d05cccf1d292a9369628d1a194f944e659900001027', 'hex', ); - const log = new EncryptedLogPayload(new Fr(0), new Fr(0), contract, plaintext); + const log = new EncryptedLogPayload(new Fr(0), contract, plaintext); const ovskM = new GrumpkinScalar(0x1d7f6b3c491e99f32aad05c433301f3a2b4ed68de661ff8255d275ff94de6fc4n); const ovKeys = getKeyValidationRequest(ovskM, contract); @@ -124,7 +124,7 @@ describe('EncryptedLogPayload', () => { const encrypted = log.encrypt(ephSk, recipientCompleteAddress.address, ovKeys).toString('hex'); expect(encrypted).toMatchInlineSnapshot( - `"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008d460c0e434d846ec1ea286e4090eb56376ff27bddc1aacae1d856549f701fa70577790aeabcc2d81ec8d0c99e7f5d2bf2f1452025dc777a178404f851d93de818923f85187871d99bdf95d695eff0a9e09ba15153fc9b4d224b6e1e71dfbdcaab06c09d5b3c749bfebe1c0407eccd04f51bbb59142680c8a091b97fc6cbcf61f6c2af9b8ebc8f78537ab23fd0c5e818e4d42d459d265adb77c2ef829bf68f87f2c47b478bb57ae7e41a07643f65c353083d557b94e31da4a2a13127498d2eb3f0346da5eed2e9bc245aaf022a954ed0b09132b498f537702899b44e3666776238ebf633b3562d7f124dbba82918e871958a94218fd796bc6983feecc7ce382c82861d63fe45999244ea9494b226ddb694b2640dce005b473acf1ae3be158f558ad1ca228e9f793d09390230a2597e0e53ad28f7aa9a700ccc302607ad6c26ea1410735b6a8c793f6317f7009409a3912b15ee2f28ccf17cf6c94b720301e5c5826de39e85bc7db3dc33aa79afcaf325670d1b359d08b10bd07840d394c9f038"`, + `"00000000000000000000000000000000000000000000000000000000000000008d460c0e434d846ec1ea286e4090eb56376ff27bddc1aacae1d856549f701fa70577790aeabcc2d81ec8d0c99e7f5d2bf2f1452025dc777a178404f851d93de818923f85187871d99bdf95d695eff0a9e09ba15153fc9b4d224b6e1e71dfbdcaab06c09d5b3c749bfebe1c0407eccd04f51bbb59142680c8a091b97fc6cbcf61f6c2af9b8ebc8f78537ab23fd0c5e818e4d42d459d265adb77c2ef829bf68f87f2c47b478bb57ae7e41a07643f65c353083d557b94e31da4a2a13127498d2eb3f0346da5eed2e9bc245aaf022a954ed0b09132b498f537702899b44e3666776238ebf633b3562d7f124dbba82918e871958a94218fd796bc6983feecc7ce382c82861d63fe45999244ea9494b226ddb694b2640dce005b473acf1ae3be158f558ad1ca228e9f793d09390230a2597e0e53ad28f7aa9a700ccc302607ad6c26ea1410735b6a8c793f6317f7009409a3912b15ee2f28ccf17cf6c94b720301e5c5826de39e85bc7db3dc33aa79afcaf325670d1b359d08b10bd07840d394c9f038"`, ); const byteArrayString = `[${encrypted.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16))}]`; diff --git a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_payload.ts b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_payload.ts index 13d777de888..e809c547c41 100644 --- a/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_payload.ts +++ b/yarn-project/circuit-types/src/logs/l1_payload/encrypted_log_payload.ts @@ -29,13 +29,9 @@ const OUTGOING_BODY_SIZE = 144; export class EncryptedLogPayload { constructor( /** - * Note discovery tag used by the recipient of the log. + * Note discovery tag. */ - public readonly incomingTag: Fr, - /** - * Note discovery tag used by the sender of the log. - */ - public readonly outgoingTag: Fr, + public readonly tag: Fr, /** * Address of a contract that emitted the log. */ @@ -75,8 +71,7 @@ export class EncryptedLogPayload { } return serializeToBuffer( - this.incomingTag, - this.outgoingTag, + this.tag, ephPk.toCompressedBuffer(), incomingHeaderCiphertext, outgoingHeaderCiphertext, @@ -104,8 +99,7 @@ export class EncryptedLogPayload { const reader = BufferReader.asReader(ciphertext); try { - const incomingTag = reader.readObject(Fr); - const outgoingTag = reader.readObject(Fr); + const tag = reader.readObject(Fr); const ephPk = Point.fromCompressedBuffer(reader.readBytes(Point.COMPRESSED_SIZE_IN_BYTES)); @@ -118,12 +112,7 @@ export class EncryptedLogPayload { // The incoming can be of variable size, so we read until the end const incomingBodyPlaintext = decrypt(reader.readToEnd(), addressSecret, ephPk); - return new EncryptedLogPayload( - incomingTag, - outgoingTag, - AztecAddress.fromBuffer(incomingHeader), - incomingBodyPlaintext, - ); + return new EncryptedLogPayload(tag, AztecAddress.fromBuffer(incomingHeader), incomingBodyPlaintext); } catch (e: any) { // Following error messages are expected to occur when decryption fails if ( @@ -160,8 +149,7 @@ export class EncryptedLogPayload { const reader = BufferReader.asReader(ciphertext); try { - const incomingTag = reader.readObject(Fr); - const outgoingTag = reader.readObject(Fr); + const tag = reader.readObject(Fr); const ephPk = Point.fromCompressedBuffer(reader.readBytes(Point.COMPRESSED_SIZE_IN_BYTES)); @@ -188,7 +176,7 @@ export class EncryptedLogPayload { // Now we decrypt the incoming body using the ephSk and recipientAddressPoint const incomingBody = decrypt(reader.readToEnd(), ephSk, recipientAddressPoint); - return new EncryptedLogPayload(incomingTag, outgoingTag, contractAddress, incomingBody); + return new EncryptedLogPayload(tag, contractAddress, incomingBody); } catch (e: any) { // Following error messages are expected to occur when decryption fails if ( @@ -206,11 +194,6 @@ export class EncryptedLogPayload { } public toBuffer() { - return serializeToBuffer( - this.incomingTag, - this.outgoingTag, - this.contractAddress.toBuffer(), - this.incomingBodyPlaintext, - ); + return serializeToBuffer(this.tag, this.contractAddress.toBuffer(), this.incomingBodyPlaintext); } } diff --git a/yarn-project/pxe/src/note_processor/note_processor.test.ts b/yarn-project/pxe/src/note_processor/note_processor.test.ts index b23df38feed..1ff8b41df33 100644 --- a/yarn-project/pxe/src/note_processor/note_processor.test.ts +++ b/yarn-project/pxe/src/note_processor/note_processor.test.ts @@ -388,6 +388,6 @@ describe('Note Processor', () => { }); function getRandomNoteLogPayload(app = AztecAddress.random()): EncryptedLogPayload { - return new EncryptedLogPayload(Fr.random(), Fr.random(), app, L1NotePayload.random(app).toIncomingBodyPlaintext()); + return new EncryptedLogPayload(Fr.random(), app, L1NotePayload.random(app).toIncomingBodyPlaintext()); } }); From 2f7127be39f97873d3b3bc55d1a20d6de82f583f Mon Sep 17 00:00:00 2001 From: esau <152162806+sklppy88@users.noreply.github.com> Date: Thu, 31 Oct 2024 07:20:47 +0000 Subject: [PATCH 13/21] fix: remove all register recipient functionality in ts (#9548) This finishes up removing registering recipients. This will need to be accompanied by a docs change via devrel. --- docs/docs/aztec/concepts/accounts/keys.md | 13 +---- .../writing_contracts/how_to_emit_event.md | 18 ------- .../aztec.js/src/wallet/base_wallet.ts | 9 ---- .../aztec.js/src/wallet/create_recipient.ts | 15 ------ .../circuit-types/src/interfaces/pxe.ts | 29 ---------- .../src/structs/complete_address.test.ts | 4 -- .../cli/src/cmds/pxe/get_recipient.ts | 14 ----- .../cli/src/cmds/pxe/get_recipients.ts | 15 ------ yarn-project/cli/src/cmds/pxe/index.ts | 36 ------------- .../cli/src/cmds/pxe/register_recipient.ts | 18 ------- .../src/composed/e2e_persistence.test.ts | 4 -- .../pxe/src/pxe_service/pxe_service.ts | 27 ---------- .../src/pxe_service/test/pxe_test_suite.ts | 54 +------------------ .../pxe/src/simulator_oracle/index.ts | 2 +- 14 files changed, 4 insertions(+), 254 deletions(-) delete mode 100644 yarn-project/aztec.js/src/wallet/create_recipient.ts delete mode 100644 yarn-project/cli/src/cmds/pxe/get_recipient.ts delete mode 100644 yarn-project/cli/src/cmds/pxe/get_recipients.ts delete mode 100644 yarn-project/cli/src/cmds/pxe/register_recipient.ts diff --git a/docs/docs/aztec/concepts/accounts/keys.md b/docs/docs/aztec/concepts/accounts/keys.md index 30f2e8610e1..602cea39f78 100644 --- a/docs/docs/aztec/concepts/accounts/keys.md +++ b/docs/docs/aztec/concepts/accounts/keys.md @@ -27,22 +27,13 @@ Instead it's up to the account contract developer to implement it. ## Public keys retrieval -The keys can be retrieved from the [Private eXecution Environment (PXE)](../pxe/index.md) using the following getter in Aztec.nr: +The keys for our accounts can be retrieved from the [Private eXecution Environment (PXE)](../pxe/index.md) using the following getter in Aztec.nr: ``` fn get_public_keys(account: AztecAddress) -> PublicKeys; ``` -It is necessary to first register the user as a recipient in our PXE, providing their public keys. - -First we need to get a hold of recipient's [complete address](#complete-address). -Below are some ways how we could instantiate it after getting the information in a string form from a recipient: - -#include_code instantiate-complete-address /yarn-project/circuits.js/src/structs/complete_address.test.ts rust - -Then to register the recipient's [complete address](#complete-address) in PXE we would call `registerRecipient` PXE endpoint using Aztec.js. - -#include_code register-recipient /yarn-project/aztec.js/src/wallet/create_recipient.ts rust +It is necessary to first register the user as an account in our PXE, by calling the `registerAccount` PXE endpoint using Aztec.js, providing the account's secret key and partial address. During private function execution these keys are obtained via an oracle call from PXE. diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/how_to_emit_event.md b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/how_to_emit_event.md index 0c0c7fc9d33..47440c4fd1c 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/how_to_emit_event.md +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/how_to_emit_event.md @@ -16,24 +16,6 @@ Unlike on Ethereum, there are 2 types of events supported by Aztec: [encrypted]( ## Encrypted Events -### Register a recipient - -Encrypted events can only be emitted by private functions and are encrypted using a public key of a recipient. -For this reason it is necessary to register a recipient in the Private Execution Environment (PXE) before encrypting the events for them. - -First we need to get a hold of recipient's complete address. -Below are some ways how we could instantiate it after getting the information in a string form from a recipient: - -#include_code instantiate-complete-address /yarn-project/circuits.js/src/structs/complete_address.test.ts rust - -Then to register the recipient's complete address in PXE we would call `registerRecipient` PXE endpoint using Aztec.js - -#include_code register-recipient /yarn-project/aztec.js/src/wallet/create_recipient.ts rust - -:::info -If a note recipient is one of the accounts inside the PXE, we don't need to register it as a recipient because we already have the public key available. You can register a recipient as shown [here](../how_to_deploy_contract.md) -::: - ### Call emit To emit encrypted logs you can import the `encode_and_encrypt` or `encode_and_encrypt_with_keys` functions and pass them into the `emit` function after inserting a note. An example can be seen in the reference token contract's transfer function: diff --git a/yarn-project/aztec.js/src/wallet/base_wallet.ts b/yarn-project/aztec.js/src/wallet/base_wallet.ts index 2ec4fe257b8..2e1a43da978 100644 --- a/yarn-project/aztec.js/src/wallet/base_wallet.ts +++ b/yarn-project/aztec.js/src/wallet/base_wallet.ts @@ -81,21 +81,12 @@ export abstract class BaseWallet implements Wallet { registerAccount(secretKey: Fr, partialAddress: PartialAddress): Promise { return this.pxe.registerAccount(secretKey, partialAddress); } - registerRecipient(account: CompleteAddress): Promise { - return this.pxe.registerRecipient(account); - } getRegisteredAccounts(): Promise { return this.pxe.getRegisteredAccounts(); } getRegisteredAccount(address: AztecAddress): Promise { return this.pxe.getRegisteredAccount(address); } - getRecipients(): Promise { - return this.pxe.getRecipients(); - } - getRecipient(address: AztecAddress): Promise { - return this.pxe.getRecipient(address); - } registerContract(contract: { /** Instance */ instance: ContractInstanceWithAddress; /** Associated artifact */ artifact?: ContractArtifact; diff --git a/yarn-project/aztec.js/src/wallet/create_recipient.ts b/yarn-project/aztec.js/src/wallet/create_recipient.ts deleted file mode 100644 index 1ec56fb6954..00000000000 --- a/yarn-project/aztec.js/src/wallet/create_recipient.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { type PXE } from '@aztec/circuit-types'; -import { CompleteAddress } from '@aztec/circuits.js'; - -/** - * Creates a random address and registers it as a recipient on the pxe server. Useful for testing. - * @param pxe - PXE. - * @returns Complete address of the registered recipient. - */ -export async function createRecipient(pxe: PXE): Promise { - const completeAddress = CompleteAddress.random(); - // docs:start:register-recipient - await pxe.registerRecipient(completeAddress); - // docs:end:register-recipient - return completeAddress; -} diff --git a/yarn-project/circuit-types/src/interfaces/pxe.ts b/yarn-project/circuit-types/src/interfaces/pxe.ts index 9b8da020962..f380cfec734 100644 --- a/yarn-project/circuit-types/src/interfaces/pxe.ts +++ b/yarn-project/circuit-types/src/interfaces/pxe.ts @@ -73,20 +73,6 @@ export interface PXE { */ registerAccount(secretKey: Fr, partialAddress: PartialAddress): Promise; - /** - * Registers a recipient in PXE. This is required when sending encrypted notes to - * a user who hasn't deployed their account contract yet. Since their account is not deployed, their - * encryption public key has not been broadcasted, so we need to manually register it on the PXE Service - * in order to be able to encrypt data for this recipient. - * - * @param recipient - The complete address of the recipient - * @remarks Called recipient because we can only send notes to this account and not receive them via this PXE Service. - * This is because we don't have the associated private key and for this reason we can't decrypt - * the recipient's notes. We can send notes to this account because we can encrypt them with the recipient's - * public key. - */ - registerRecipient(recipient: CompleteAddress): Promise; - /** * Retrieves the user accounts registered on this PXE Service. * @returns An array of the accounts registered on this PXE Service. @@ -102,21 +88,6 @@ export interface PXE { */ getRegisteredAccount(address: AztecAddress): Promise; - /** - * Retrieves the recipients added to this PXE Service. - * @returns An array of recipients registered on this PXE Service. - */ - getRecipients(): Promise; - - /** - * Retrieves the complete address of the recipient corresponding to the provided aztec address. - * Complete addresses include the address, the partial address, and the encryption public key. - * - * @param address - The aztec address of the recipient. - * @returns The complete address of the requested recipient. - */ - getRecipient(address: AztecAddress): Promise; - /** * Registers a contract class in the PXE without registering any associated contract instance with it. * diff --git a/yarn-project/circuits.js/src/structs/complete_address.test.ts b/yarn-project/circuits.js/src/structs/complete_address.test.ts index d531e2e344a..a68d00e5c01 100644 --- a/yarn-project/circuits.js/src/structs/complete_address.test.ts +++ b/yarn-project/circuits.js/src/structs/complete_address.test.ts @@ -35,13 +35,10 @@ describe('CompleteAddress', () => { }); it('instantiates from string and individual components', () => { - // docs:start:instantiate-complete-address - // Typically a recipient would share their complete address with the sender const completeAddressFromString = CompleteAddress.fromString( '0x24e4646f58b9fbe7d38e317db8d5636c423fbbdfbe119fc190fe9c64747e0c6222f7fcddfa3ce3e8f0cc8e82d7b94cdd740afa3e77f8e4a63ea78a239432dcab0471657de2b6216ade6c506d28fbc22ba8b8ed95c871ad9f3e3984e90d9723a7111223493147f6785514b1c195bb37a2589f22a6596d30bb2bb145fdc9ca8f1e273bbffd678edce8fe30e0deafc4f66d58357c06fd4a820285294b9746c3be9509115c96e962322ffed6522f57194627136b8d03ac7469109707f5e44190c4840c49773308a13d740a7f0d4f0e6163b02c5a408b6f965856b6a491002d073d5b00d3d81beb009873eb7116327cf47c612d5758ef083d4fda78e9b63980b2a7622f567d22d2b02fe1f4ad42db9d58a36afd1983e7e2909d1cab61cafedad6193a0a7c585381b10f4666044266a02405bf6e01fa564c8517d4ad5823493abd31de', ); - // Alternatively, a recipient could share the individual components with the sender const address = Fr.fromString('0x24e4646f58b9fbe7d38e317db8d5636c423fbbdfbe119fc190fe9c64747e0c62'); const npkM = Point.fromString( '0x22f7fcddfa3ce3e8f0cc8e82d7b94cdd740afa3e77f8e4a63ea78a239432dcab0471657de2b6216ade6c506d28fbc22ba8b8ed95c871ad9f3e3984e90d9723a7', @@ -63,7 +60,6 @@ describe('CompleteAddress', () => { new PublicKeys(npkM, ivpkM, ovpkM, tpkM), partialAddress, ); - // docs:end:instantiate-complete-address expect(completeAddressFromComponents.equals(completeAddressFromString)).toBe(true); }); diff --git a/yarn-project/cli/src/cmds/pxe/get_recipient.ts b/yarn-project/cli/src/cmds/pxe/get_recipient.ts deleted file mode 100644 index 172ce3e1929..00000000000 --- a/yarn-project/cli/src/cmds/pxe/get_recipient.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { type AztecAddress } from '@aztec/aztec.js'; -import { createCompatibleClient } from '@aztec/aztec.js'; -import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; - -export async function getRecipient(aztecAddress: AztecAddress, rpcUrl: string, debugLogger: DebugLogger, log: LogFn) { - const client = await createCompatibleClient(rpcUrl, debugLogger); - const recipient = await client.getRecipient(aztecAddress); - - if (!recipient) { - log(`Unknown recipient ${aztecAddress.toString()}`); - } else { - log(recipient.toReadableString()); - } -} diff --git a/yarn-project/cli/src/cmds/pxe/get_recipients.ts b/yarn-project/cli/src/cmds/pxe/get_recipients.ts deleted file mode 100644 index 78109911e27..00000000000 --- a/yarn-project/cli/src/cmds/pxe/get_recipients.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { createCompatibleClient } from '@aztec/aztec.js'; -import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; - -export async function getRecipients(rpcUrl: string, debugLogger: DebugLogger, log: LogFn) { - const client = await createCompatibleClient(rpcUrl, debugLogger); - const recipients = await client.getRecipients(); - if (!recipients.length) { - log('No recipients found.'); - } else { - log(`Recipients found: \n`); - for (const recipient of recipients) { - log(recipient.toReadableString()); - } - } -} diff --git a/yarn-project/cli/src/cmds/pxe/index.ts b/yarn-project/cli/src/cmds/pxe/index.ts index 4502505c9fe..bc3e4969a88 100644 --- a/yarn-project/cli/src/cmds/pxe/index.ts +++ b/yarn-project/cli/src/cmds/pxe/index.ts @@ -13,7 +13,6 @@ import { parseOptionalInteger, parseOptionalLogId, parseOptionalTxHash, - parsePartialAddress, parsePublicKey, pxeOption, } from '../../utils/commands.js'; @@ -91,22 +90,6 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: DebugL await getLogs(txHash, fromBlock, toBlock, afterLog, contractAddress, rpcUrl, follow, debugLogger, log); }); - program - .command('register-recipient') - .description('Register a recipient in the PXE.') - .requiredOption('-a, --address ', "The account's Aztec address.", parseAztecAddress) - .requiredOption('-p, --public-key ', 'The account public key.', parsePublicKey) - .requiredOption( - '-pa, --partial-address ', - 'The partially computed address of the account contract.', - parsePartialAddress, - ) - .addOption(pxeOption) - .action(async ({ address, publicKey, partialAddress, rpcUrl }) => { - const { registerRecipient } = await import('./register_recipient.js'); - await registerRecipient(address, publicKey, partialAddress, rpcUrl, debugLogger, log); - }); - program .command('get-accounts') .description('Gets all the Aztec accounts stored in the PXE.') @@ -127,25 +110,6 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: DebugL await getAccount(address, options.rpcUrl, debugLogger, log); }); - program - .command('get-recipients') - .description('Gets all the recipients stored in the PXE.') - .addOption(pxeOption) - .action(async (options: any) => { - const { getRecipients } = await import('./get_recipients.js'); - await getRecipients(options.rpcUrl, debugLogger, log); - }); - - program - .command('get-recipient') - .description('Gets a recipient given its Aztec address.') - .argument('
', 'The Aztec address to get recipient for', parseAztecAddress) - .addOption(pxeOption) - .action(async (address, options) => { - const { getRecipient } = await import('./get_recipient.js'); - await getRecipient(address, options.rpcUrl, debugLogger, log); - }); - program .command('block-number') .description('Gets the current Aztec L2 block number.') diff --git a/yarn-project/cli/src/cmds/pxe/register_recipient.ts b/yarn-project/cli/src/cmds/pxe/register_recipient.ts deleted file mode 100644 index d16987d3eed..00000000000 --- a/yarn-project/cli/src/cmds/pxe/register_recipient.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { type AztecAddress, type Fr } from '@aztec/aztec.js'; -import { createCompatibleClient } from '@aztec/aztec.js'; -import { CompleteAddress } from '@aztec/circuit-types'; -import { type PublicKeys } from '@aztec/circuits.js'; -import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; - -export async function registerRecipient( - aztecAddress: AztecAddress, - publicKeys: PublicKeys, - partialAddress: Fr, - rpcUrl: string, - debugLogger: DebugLogger, - log: LogFn, -) { - const client = await createCompatibleClient(rpcUrl, debugLogger); - await client.registerRecipient(new CompleteAddress(aztecAddress, publicKeys, partialAddress)); - log(`\nRegistered details for account with address: ${aztecAddress}\n`); -} diff --git a/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts b/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts index fabf8f8bae6..fbe36b3effc 100644 --- a/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts +++ b/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts @@ -200,10 +200,6 @@ describe('Aztec persistence', () => { await context.teardown(); }); - it('pxe does not have the owner account', async () => { - await expect(context.pxe.getRecipient(ownerAddress.address)).resolves.toBeUndefined(); - }); - it('the node has the contract', async () => { await expect(context.aztecNode.getContract(contractAddress)).resolves.toBeDefined(); }); diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index b32cbac2846..4504c8d4503 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -221,33 +221,6 @@ export class PXEService implements PXE { return Promise.resolve(account); } - public async registerRecipient(recipient: CompleteAddress): Promise { - const wasAdded = await this.db.addCompleteAddress(recipient); - - if (wasAdded) { - this.log.info(`Added recipient:\n ${recipient.toReadableString()}`); - } else { - this.log.info(`Recipient:\n "${recipient.toReadableString()}"\n already registered.`); - } - } - - public async getRecipients(): Promise { - // Get complete addresses of both the recipients and the accounts - const completeAddresses = await this.db.getCompleteAddresses(); - // Filter out the addresses corresponding to accounts - const accounts = await this.keyStore.getAccounts(); - const recipients = completeAddresses.filter( - completeAddress => !accounts.find(account => account.equals(completeAddress.address)), - ); - return recipients; - } - - public async getRecipient(address: AztecAddress): Promise { - const result = await this.getRecipients(); - const recipient = result.find(r => r.address.equals(address)); - return Promise.resolve(recipient); - } - public async registerContractClass(artifact: ContractArtifact): Promise { const contractClassId = computeContractClassId(getContractClassFromArtifact(artifact)); await this.db.addContractArtifact(contractClassId, artifact); diff --git a/yarn-project/pxe/src/pxe_service/test/pxe_test_suite.ts b/yarn-project/pxe/src/pxe_service/test/pxe_test_suite.ts index c54cc97e274..7a3161ab229 100644 --- a/yarn-project/pxe/src/pxe_service/test/pxe_test_suite.ts +++ b/yarn-project/pxe/src/pxe_service/test/pxe_test_suite.ts @@ -4,15 +4,7 @@ import { randomContractInstanceWithAddress, randomDeployedContract, } from '@aztec/circuit-types'; -import { - AztecAddress, - CompleteAddress, - Fr, - INITIAL_L2_BLOCK_NUM, - Point, - PublicKeys, - getContractClassFromArtifact, -} from '@aztec/circuits.js'; +import { AztecAddress, Fr, INITIAL_L2_BLOCK_NUM, getContractClassFromArtifact } from '@aztec/circuits.js'; export const pxeTestSuite = (testName: string, pxeSetup: () => Promise) => { describe(testName, () => { @@ -29,33 +21,11 @@ export const pxeTestSuite = (testName: string, pxeSetup: () => Promise) => // Check that the account is correctly registered using the getAccounts and getRecipients methods const accounts = await pxe.getRegisteredAccounts(); - const recipients = await pxe.getRecipients(); expect(accounts).toContainEqual(completeAddress); - expect(recipients).not.toContainEqual(completeAddress); // Check that the account is correctly registered using the getAccount and getRecipient methods const account = await pxe.getRegisteredAccount(completeAddress.address); - const recipient = await pxe.getRecipient(completeAddress.address); expect(account).toEqual(completeAddress); - expect(recipient).toBeUndefined(); - }); - - it('registers a recipient and returns it as a recipient only and not as an account', async () => { - const completeAddress = CompleteAddress.random(); - - await pxe.registerRecipient(completeAddress); - - // Check that the recipient is correctly registered using the getAccounts and getRecipients methods - const accounts = await pxe.getRegisteredAccounts(); - const recipients = await pxe.getRecipients(); - expect(accounts).not.toContainEqual(completeAddress); - expect(recipients).toContainEqual(completeAddress); - - // Check that the recipient is correctly registered using the getAccount and getRecipient methods - const account = await pxe.getRegisteredAccount(completeAddress.address); - const recipient = await pxe.getRecipient(completeAddress.address); - expect(account).toBeUndefined(); - expect(recipient).toEqual(completeAddress); }); it('does not throw when registering the same account twice (just ignores the second attempt)', async () => { @@ -66,28 +36,6 @@ export const pxeTestSuite = (testName: string, pxeSetup: () => Promise) => await pxe.registerAccount(randomSecretKey, randomPartialAddress); }); - // Disabled as CompleteAddress constructor now performs preimage validation. - it.skip('cannot register a recipient with the same aztec address but different pub key or partial address', async () => { - const recipient1 = CompleteAddress.random(); - const recipient2 = new CompleteAddress( - recipient1.address, - new PublicKeys(Point.random(), Point.random(), Point.random(), Point.random()), - Fr.random(), - ); - - await pxe.registerRecipient(recipient1); - await expect(() => pxe.registerRecipient(recipient2)).rejects.toThrow( - `Complete address with aztec address ${recipient1.address}`, - ); - }); - - it('does not throw when registering the same recipient twice (just ignores the second attempt)', async () => { - const completeAddress = CompleteAddress.random(); - - await pxe.registerRecipient(completeAddress); - await pxe.registerRecipient(completeAddress); - }); - it('successfully adds a contract', async () => { const contracts = [randomDeployedContract(), randomDeployedContract()]; for (const contract of contracts) { diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 50570db1ae5..4295bc1e555 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -49,7 +49,7 @@ export class SimulatorOracle implements DBOracle { if (!completeAddress) { throw new Error( `No public key registered for address ${account}. - Register it by calling pxe.registerRecipient(...) or pxe.registerAccount(...).\nSee docs for context: https://docs.aztec.network/reference/common_errors/aztecnr-errors#simulation-error-no-public-key-registered-for-address-0x0-register-it-by-calling-pxeregisterrecipient-or-pxeregisteraccount`, + Register it by calling pxe.registerAccount(...).\nSee docs for context: https://docs.aztec.network/reference/common_errors/aztecnr-errors#simulation-error-no-public-key-registered-for-address-0x0-register-it-by-calling-pxeregisterrecipient-or-pxeregisteraccount`, ); } return completeAddress; From 0cc4a4881ea3d61d4ab0bad7594da4f610746f4f Mon Sep 17 00:00:00 2001 From: Gregorio Juliana Date: Thu, 31 Oct 2024 08:25:10 +0100 Subject: [PATCH 14/21] feat: sync tagged logs (#9595) Closes: https://github.com/AztecProtocol/aztec-packages/issues/9380 Implements the logic to sync tagged note logs from the node, taking into account the indices of "last seen" ones. Unfortunately, the approach of modularizing it as oracles and putting the logic in the contract itself was met with limitations in noir (namely, nested slices) and even the code is ready and close to becoming separate oracles, it cannot be done (yet! or at least without major caveats and performance implications) --- .../aztec-nr/aztec/src/oracle/notes.nr | 4 +- .../types/src/indexed_tagging_secret.nr | 4 +- yarn-project/aztec.js/src/utils/account.ts | 3 +- .../src/logs/encrypted_l2_note_log.ts | 4 +- .../circuits.js/src/keys/derivation.ts | 5 +- yarn-project/circuits.js/src/structs/index.ts | 2 +- .../src/structs/indexed_tagging_secret.ts | 9 - .../circuits.js/src/structs/tagging_secret.ts | 20 +++ yarn-project/pxe/package.json | 2 + .../pxe/src/database/kv_pxe_database.ts | 28 +-- yarn-project/pxe/src/database/pxe_database.ts | 18 +- yarn-project/pxe/src/index.ts | 2 +- .../pxe/src/simulator_oracle/index.ts | 62 ++++++- .../simulator_oracle/simulator_oracle.test.ts | 165 ++++++++++++++++++ yarn-project/txe/src/oracle/txe_oracle.ts | 13 +- yarn-project/yarn.lock | 4 +- 16 files changed, 301 insertions(+), 44 deletions(-) delete mode 100644 yarn-project/circuits.js/src/structs/indexed_tagging_secret.ts create mode 100644 yarn-project/circuits.js/src/structs/tagging_secret.ts create mode 100644 yarn-project/pxe/src/simulator_oracle/simulator_oracle.test.ts diff --git a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr index 00bb2f14ab1..39ed994516f 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr @@ -231,11 +231,11 @@ pub unconstrained fn get_app_tagging_secrets_for_senders( let results = get_app_tagging_secrets_for_senders_oracle(recipient); let mut indexed_tagging_secrets = &[]; for i in 0..results.len() { - if i % 2 != 0 { + if i % 3 != 0 { continue; } indexed_tagging_secrets = indexed_tagging_secrets.push_back( - IndexedTaggingSecret::deserialize([results[i], results[i + 1]]), + IndexedTaggingSecret::deserialize([results[i], results[i + 1], results[i + 2]]), ); } indexed_tagging_secrets diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/indexed_tagging_secret.nr b/noir-projects/noir-protocol-circuits/crates/types/src/indexed_tagging_secret.nr index 1685fdf38e8..c3191eb9e08 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/indexed_tagging_secret.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/indexed_tagging_secret.nr @@ -1,10 +1,12 @@ use crate::traits::Deserialize; +use super::address::aztec_address::AztecAddress; use std::meta::derive; -pub global INDEXED_TAGGING_SECRET_LENGTH: u32 = 2; +pub global INDEXED_TAGGING_SECRET_LENGTH: u32 = 3; #[derive(Deserialize)] pub struct IndexedTaggingSecret { secret: Field, + recipient: AztecAddress, index: u32, } diff --git a/yarn-project/aztec.js/src/utils/account.ts b/yarn-project/aztec.js/src/utils/account.ts index cfc1c5bf479..5f11c192ede 100644 --- a/yarn-project/aztec.js/src/utils/account.ts +++ b/yarn-project/aztec.js/src/utils/account.ts @@ -1,4 +1,5 @@ -import { type CompleteAddress, type PXE } from '@aztec/circuit-types'; +import { type PXE } from '@aztec/circuit-types'; +import { type CompleteAddress } from '@aztec/circuits.js'; import { retryUntil } from '@aztec/foundation/retry'; import { DefaultWaitOpts, type WaitOpts } from '../contract/sent_tx.js'; diff --git a/yarn-project/circuit-types/src/logs/encrypted_l2_note_log.ts b/yarn-project/circuit-types/src/logs/encrypted_l2_note_log.ts index 3202155a858..095fe870b29 100644 --- a/yarn-project/circuit-types/src/logs/encrypted_l2_note_log.ts +++ b/yarn-project/circuit-types/src/logs/encrypted_l2_note_log.ts @@ -60,10 +60,10 @@ export class EncryptedL2NoteLog { * Crates a random log. * @returns A random log. */ - public static random(): EncryptedL2NoteLog { + public static random(tag: Fr = Fr.random()): EncryptedL2NoteLog { const randomEphPubKey = Point.random(); const randomLogContent = randomBytes(144 - Point.COMPRESSED_SIZE_IN_BYTES); - const data = Buffer.concat([Fr.random().toBuffer(), randomLogContent, randomEphPubKey.toCompressedBuffer()]); + const data = Buffer.concat([tag.toBuffer(), randomLogContent, randomEphPubKey.toCompressedBuffer()]); return new EncryptedL2NoteLog(data); } diff --git a/yarn-project/circuits.js/src/keys/derivation.ts b/yarn-project/circuits.js/src/keys/derivation.ts index 1fa9276e3f3..337091c197a 100644 --- a/yarn-project/circuits.js/src/keys/derivation.ts +++ b/yarn-project/circuits.js/src/keys/derivation.ts @@ -134,5 +134,8 @@ export function computeTaggingSecret(knownAddress: CompleteAddress, ivsk: Fq, ex const curve = new Grumpkin(); // Given A (known complete address) -> B (external address) and h == preaddress // Compute shared secret as S = (h_A + ivsk_A) * Addr_Point_B - return curve.mul(externalAddressPoint, ivsk.add(new Fq(knownPreaddress.toBigInt()))); + + // Beware! h_a + ivsk_a (also known as the address secret) can lead to an address point with a negative y-coordinate, since there's two possible candidates + // computeAddressSecret takes care of selecting the one that leads to a positive y-coordinate, which is the only valid address point + return curve.mul(externalAddressPoint, computeAddressSecret(knownPreaddress, ivsk)); } diff --git a/yarn-project/circuits.js/src/structs/index.ts b/yarn-project/circuits.js/src/structs/index.ts index 4e047dfc520..7bc2702e0a5 100644 --- a/yarn-project/circuits.js/src/structs/index.ts +++ b/yarn-project/circuits.js/src/structs/index.ts @@ -13,7 +13,7 @@ export * from './gas_fees.js'; export * from './gas_settings.js'; export * from './global_variables.js'; export * from './header.js'; -export * from './indexed_tagging_secret.js'; +export * from './tagging_secret.js'; export * from './kernel/combined_accumulated_data.js'; export * from './kernel/combined_constant_data.js'; export * from './kernel/enqueued_call_data.js'; diff --git a/yarn-project/circuits.js/src/structs/indexed_tagging_secret.ts b/yarn-project/circuits.js/src/structs/indexed_tagging_secret.ts deleted file mode 100644 index ab218b5b7ee..00000000000 --- a/yarn-project/circuits.js/src/structs/indexed_tagging_secret.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Fr } from '@aztec/foundation/fields'; - -export class IndexedTaggingSecret { - constructor(public secret: Fr, public index: number) {} - - toFields(): Fr[] { - return [this.secret, new Fr(this.index)]; - } -} diff --git a/yarn-project/circuits.js/src/structs/tagging_secret.ts b/yarn-project/circuits.js/src/structs/tagging_secret.ts new file mode 100644 index 00000000000..b43e8cb8030 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/tagging_secret.ts @@ -0,0 +1,20 @@ +import { type AztecAddress } from '@aztec/foundation/aztec-address'; +import { Fr } from '@aztec/foundation/fields'; + +export class TaggingSecret { + constructor(public secret: Fr, public recipient: AztecAddress) {} + + toFields(): Fr[] { + return [this.secret, this.recipient]; + } +} + +export class IndexedTaggingSecret extends TaggingSecret { + constructor(secret: Fr, recipient: AztecAddress, public index: number) { + super(secret, recipient); + } + + override toFields(): Fr[] { + return [this.secret, this.recipient, new Fr(this.index)]; + } +} diff --git a/yarn-project/pxe/package.json b/yarn-project/pxe/package.json index b36c544458d..2d84ed81f13 100644 --- a/yarn-project/pxe/package.json +++ b/yarn-project/pxe/package.json @@ -87,9 +87,11 @@ "@jest/globals": "^29.5.0", "@types/jest": "^29.5.0", "@types/lodash.omit": "^4.5.7", + "@types/lodash.times": "^4.3.9", "@types/node": "^18.7.23", "jest": "^29.5.0", "jest-mock-extended": "^3.0.3", + "lodash.times": "^4.3.2", "ts-node": "^10.9.1", "typescript": "^5.0.4" }, diff --git a/yarn-project/pxe/src/database/kv_pxe_database.ts b/yarn-project/pxe/src/database/kv_pxe_database.ts index 95a5da82e45..6d84f65c567 100644 --- a/yarn-project/pxe/src/database/kv_pxe_database.ts +++ b/yarn-project/pxe/src/database/kv_pxe_database.ts @@ -1,16 +1,12 @@ -import { - type IncomingNotesFilter, - MerkleTreeId, - NoteStatus, - type OutgoingNotesFilter, - type PublicKey, -} from '@aztec/circuit-types'; +import { type IncomingNotesFilter, MerkleTreeId, NoteStatus, type OutgoingNotesFilter } from '@aztec/circuit-types'; import { AztecAddress, CompleteAddress, type ContractInstanceWithAddress, Header, + type PublicKey, SerializableContractInstance, + type TaggingSecret, computePoint, } from '@aztec/circuits.js'; import { type ContractArtifact } from '@aztec/foundation/abi'; @@ -576,19 +572,23 @@ export class KVPxeDatabase implements PxeDatabase { return incomingNotesSize + outgoingNotesSize + treeRootsSize + authWitsSize + addressesSize; } - async incrementTaggingSecretsIndexes(appTaggingSecrets: Fr[]): Promise { - const indexes = await this.getTaggingSecretsIndexes(appTaggingSecrets); + async incrementTaggingSecretsIndexes(appTaggingSecretsWithRecipient: TaggingSecret[]): Promise { + const indexes = await this.getTaggingSecretsIndexes(appTaggingSecretsWithRecipient); await this.db.transaction(() => { - indexes.forEach(index => { - const nextIndex = index ? index + 1 : 1; - void this.#taggingSecretIndexes.set(appTaggingSecrets.toString(), nextIndex); + indexes.forEach((taggingSecretIndex, listIndex) => { + const nextIndex = taggingSecretIndex ? taggingSecretIndex + 1 : 1; + const { secret, recipient } = appTaggingSecretsWithRecipient[listIndex]; + const key = `${secret.toString()}-${recipient.toString()}`; + void this.#taggingSecretIndexes.set(key, nextIndex); }); }); } - getTaggingSecretsIndexes(appTaggingSecrets: Fr[]): Promise { + getTaggingSecretsIndexes(appTaggingSecretsWithRecipient: TaggingSecret[]): Promise { return this.db.transaction(() => - appTaggingSecrets.map(secret => this.#taggingSecretIndexes.get(secret.toString()) ?? 0), + appTaggingSecretsWithRecipient.map( + ({ secret, recipient }) => this.#taggingSecretIndexes.get(`${secret.toString()}-${recipient.toString()}`) ?? 0, + ), ); } } diff --git a/yarn-project/pxe/src/database/pxe_database.ts b/yarn-project/pxe/src/database/pxe_database.ts index db845d06828..961301501de 100644 --- a/yarn-project/pxe/src/database/pxe_database.ts +++ b/yarn-project/pxe/src/database/pxe_database.ts @@ -4,6 +4,7 @@ import { type ContractInstanceWithAddress, type Header, type PublicKey, + type TaggingSecret, } from '@aztec/circuits.js'; import { type ContractArtifact } from '@aztec/foundation/abi'; import { type AztecAddress } from '@aztec/foundation/aztec-address'; @@ -186,7 +187,20 @@ export interface PxeDatabase extends ContractArtifactDatabase, ContractInstanceD */ estimateSize(): Promise; - getTaggingSecretsIndexes(appTaggingSecrets: Fr[]): Promise; + /** + * Returns the last seen indexes for the provided app siloed tagging secrets or 0 if they've never been seen. + * The recipient must also be provided to convey "directionality" of the secret and index pair, or in other words + * whether the index was used to tag a sent or received note. + * @param appTaggingSecrets - The app siloed tagging secrets. + * @returns The indexes for the provided secrets, 0 if they've never been seen. + */ + getTaggingSecretsIndexes(appTaggingSecretsWithRecipient: TaggingSecret[]): Promise; - incrementTaggingSecretsIndexes(appTaggingSecrets: Fr[]): Promise; + /** + * Increments the index for the provided app siloed tagging secrets. + * The recipient must also be provided to convey "directionality" of the secret and index pair, or in other words + * whether the index was used to tag a sent or received note. + * @param appTaggingSecrets - The app siloed tagging secrets. + */ + incrementTaggingSecretsIndexes(appTaggingSecretsWithRecipient: TaggingSecret[]): Promise; } diff --git a/yarn-project/pxe/src/index.ts b/yarn-project/pxe/src/index.ts index 86b3f1205e7..d6e46f5ad84 100644 --- a/yarn-project/pxe/src/index.ts +++ b/yarn-project/pxe/src/index.ts @@ -4,7 +4,7 @@ export * from './config/index.js'; export { Tx, TxHash } from '@aztec/circuit-types'; -export { TxRequest, PartialAddress } from '@aztec/circuits.js'; +export { TxRequest } from '@aztec/circuits.js'; export * from '@aztec/foundation/fields'; export * from '@aztec/foundation/eth-address'; export * from '@aztec/foundation/aztec-address'; diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 4295bc1e555..9f4b278ecba 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -1,5 +1,6 @@ import { type AztecNode, + type EncryptedL2NoteLog, type L2Block, MerkleTreeId, type NoteStatus, @@ -17,6 +18,7 @@ import { IndexedTaggingSecret, type KeyValidationRequest, type L1_TO_L2_MSG_TREE_HEIGHT, + TaggingSecret, computeTaggingSecret, } from '@aztec/circuits.js'; import { type FunctionArtifact, getFunctionArtifact } from '@aztec/foundation/abi'; @@ -247,9 +249,11 @@ export class SimulatorOracle implements DBOracle { const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender); const sharedSecret = computeTaggingSecret(senderCompleteAddress, senderIvsk, recipient); // Silo the secret to the app so it can't be used to track other app's notes - const secret = poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]); - const [index] = await this.db.getTaggingSecretsIndexes([secret]); - return new IndexedTaggingSecret(secret, index); + const siloedSecret = poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]); + // Get the index of the secret, ensuring the directionality (sender -> recipient) + const directionalSecret = new TaggingSecret(siloedSecret, recipient); + const [index] = await this.db.getTaggingSecretsIndexes([directionalSecret]); + return new IndexedTaggingSecret(siloedSecret, recipient, index); } /** @@ -274,7 +278,55 @@ export class SimulatorOracle implements DBOracle { const sharedSecret = computeTaggingSecret(recipientCompleteAddress, recipientIvsk, sender); return poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]); }); - const indexes = await this.db.getTaggingSecretsIndexes(secrets); - return secrets.map((secret, i) => new IndexedTaggingSecret(secret, indexes[i])); + // Ensure the directionality (sender -> recipient) + const directionalSecrets = secrets.map(secret => new TaggingSecret(secret, recipient)); + const indexes = await this.db.getTaggingSecretsIndexes(directionalSecrets); + return directionalSecrets.map( + ({ secret, recipient }, i) => new IndexedTaggingSecret(secret, recipient, indexes[i]), + ); + } + + /** + * Synchronizes the logs tagged with the recipient's address and all the senders in the addressbook. + * Returns the unsynched logs and updates the indexes of the secrets used to tag them until there are no more logs to sync. + * @param contractAddress - The address of the contract that the logs are tagged for + * @param recipient - The address of the recipient + * @returns A list of encrypted logs tagged with the recipient's address + */ + public async syncTaggedLogs(contractAddress: AztecAddress, recipient: AztecAddress): Promise { + // Ideally this algorithm would be implemented in noir, exposing its building blocks as oracles. + // However it is impossible at the moment due to the language not supporting nested slices. + // This nesting is necessary because for a given set of tags we don't + // know how many logs we will get back. Furthermore, these logs are of undetermined + // length, since we don't really know the note they correspond to until we decrypt them. + + // 1. Get all the secrets for the recipient and sender pairs (#9365) + let appTaggingSecrets = await this.getAppTaggingSecretsForSenders(contractAddress, recipient); + + const logs: EncryptedL2NoteLog[] = []; + while (appTaggingSecrets.length > 0) { + // 2. Compute tags using the secrets, recipient and index. Obtain logs for each tag (#9380) + const currentTags = appTaggingSecrets.map(({ secret, recipient, index }) => + poseidon2Hash([secret, recipient, index]), + ); + const logsByTags = await this.aztecNode.getLogsByTags(currentTags); + const newTaggingSecrets: IndexedTaggingSecret[] = []; + logsByTags.forEach((logsByTag, index) => { + // 3.1. Append logs to the list and increment the index for the tags that have logs (#9380) + if (logsByTag.length > 0) { + logs.push(...logsByTag); + // 3.2. Increment the index for the tags that have logs (#9380) + newTaggingSecrets.push( + new IndexedTaggingSecret(appTaggingSecrets[index].secret, recipient, appTaggingSecrets[index].index + 1), + ); + } + }); + // 4. Consolidate in db and replace initial appTaggingSecrets with the new ones (updated indexes) + await this.db.incrementTaggingSecretsIndexes( + newTaggingSecrets.map(secret => new TaggingSecret(secret.secret, recipient)), + ); + appTaggingSecrets = newTaggingSecrets; + } + return logs; } } diff --git a/yarn-project/pxe/src/simulator_oracle/simulator_oracle.test.ts b/yarn-project/pxe/src/simulator_oracle/simulator_oracle.test.ts new file mode 100644 index 00000000000..d9e8728ad95 --- /dev/null +++ b/yarn-project/pxe/src/simulator_oracle/simulator_oracle.test.ts @@ -0,0 +1,165 @@ +import { type AztecNode, EncryptedL2NoteLog } from '@aztec/circuit-types'; +import { + AztecAddress, + CompleteAddress, + type Fq, + Fr, + TaggingSecret, + computeAddress, + computeTaggingSecret, + deriveKeys, +} from '@aztec/circuits.js'; +import { poseidon2Hash } from '@aztec/foundation/crypto'; +import { KeyStore } from '@aztec/key-store'; +import { openTmpStore } from '@aztec/kv-store/utils'; + +import { type MockProxy, mock } from 'jest-mock-extended'; +import times from 'lodash.times'; + +import { type PxeDatabase } from '../database/index.js'; +import { KVPxeDatabase } from '../database/kv_pxe_database.js'; +import { ContractDataOracle } from '../index.js'; +import { SimulatorOracle } from './index.js'; + +function computeTagForIndex( + sender: { completeAddress: CompleteAddress; ivsk: Fq }, + recipient: AztecAddress, + contractAddress: AztecAddress, + index: number, +) { + const sharedSecret = computeTaggingSecret(sender.completeAddress, sender.ivsk, recipient); + const siloedSecret = poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]); + return poseidon2Hash([siloedSecret, recipient, index]); +} + +describe('Simulator oracle', () => { + let aztecNode: MockProxy; + let database: PxeDatabase; + let contractDataOracle: ContractDataOracle; + let simulatorOracle: SimulatorOracle; + let keyStore: KeyStore; + + let recipient: CompleteAddress; + let contractAddress: AztecAddress; + + const NUM_SENDERS = 10; + let senders: { completeAddress: CompleteAddress; ivsk: Fq }[]; + + beforeEach(async () => { + const db = openTmpStore(); + aztecNode = mock(); + database = new KVPxeDatabase(db); + contractDataOracle = new ContractDataOracle(database); + keyStore = new KeyStore(db); + simulatorOracle = new SimulatorOracle(contractDataOracle, database, keyStore, aztecNode); + // Set up contract address + contractAddress = AztecAddress.random(); + // Set up recipient account + recipient = await keyStore.addAccount(new Fr(69), Fr.random()); + await database.addCompleteAddress(recipient); + // Set up the address book + senders = times(NUM_SENDERS).map((_, index) => { + const keys = deriveKeys(new Fr(index)); + const partialAddress = Fr.random(); + const address = computeAddress(keys.publicKeys, partialAddress); + const completeAddress = new CompleteAddress(address, keys.publicKeys, partialAddress); + return { completeAddress, ivsk: keys.masterIncomingViewingSecretKey }; + }); + for (const sender of senders) { + await database.addCompleteAddress(sender.completeAddress); + } + + const logs: { [k: string]: EncryptedL2NoteLog[] } = {}; + + // Add a random note from every address in the address book for our account with index 0 + // Compute the tag as sender (knowledge of preaddress and ivsk) + for (const sender of senders) { + const tag = computeTagForIndex(sender, recipient.address, contractAddress, 0); + const log = EncryptedL2NoteLog.random(tag); + logs[tag.toString()] = [log]; + } + // Accumulated logs intended for recipient: NUM_SENDERS + + // Add a random note from the first sender in the address book, repeating the tag + // Compute the tag as sender (knowledge of preaddress and ivsk) + const firstSender = senders[0]; + const tag = computeTagForIndex(firstSender, recipient.address, contractAddress, 0); + const log = EncryptedL2NoteLog.random(tag); + logs[tag.toString()].push(log); + // Accumulated logs intended for recipient: NUM_SENDERS + 1 + + // Add a random note from half the address book for our account with index 1 + // Compute the tag as sender (knowledge of preaddress and ivsk) + for (let i = NUM_SENDERS / 2; i < NUM_SENDERS; i++) { + const sender = senders[i]; + const tag = computeTagForIndex(sender, recipient.address, contractAddress, 1); + const log = EncryptedL2NoteLog.random(tag); + logs[tag.toString()] = [log]; + } + // Accumulated logs intended for recipient: NUM_SENDERS + 1 + NUM_SENDERS / 2 + + // Add a random note from every address in the address book for a random recipient with index 0 + // Compute the tag as sender (knowledge of preaddress and ivsk) + for (const sender of senders) { + const keys = deriveKeys(Fr.random()); + const partialAddress = Fr.random(); + const randomRecipient = computeAddress(keys.publicKeys, partialAddress); + const tag = computeTagForIndex(sender, randomRecipient, contractAddress, 0); + const log = EncryptedL2NoteLog.random(tag); + logs[tag.toString()] = [log]; + } + // Accumulated logs intended for recipient: NUM_SENDERS + 1 + NUM_SENDERS / 2 + + // Set up the getTaggedLogs mock + + aztecNode.getLogsByTags.mockImplementation(tags => { + return Promise.resolve(tags.map(tag => logs[tag.toString()] ?? [])); + }); + }); + + describe('sync tagged logs', () => { + it('should sync tagged logs', async () => { + const syncedLogs = await simulatorOracle.syncTaggedLogs(contractAddress, recipient.address); + // We expect to have all logs intended for the recipient, one per sender + 1 with a duplicated tag for the first one + half of the logs for the second index + expect(syncedLogs).toHaveLength(NUM_SENDERS + 1 + NUM_SENDERS / 2); + + // Recompute the secrets (as recipient) to ensure indexes are updated + + const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); + const directionalSecrets = senders.map(sender => { + const firstSenderSharedSecret = computeTaggingSecret(recipient, ivsk, sender.completeAddress.address); + const siloedSecret = poseidon2Hash([firstSenderSharedSecret.x, firstSenderSharedSecret.y, contractAddress]); + return new TaggingSecret(siloedSecret, recipient.address); + }); + + // First sender should have 2 logs, but keep index 1 since they were built using the same tag + // Next 4 senders hould also have index 1 + // Last 5 senders should have index 2 + const indexes = await database.getTaggingSecretsIndexes(directionalSecrets); + + expect(indexes).toHaveLength(NUM_SENDERS); + expect(indexes).toEqual([1, 1, 1, 1, 1, 2, 2, 2, 2, 2]); + }); + + it('should only sync tagged logs for which indexes are not updated', async () => { + // Recompute the secrets (as recipient) to update indexes + + const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); + const directionalSecrets = senders.map(sender => { + const firstSenderSharedSecret = computeTaggingSecret(recipient, ivsk, sender.completeAddress.address); + const siloedSecret = poseidon2Hash([firstSenderSharedSecret.x, firstSenderSharedSecret.y, contractAddress]); + return new TaggingSecret(siloedSecret, recipient.address); + }); + + await database.incrementTaggingSecretsIndexes(directionalSecrets); + + const syncedLogs = await simulatorOracle.syncTaggedLogs(contractAddress, recipient.address); + + // Only half of the logs should be synced since we start from index 1, the other half should be skipped + expect(syncedLogs).toHaveLength(NUM_SENDERS / 2); + + // We should have called the node twice, once for index 1 and once for index 2 (which should return no logs) + expect(aztecNode.getLogsByTags.mock.calls.length).toBe(2); + }); + }); +}); diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 1078e6e329e..1754fa8e8c1 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -1,5 +1,6 @@ import { AuthWitness, + type EncryptedL2NoteLog, MerkleTreeId, Note, type NoteStatus, @@ -29,6 +30,7 @@ import { PrivateContextInputs, PublicDataTreeLeaf, type PublicDataTreeLeafPreimage, + TaggingSecret, TxContext, computeContractClassId, computeTaggingSecret, @@ -90,6 +92,8 @@ export class TXE implements TypedOracle { private version: Fr = Fr.ONE; private chainId: Fr = Fr.ONE; + private logsByTags = new Map(); + constructor( private logger: Logger, private trees: MerkleTrees, @@ -758,8 +762,8 @@ export class TXE implements TypedOracle { const sharedSecret = computeTaggingSecret(senderCompleteAddress, senderIvsk, recipient); // Silo the secret to the app so it can't be used to track other app's notes const secret = poseidon2Hash([sharedSecret.x, sharedSecret.y, this.contractAddress]); - const [index] = await this.txeDatabase.getTaggingSecretsIndexes([secret]); - return new IndexedTaggingSecret(secret, index); + const [index] = await this.txeDatabase.getTaggingSecretsIndexes([new TaggingSecret(secret, recipient)]); + return new IndexedTaggingSecret(secret, recipient, index); } async getAppTaggingSecretsForSenders(recipient: AztecAddress): Promise { @@ -775,8 +779,9 @@ export class TXE implements TypedOracle { const sharedSecret = computeTaggingSecret(recipientCompleteAddress, recipientIvsk, sender); return poseidon2Hash([sharedSecret.x, sharedSecret.y, this.contractAddress]); }); - const indexes = await this.txeDatabase.getTaggingSecretsIndexes(secrets); - return secrets.map((secret, i) => new IndexedTaggingSecret(secret, indexes[i])); + const directionalSecrets = secrets.map(secret => new TaggingSecret(secret, recipient)); + const indexes = await this.txeDatabase.getTaggingSecretsIndexes(directionalSecrets); + return secrets.map((secret, i) => new IndexedTaggingSecret(secret, recipient, indexes[i])); } // AVM oracles diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index a20dfb4d126..d31f814b645 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -1022,12 +1022,14 @@ __metadata: "@noir-lang/types": "workspace:*" "@types/jest": ^29.5.0 "@types/lodash.omit": ^4.5.7 + "@types/lodash.times": ^4.3.9 "@types/node": ^18.7.23 jest: ^29.5.0 jest-mock-extended: ^3.0.3 koa: ^2.14.2 koa-router: ^12.0.0 lodash.omit: ^4.5.0 + lodash.times: ^4.3.2 sha3: ^2.1.4 ts-node: ^10.9.1 tslib: ^2.4.0 @@ -4718,7 +4720,7 @@ __metadata: languageName: node linkType: hard -"@types/lodash.times@npm:^4.3.7": +"@types/lodash.times@npm:^4.3.7, @types/lodash.times@npm:^4.3.9": version: 4.3.9 resolution: "@types/lodash.times@npm:4.3.9" dependencies: From 6526069b4faabf1a3b6834da9c290e077715a496 Mon Sep 17 00:00:00 2001 From: esau <152162806+sklppy88@users.noreply.github.com> Date: Thu, 31 Oct 2024 07:47:44 +0000 Subject: [PATCH 15/21] feat: barebones addressbook for tagging (#9572) This is a barebones addressbook for us to be able to get our contacts to compute tags for them. We then search for these tags when querying for notes. --- .../aztec.js/src/wallet/base_wallet.ts | 9 ++++ .../circuit-types/src/interfaces/pxe.ts | 26 ++++++++- .../pxe/src/database/kv_pxe_database.ts | 53 ++++++++++++++----- yarn-project/pxe/src/database/pxe_database.ts | 20 +++++++ .../pxe/src/pxe_service/pxe_service.ts | 36 +++++++++++++ .../pxe/src/simulator_oracle/index.ts | 10 ++++ 6 files changed, 139 insertions(+), 15 deletions(-) diff --git a/yarn-project/aztec.js/src/wallet/base_wallet.ts b/yarn-project/aztec.js/src/wallet/base_wallet.ts index 2e1a43da978..afae5b2f813 100644 --- a/yarn-project/aztec.js/src/wallet/base_wallet.ts +++ b/yarn-project/aztec.js/src/wallet/base_wallet.ts @@ -87,6 +87,15 @@ export abstract class BaseWallet implements Wallet { getRegisteredAccount(address: AztecAddress): Promise { return this.pxe.getRegisteredAccount(address); } + registerContact(address: AztecAddress): Promise { + return this.pxe.registerContact(address); + } + getContacts(): Promise { + return this.pxe.getContacts(); + } + async removeContact(address: AztecAddress): Promise { + await this.pxe.removeContact(address); + } registerContract(contract: { /** Instance */ instance: ContractInstanceWithAddress; /** Associated artifact */ artifact?: ContractArtifact; diff --git a/yarn-project/circuit-types/src/interfaces/pxe.ts b/yarn-project/circuit-types/src/interfaces/pxe.ts index f380cfec734..373d1527a3f 100644 --- a/yarn-project/circuit-types/src/interfaces/pxe.ts +++ b/yarn-project/circuit-types/src/interfaces/pxe.ts @@ -88,6 +88,28 @@ export interface PXE { */ getRegisteredAccount(address: AztecAddress): Promise; + /** + * Registers a user contact in PXE. + * + * Once a new contact is registered, the PXE Service will be able to receive notes tagged from this contact. + * Will do nothing if the account is already registered. + * + * @param address - Address of the user to add to the address book + * @returns The address address of the account. + */ + registerContact(address: AztecAddress): Promise; + + /** + * Retrieves the addresses stored as contacts on this PXE Service. + * @returns An array of the contacts on this PXE Service. + */ + getContacts(): Promise; + + /** + * Removes a contact in the address book. + */ + removeContact(address: AztecAddress): Promise; + /** * Registers a contract class in the PXE without registering any associated contract instance with it. * @@ -328,7 +350,7 @@ export interface PXE { getSyncStats(): Promise<{ [key: string]: NoteProcessorStats }>; /** - * Returns a Contact Instance given its address, which includes the contract class identifier, + * Returns a Contract Instance given its address, which includes the contract class identifier, * initialization hash, deployment salt, and public keys hash. * TODO(@spalladino): Should we return the public keys in plain as well here? * @param address - Deployment address of the contract. @@ -336,7 +358,7 @@ export interface PXE { getContractInstance(address: AztecAddress): Promise; /** - * Returns a Contact Class given its identifier. + * Returns a Contract Class given its identifier. * TODO(@spalladino): The PXE actually holds artifacts and not classes, what should we return? Also, * should the pxe query the node for contract public info, and merge it with its own definitions? * @param id - Identifier of the class. diff --git a/yarn-project/pxe/src/database/kv_pxe_database.ts b/yarn-project/pxe/src/database/kv_pxe_database.ts index 6d84f65c567..0468c3c445d 100644 --- a/yarn-project/pxe/src/database/kv_pxe_database.ts +++ b/yarn-project/pxe/src/database/kv_pxe_database.ts @@ -32,8 +32,9 @@ import { type PxeDatabase } from './pxe_database.js'; */ export class KVPxeDatabase implements PxeDatabase { #synchronizedBlock: AztecSingleton; - #addresses: AztecArray; - #addressIndex: AztecMap; + #completeAddresses: AztecArray; + #completeAddressIndex: AztecMap; + #addressBook: AztecSet; #authWitnesses: AztecMap; #capsules: AztecArray; #notes: AztecMap; @@ -68,8 +69,10 @@ export class KVPxeDatabase implements PxeDatabase { constructor(private db: AztecKVStore) { this.#db = db; - this.#addresses = db.openArray('addresses'); - this.#addressIndex = db.openMap('address_index'); + this.#completeAddresses = db.openArray('complete_addresses'); + this.#completeAddressIndex = db.openMap('complete_address_index'); + + this.#addressBook = db.openSet('address_book'); this.#authWitnesses = db.openMap('auth_witnesses'); this.#capsules = db.openArray('capsules'); @@ -505,15 +508,15 @@ export class KVPxeDatabase implements PxeDatabase { return this.#db.transaction(() => { const addressString = completeAddress.address.toString(); const buffer = completeAddress.toBuffer(); - const existing = this.#addressIndex.get(addressString); + const existing = this.#completeAddressIndex.get(addressString); if (typeof existing === 'undefined') { - const index = this.#addresses.length; - void this.#addresses.push(buffer); - void this.#addressIndex.set(addressString, index); + const index = this.#completeAddresses.length; + void this.#completeAddresses.push(buffer); + void this.#completeAddressIndex.set(addressString, index); return true; } else { - const existingBuffer = this.#addresses.at(existing); + const existingBuffer = this.#completeAddresses.at(existing); if (existingBuffer?.equals(buffer)) { return false; @@ -527,12 +530,12 @@ export class KVPxeDatabase implements PxeDatabase { } #getCompleteAddress(address: AztecAddress): CompleteAddress | undefined { - const index = this.#addressIndex.get(address.toString()); + const index = this.#completeAddressIndex.get(address.toString()); if (typeof index === 'undefined') { return undefined; } - const value = this.#addresses.at(index); + const value = this.#completeAddresses.at(index); return value ? CompleteAddress.fromBuffer(value) : undefined; } @@ -541,7 +544,31 @@ export class KVPxeDatabase implements PxeDatabase { } getCompleteAddresses(): Promise { - return Promise.resolve(Array.from(this.#addresses).map(v => CompleteAddress.fromBuffer(v))); + return Promise.resolve(Array.from(this.#completeAddresses).map(v => CompleteAddress.fromBuffer(v))); + } + + async addContactAddress(address: AztecAddress): Promise { + if (this.#addressBook.has(address.toString())) { + return false; + } + + await this.#addressBook.add(address.toString()); + + return true; + } + + getContactAddresses(): AztecAddress[] { + return [...this.#addressBook.entries()].map(AztecAddress.fromString); + } + + async removeContactAddress(address: AztecAddress): Promise { + if (!this.#addressBook.has(address.toString())) { + return false; + } + + await this.#addressBook.delete(address.toString()); + + return true; } getSynchedBlockNumberForAccount(account: AztecAddress): number | undefined { @@ -566,7 +593,7 @@ export class KVPxeDatabase implements PxeDatabase { (sum, value) => sum + value.length * Fr.SIZE_IN_BYTES, 0, ); - const addressesSize = this.#addresses.length * CompleteAddress.SIZE_IN_BYTES; + const addressesSize = this.#completeAddresses.length * CompleteAddress.SIZE_IN_BYTES; const treeRootsSize = Object.keys(MerkleTreeId).length * Fr.SIZE_IN_BYTES; return incomingNotesSize + outgoingNotesSize + treeRootsSize + authWitsSize + addressesSize; diff --git a/yarn-project/pxe/src/database/pxe_database.ts b/yarn-project/pxe/src/database/pxe_database.ts index 961301501de..d6758e233d8 100644 --- a/yarn-project/pxe/src/database/pxe_database.ts +++ b/yarn-project/pxe/src/database/pxe_database.ts @@ -146,6 +146,26 @@ export interface PxeDatabase extends ContractArtifactDatabase, ContractInstanceD */ setHeader(header: Header): Promise; + /** + * Adds contact address to the database. + * @param address - The address to add to the address book. + * @returns A promise resolving to true if the address was added, false if it already exists. + */ + addContactAddress(address: AztecAddress): Promise; + + /** + * Retrieves the list of contact addresses in the address book. + * @returns An array of Aztec addresses. + */ + getContactAddresses(): AztecAddress[]; + + /** + * Removes a contact address from the database. + * @param address - The address to remove from the address book. + * @returns A promise resolving to true if the address was removed, false if it does not exist. + */ + removeContactAddress(address: AztecAddress): Promise; + /** * Adds complete address to the database. * @param address - The complete address to add. diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index 4504c8d4503..31490047558 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -205,6 +205,42 @@ export class PXEService implements PXE { return accountCompleteAddress; } + public async registerContact(address: AztecAddress): Promise { + const accounts = await this.keyStore.getAccounts(); + if (accounts.includes(address)) { + this.log.info(`Account:\n "${address.toString()}"\n already registered.`); + return address; + } + + const wasAdded = await this.db.addContactAddress(address); + + if (wasAdded) { + this.log.info(`Added contact:\n ${address.toString()}`); + } else { + this.log.info(`Contact:\n "${address.toString()}"\n already registered.`); + } + + return address; + } + + public getContacts(): Promise { + const contacts = this.db.getContactAddresses(); + + return Promise.resolve(contacts); + } + + public async removeContact(address: AztecAddress): Promise { + const wasRemoved = await this.db.removeContactAddress(address); + + if (wasRemoved) { + this.log.info(`Removed contact:\n ${address.toString()}`); + } else { + this.log.info(`Contact:\n "${address.toString()}"\n not in address book.`); + } + + return Promise.resolve(); + } + public async getRegisteredAccounts(): Promise { // Get complete addresses of both the recipients and the accounts const completeAddresses = await this.db.getCompleteAddresses(); diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 9f4b278ecba..b67a5dd2da1 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -232,6 +232,16 @@ export class SimulatorOracle implements DBOracle { return this.contractDataOracle.getDebugFunctionName(contractAddress, selector); } + /** + * Returns the full contents of your address book. + * This is used when calculating tags for incoming notes by deriving the shared secret, the contract-siloed tagging secret, and + * finally the index specified tag. We will then query the node with this tag for each address in the address book. + * @returns The full list of the users contact addresses. + */ + public getContacts(): AztecAddress[] { + return this.db.getContactAddresses(); + } + /** * Returns the tagging secret for a given sender and recipient pair. For this to work, the ivpsk_m of the sender must be known. * Includes the last known index used for tagging with this secret. From 8ce6834ddcfe48aa672632012c48d5d679c8687c Mon Sep 17 00:00:00 2001 From: esau <152162806+sklppy88@users.noreply.github.com> Date: Thu, 31 Oct 2024 11:50:36 +0000 Subject: [PATCH 16/21] refactor: add sender to encode and encrypt (#9562) This PR is purely plumbing, as we add a sender to encode_and_encrypt, changing the API because in the future we will require the sender as a param to request the shared secret from the PXE that will be used to compute the tag. We are using a placeholder of `msg_sender` and trying to infer from context who the sender should be whenever possible. For the fee / partial note stuff, it may be good to have a better understanding who the sender is. Finally, some patterns arise, namely that we should probably add self as an implicit member of the address book :D. To note: this pollutes the API even more, and is beyond the standard of sane. as discussed on Slack, we should abstract this pollution wherever possible. --- boxes/boxes/react/src/contracts/src/main.nr | 4 +-- boxes/boxes/vanilla/src/contracts/src/main.nr | 4 +-- .../encrypted_event_emission.nr | 33 ++++++++++++++----- .../encrypted_logs/encrypted_note_emission.nr | 30 ++++++++++++----- .../aztec/src/encrypted_logs/payload.nr | 6 ++++ .../aztec-nr/aztec/src/macros/notes/mod.nr | 3 +- .../src/easy_private_uint.nr | 18 ++++++++-- .../aztec-nr/value-note/src/utils.nr | 8 +++-- .../app_subscription_contract/src/main.nr | 8 ++++- .../benchmarking_contract/src/main.nr | 16 +++++++-- .../contracts/card_game_contract/src/cards.nr | 1 + .../contracts/child_contract/src/main.nr | 1 + .../contracts/counter_contract/src/main.nr | 4 +-- .../crowdfunding_contract/src/main.nr | 1 + .../docs_example_contract/src/main.nr | 6 ++++ .../easy_private_token_contract/src/main.nr | 8 ++--- .../ecdsa_k_account_contract/src/main.nr | 1 + .../ecdsa_r_account_contract/src/main.nr | 1 + .../contracts/escrow_contract/src/main.nr | 1 + .../inclusion_proofs_contract/src/main.nr | 1 + .../contracts/nft_contract/src/main.nr | 4 ++- .../pending_note_hashes_contract/src/main.nr | 20 +++++++++-- .../schnorr_account_contract/src/main.nr | 1 + .../contracts/spam_contract/src/main.nr | 2 +- .../stateful_test_contract/src/main.nr | 12 +++---- .../static_child_contract/src/main.nr | 2 ++ .../contracts/test_contract/src/main.nr | 4 +++ .../contracts/test_log_contract/src/main.nr | 3 ++ .../token_blacklist_contract/src/main.nr | 13 ++++++-- .../contracts/token_contract/src/main.nr | 22 +++++++++---- 30 files changed, 183 insertions(+), 55 deletions(-) diff --git a/boxes/boxes/react/src/contracts/src/main.nr b/boxes/boxes/react/src/contracts/src/main.nr index ed7cb86ea80..f4924981e8e 100644 --- a/boxes/boxes/react/src/contracts/src/main.nr +++ b/boxes/boxes/react/src/contracts/src/main.nr @@ -26,7 +26,7 @@ contract BoxReact { let mut new_number = ValueNote::new(number, owner); let owner_ovpk_m = get_public_keys(owner).ovpk_m; - numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_note(&mut context, owner_ovpk_m, owner)); + numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_note(&mut context, owner_ovpk_m, owner, context.msg_sender())); } #[private] @@ -38,7 +38,7 @@ contract BoxReact { let mut new_number = ValueNote::new(number, owner); let owner_ovpk_m = get_public_keys(owner).ovpk_m; - numbers.at(owner).replace(&mut new_number).emit(encode_and_encrypt_note(&mut context, owner_ovpk_m, owner)); + numbers.at(owner).replace(&mut new_number).emit(encode_and_encrypt_note(&mut context, owner_ovpk_m, owner, context.msg_sender())); } unconstrained fn getNumber(owner: AztecAddress) -> pub ValueNote { diff --git a/boxes/boxes/vanilla/src/contracts/src/main.nr b/boxes/boxes/vanilla/src/contracts/src/main.nr index 98c205b85fd..c27e8f81a74 100644 --- a/boxes/boxes/vanilla/src/contracts/src/main.nr +++ b/boxes/boxes/vanilla/src/contracts/src/main.nr @@ -26,7 +26,7 @@ contract Vanilla { let mut new_number = ValueNote::new(number, owner); let owner_ovpk_m = get_public_keys(owner).ovpk_m; - numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_note(&mut context, owner_ovpk_m, owner)); + numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_note(&mut context, owner_ovpk_m, owner, context.msg_sender())); } #[private] @@ -38,7 +38,7 @@ contract Vanilla { let mut new_number = ValueNote::new(number, owner); let owner_ovpk_m = get_public_keys(owner).ovpk_m; - numbers.at(owner).replace(&mut new_number).emit(encode_and_encrypt_note(&mut context, owner_ovpk_m, owner)); + numbers.at(owner).replace(&mut new_number).emit(encode_and_encrypt_note(&mut context, owner_ovpk_m, owner, context.msg_sender())); } unconstrained fn getNumber(owner: AztecAddress) -> pub ValueNote { diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_event_emission.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_event_emission.nr index 967bae1b570..920ef2ee5cc 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_event_emission.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_event_emission.nr @@ -12,6 +12,7 @@ fn compute_payload_and_hash( ovsk_app: Field, ovpk: OvpkM, recipient: AztecAddress, + sender: AztecAddress, ) -> ([u8; 384 + N * 32], Field) where Event: EventInterface, @@ -25,6 +26,7 @@ where ovsk_app, ovpk, recipient, + sender, plaintext, false, ); @@ -38,19 +40,29 @@ unconstrained fn compute_payload_and_hash_unconstrained( randomness: Field, ovpk: OvpkM, recipient: AztecAddress, + sender: AztecAddress, ) -> ([u8; 384 + N * 32], Field) where Event: EventInterface, { let ovsk_app = get_ovsk_app(ovpk.hash()); - compute_payload_and_hash(context, event, randomness, ovsk_app, ovpk, recipient) + compute_payload_and_hash( + context, + event, + randomness, + ovsk_app, + ovpk, + recipient, + sender, + ) } pub fn encode_and_encrypt_event( context: &mut PrivateContext, ovpk: OvpkM, recipient: AztecAddress, -) -> fn[(&mut PrivateContext, OvpkM, AztecAddress)](Event) -> () + sender: AztecAddress, +) -> fn[(&mut PrivateContext, OvpkM, AztecAddress, AztecAddress)](Event) -> () where Event: EventInterface, { @@ -62,7 +74,7 @@ where let randomness = unsafe { random() }; let ovsk_app: Field = context.request_ovsk_app(ovpk.hash()); let (encrypted_log, log_hash) = - compute_payload_and_hash(*context, e, randomness, ovsk_app, ovpk, recipient); + compute_payload_and_hash(*context, e, randomness, ovsk_app, ovpk, recipient, sender); context.emit_raw_event_log_with_masked_address(randomness, encrypted_log, log_hash); } } @@ -71,7 +83,8 @@ pub fn encode_and_encrypt_event_unconstrained( context: &mut PrivateContext, ovpk: OvpkM, recipient: AztecAddress, -) -> fn[(&mut PrivateContext, OvpkM, AztecAddress)](Event) -> () + sender: AztecAddress, +) -> fn[(&mut PrivateContext, OvpkM, AztecAddress, AztecAddress)](Event) -> () where Event: EventInterface, { @@ -82,7 +95,7 @@ where // value generation. let randomness = unsafe { random() }; let (encrypted_log, log_hash) = unsafe { - compute_payload_and_hash_unconstrained(*context, e, randomness, ovpk, recipient) + compute_payload_and_hash_unconstrained(*context, e, randomness, ovpk, recipient, sender) }; context.emit_raw_event_log_with_masked_address(randomness, encrypted_log, log_hash); } @@ -96,14 +109,15 @@ pub fn encode_and_encrypt_event_with_randomness( randomness: Field, ovpk: OvpkM, recipient: AztecAddress, -) -> fn[(&mut PrivateContext, OvpkM, Field, AztecAddress)](Event) -> () + sender: AztecAddress, +) -> fn[(&mut PrivateContext, OvpkM, Field, AztecAddress, AztecAddress)](Event) -> () where Event: EventInterface, { |e: Event| { let ovsk_app: Field = context.request_ovsk_app(ovpk.hash()); let (encrypted_log, log_hash) = - compute_payload_and_hash(*context, e, randomness, ovsk_app, ovpk, recipient); + compute_payload_and_hash(*context, e, randomness, ovsk_app, ovpk, recipient, sender); context.emit_raw_event_log_with_masked_address(randomness, encrypted_log, log_hash); } } @@ -113,7 +127,8 @@ pub fn encode_and_encrypt_event_with_randomness_unconstrained randomness: Field, ovpk: OvpkM, recipient: AztecAddress, -) -> fn[(&mut PrivateContext, Field, OvpkM, AztecAddress)](Event) -> () + sender: AztecAddress, +) -> fn[(&mut PrivateContext, Field, OvpkM, AztecAddress, AztecAddress)](Event) -> () where Event: EventInterface, { @@ -133,7 +148,7 @@ where // return the log from this function to the app, otherwise it could try to do stuff with it and then that might // be wrong. let (encrypted_log, log_hash) = unsafe { - compute_payload_and_hash_unconstrained(*context, e, randomness, ovpk, recipient) + compute_payload_and_hash_unconstrained(*context, e, randomness, ovpk, recipient, sender) }; context.emit_raw_event_log_with_masked_address(randomness, encrypted_log, log_hash); } diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_note_emission.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_note_emission.nr index 0c280a9a257..0faf7cf2ef0 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_note_emission.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_note_emission.nr @@ -15,6 +15,7 @@ fn compute_payload_and_hash( ovsk_app: Field, ovpk: OvpkM, recipient: AztecAddress, + sender: AztecAddress, ) -> (u32, [u8; 385 + N * 32], Field) where Note: NoteInterface, @@ -32,8 +33,15 @@ where let plaintext = note.to_be_bytes(storage_slot); // For note logs we always include public values prefix - let encrypted_log: [u8; 385 + N * 32] = - compute_private_log_payload(contract_address, ovsk_app, ovpk, recipient, plaintext, true); + let encrypted_log: [u8; 385 + N * 32] = compute_private_log_payload( + contract_address, + ovsk_app, + ovpk, + recipient, + sender, + plaintext, + true, + ); let log_hash = sha256_to_field(encrypted_log); (note_hash_counter, encrypted_log, log_hash) @@ -44,12 +52,13 @@ unconstrained fn compute_payload_and_hash_unconstrained( note: Note, ovpk: OvpkM, recipient: AztecAddress, + sender: AztecAddress, ) -> (u32, [u8; 385 + N * 32], Field) where Note: NoteInterface, { let ovsk_app = get_ovsk_app(ovpk.hash()); - compute_payload_and_hash(context, note, ovsk_app, ovpk, recipient) + compute_payload_and_hash(context, note, ovsk_app, ovpk, recipient, sender) } // This function seems to be affected by the following Noir bug: @@ -59,7 +68,9 @@ pub fn encode_and_encrypt_note( context: &mut PrivateContext, ovpk: OvpkM, recipient: AztecAddress, -) -> fn[(&mut PrivateContext, OvpkM, AztecAddress)](NoteEmission) -> () + // TODO: We need this because to compute a tagging secret, we require a sender. Should we have the tagging secret oracle take a ovpk_m as input instead of the address? + sender: AztecAddress, +) -> fn[(&mut PrivateContext, OvpkM, AztecAddress, AztecAddress)](NoteEmission) -> () where Note: NoteInterface, { @@ -67,7 +78,7 @@ where let ovsk_app: Field = context.request_ovsk_app(ovpk.hash()); let (note_hash_counter, encrypted_log, log_hash) = - compute_payload_and_hash(*context, e.note, ovsk_app, ovpk, recipient); + compute_payload_and_hash(*context, e.note, ovsk_app, ovpk, recipient, sender); context.emit_raw_note_log(note_hash_counter, encrypted_log, log_hash); } } @@ -76,7 +87,9 @@ pub fn encode_and_encrypt_note_unconstrained( context: &mut PrivateContext, ovpk: OvpkM, recipient: AztecAddress, -) -> fn[(&mut PrivateContext, OvpkM, AztecAddress)](NoteEmission) -> () + // TODO: We need this because to compute a tagging secret, we require a sender. Should we have the tagging secret oracle take a ovpk_m as input instead of the address? + sender: AztecAddress, +) -> fn[(&mut PrivateContext, OvpkM, AztecAddress, AztecAddress)](NoteEmission) -> () where Note: NoteInterface, { @@ -100,8 +113,9 @@ where // for the log to be deleted when it shouldn't have (which is fine - they can already make the content be // whatever), or cause for the log to not be deleted when it should have (which is also fine - it'll be a log // for a note that doesn't exist). - let (note_hash_counter, encrypted_log, log_hash) = - unsafe { compute_payload_and_hash_unconstrained(*context, e.note, ovpk, recipient) }; + let (note_hash_counter, encrypted_log, log_hash) = unsafe { + compute_payload_and_hash_unconstrained(*context, e.note, ovpk, recipient, sender) + }; context.emit_raw_note_log(note_hash_counter, encrypted_log, log_hash); } } diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr index 27bdc617044..8e9d0001910 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr @@ -19,6 +19,7 @@ fn compute_private_log_payload( ovsk_app: Field, ovpk: OvpkM, recipient: AztecAddress, + sender: AztecAddress, plaintext: [u8; P], include_public_values_prefix: bool, ) -> [u8; M] { @@ -206,11 +207,16 @@ mod test { 0x25afb798ea6d0b8c1618e50fdeafa463059415013d3b7c75d46abf5e242be70c, ); + let sender = AztecAddress::from_field( + 0x25afb798ea6d0b8c1618e50fdeafa463059415013d3b7c75d46abf5e242be70c, + ); + let log = compute_private_log_payload( contract_address, ovsk_app, ovpk_m, recipient, + sender, plaintext, false, ); diff --git a/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr index 05fde8cfe25..3e9c6ce2347 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr @@ -463,7 +463,7 @@ comptime fn generate_setup_payload( } } - fn encrypt_log(self, context: &mut PrivateContext, ovpk: aztec::protocol_types::public_keys::OvpkM, recipient: aztec::protocol_types::address::AztecAddress) -> [Field; $encrypted_log_field_length] { + fn encrypt_log(self, context: &mut PrivateContext, ovpk: aztec::protocol_types::public_keys::OvpkM, recipient: aztec::protocol_types::address::AztecAddress, sender: aztec::protocol_types::address::AztecAddress) -> [Field; $encrypted_log_field_length] { let ovsk_app: Field = context.request_ovsk_app(ovpk.hash()); let encrypted_log_bytes: [u8; $encrypted_log_byte_length] = aztec::encrypted_logs::payload::compute_private_log_payload( @@ -471,6 +471,7 @@ comptime fn generate_setup_payload( ovsk_app, ovpk, recipient, + sender, self.log_plaintext, true ); diff --git a/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr b/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr index dbbaebb2859..d42e7c6d7ef 100644 --- a/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr +++ b/noir-projects/aztec-nr/easy-private-state/src/easy_private_uint.nr @@ -22,7 +22,13 @@ impl EasyPrivateUint { impl EasyPrivateUint<&mut PrivateContext> { // Very similar to `value_note::utils::increment`. - pub fn add(self, addend: u64, owner: AztecAddress, outgoing_viewer: AztecAddress) { + pub fn add( + self, + addend: u64, + owner: AztecAddress, + outgoing_viewer: AztecAddress, + sender: AztecAddress, + ) { let outgoing_viewer_keys = get_public_keys(outgoing_viewer); // Creates new note for the owner. let mut addend_note = ValueNote::new(addend as Field, owner); @@ -33,12 +39,19 @@ impl EasyPrivateUint<&mut PrivateContext> { self.context, outgoing_viewer_keys.ovpk_m, owner, + sender, )); // docs:end:insert } // Very similar to `value_note::utils::decrement`. - pub fn sub(self, subtrahend: u64, owner: AztecAddress, outgoing_viewer: AztecAddress) { + pub fn sub( + self, + subtrahend: u64, + owner: AztecAddress, + outgoing_viewer: AztecAddress, + sender: AztecAddress, + ) { let outgoing_viewer_keys = get_public_keys(outgoing_viewer); // docs:start:pop_notes @@ -63,6 +76,7 @@ impl EasyPrivateUint<&mut PrivateContext> { self.context, outgoing_viewer_keys.ovpk_m, owner, + sender, )); } } diff --git a/noir-projects/aztec-nr/value-note/src/utils.nr b/noir-projects/aztec-nr/value-note/src/utils.nr index fd7bb7c41c8..06eaca64a06 100644 --- a/noir-projects/aztec-nr/value-note/src/utils.nr +++ b/noir-projects/aztec-nr/value-note/src/utils.nr @@ -23,6 +23,7 @@ pub fn increment( amount: Field, recipient: AztecAddress, outgoing_viewer: AztecAddress, // docs:end:increment_args + sender: AztecAddress, ) { let outgoing_viewer_ovpk_m = get_public_keys(outgoing_viewer).ovpk_m; @@ -32,6 +33,7 @@ pub fn increment( balance.context, outgoing_viewer_ovpk_m, recipient, + sender, )); } @@ -44,8 +46,9 @@ pub fn decrement( amount: Field, owner: AztecAddress, outgoing_viewer: AztecAddress, + sender: AztecAddress, ) { - let sum = decrement_by_at_most(balance, amount, owner, outgoing_viewer); + let sum = decrement_by_at_most(balance, amount, owner, outgoing_viewer, sender); assert(sum == amount, "Balance too low"); } @@ -62,6 +65,7 @@ pub fn decrement_by_at_most( max_amount: Field, owner: AztecAddress, outgoing_viewer: AztecAddress, + sender: AztecAddress, ) -> Field { let options = create_note_getter_options_for_decreasing_balance(max_amount); let notes = balance.pop_notes(options); @@ -80,7 +84,7 @@ pub fn decrement_by_at_most( change_value = decremented - max_amount; decremented -= change_value; } - increment(balance, change_value, owner, outgoing_viewer); + increment(balance, change_value, owner, outgoing_viewer, sender); decremented } diff --git a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr index 2e9f3e9299d..fd11c9a9c5d 100644 --- a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr @@ -53,6 +53,7 @@ contract AppSubscription { &mut context, keys.ovpk_m, user_address, + user_address, )); context.set_as_fee_payer(); @@ -116,7 +117,12 @@ contract AppSubscription { let mut subscription_note = SubscriptionNote::new(subscriber, expiry_block_number, tx_count); storage.subscriptions.at(subscriber).initialize_or_replace(&mut subscription_note).emit( - encode_and_encrypt_note(&mut context, msg_sender_ovpk_m, subscriber), + encode_and_encrypt_note( + &mut context, + msg_sender_ovpk_m, + subscriber, + context.msg_sender(), + ), ); } diff --git a/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr b/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr index 436617354af..e04c1c2ffb9 100644 --- a/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr @@ -22,7 +22,13 @@ contract Benchmarking { #[private] fn create_note(owner: AztecAddress, outgoing_viewer: AztecAddress, value: Field) { // docs:start:increment_valuenote - increment(storage.notes.at(owner), value, owner, outgoing_viewer); + increment( + storage.notes.at(owner), + value, + owner, + outgoing_viewer, + outgoing_viewer, + ); // docs:end:increment_valuenote } // Deletes a note at a specific index in the set and creates a new one with the same value. @@ -36,7 +42,13 @@ contract Benchmarking { let mut getter_options = NoteGetterOptions::new(); let notes = owner_notes.pop_notes(getter_options.set_limit(1).set_offset(index)); let note = notes.get(0); - increment(owner_notes, note.value, owner, outgoing_viewer); + increment( + owner_notes, + note.value, + owner, + outgoing_viewer, + outgoing_viewer, + ); } // Reads and writes to public storage and enqueues a call to another public function. diff --git a/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr b/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr index 2cc91c195db..34edfa95d37 100644 --- a/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr +++ b/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr @@ -118,6 +118,7 @@ impl Deck<&mut PrivateContext> { self.set.context, msg_sender_ovpk_m, owner, + self.set.context.msg_sender(), )); inserted_cards = inserted_cards.push_back(card_note); } diff --git a/noir-projects/noir-contracts/contracts/child_contract/src/main.nr b/noir-projects/noir-contracts/contracts/child_contract/src/main.nr index a07237b535a..9a995e47d95 100644 --- a/noir-projects/noir-contracts/contracts/child_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/child_contract/src/main.nr @@ -63,6 +63,7 @@ contract Child { &mut context, owner_ovpk_m, owner, + context.msg_sender(), )); new_value } diff --git a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr index 965de3140a5..c7dbedbfca0 100644 --- a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr @@ -24,7 +24,7 @@ contract Counter { // We can name our initializer anything we want as long as it's marked as aztec(initializer) fn initialize(headstart: u64, owner: AztecAddress, outgoing_viewer: AztecAddress) { let counters = storage.counters; - counters.at(owner).add(headstart, owner, outgoing_viewer); + counters.at(owner).add(headstart, owner, outgoing_viewer, context.msg_sender()); } // docs:end:constructor @@ -38,7 +38,7 @@ contract Counter { ); } let counters = storage.counters; - counters.at(owner).add(1, owner, outgoing_viewer); + counters.at(owner).add(1, owner, outgoing_viewer, context.msg_sender()); } // docs:end:increment // docs:start:get_counter diff --git a/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr b/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr index d59d4c89e0c..92e89729e74 100644 --- a/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr @@ -91,6 +91,7 @@ contract Crowdfunding { &mut context, donor_ovpk_m, donor, + donor, )); } // docs:end:donate diff --git a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr index 9422490654b..dada8fd336b 100644 --- a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr @@ -181,6 +181,7 @@ contract DocsExample { &mut context, msg_sender_ovpk_m, context.msg_sender(), + context.msg_sender(), )); } // docs:end:initialize-private-mutable @@ -196,6 +197,7 @@ contract DocsExample { &mut context, msg_sender_ovpk_m, context.msg_sender(), + context.msg_sender(), )); } @@ -209,6 +211,7 @@ contract DocsExample { &mut context, msg_sender_ovpk_m, context.msg_sender(), + context.msg_sender(), )); } } @@ -221,6 +224,7 @@ contract DocsExample { &mut context, msg_sender_ovpk_m, context.msg_sender(), + context.msg_sender(), )); } // docs:start:state_vars-NoteGetterOptionsComparatorExampleNoir @@ -238,6 +242,7 @@ contract DocsExample { &mut context, msg_sender_ovpk_m, context.msg_sender(), + context.msg_sender(), )); DocsExample::at(context.this_address()).update_leader(context.msg_sender(), points).enqueue( &mut context, @@ -259,6 +264,7 @@ contract DocsExample { &mut context, msg_sender_ovpk_m, context.msg_sender(), + context.msg_sender(), )); // docs:end:state_vars-PrivateMutableReplace DocsExample::at(context.this_address()).update_leader(context.msg_sender(), points).enqueue( diff --git a/noir-projects/noir-contracts/contracts/easy_private_token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/easy_private_token_contract/src/main.nr index 0544a592f73..8abf2a3be45 100644 --- a/noir-projects/noir-contracts/contracts/easy_private_token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/easy_private_token_contract/src/main.nr @@ -21,7 +21,7 @@ contract EasyPrivateToken { fn constructor(initial_supply: u64, owner: AztecAddress, outgoing_viewer: AztecAddress) { let balances = storage.balances; - balances.at(owner).add(initial_supply, owner, outgoing_viewer); + balances.at(owner).add(initial_supply, owner, outgoing_viewer, context.msg_sender()); } // Mints `amount` of tokens to `owner`. @@ -29,7 +29,7 @@ contract EasyPrivateToken { fn mint(amount: u64, owner: AztecAddress, outgoing_viewer: AztecAddress) { let balances = storage.balances; - balances.at(owner).add(amount, owner, outgoing_viewer); + balances.at(owner).add(amount, owner, outgoing_viewer, context.msg_sender()); } // Transfers `amount` of tokens from `sender` to a `recipient`. @@ -42,8 +42,8 @@ contract EasyPrivateToken { ) { let balances = storage.balances; - balances.at(sender).sub(amount, sender, outgoing_viewer); - balances.at(recipient).add(amount, recipient, outgoing_viewer); + balances.at(sender).sub(amount, sender, outgoing_viewer, sender); + balances.at(recipient).add(amount, recipient, outgoing_viewer, sender); } // Helper function to get the balance of a user ("unconstrained" is a Noir alternative of Solidity's "view" function). diff --git a/noir-projects/noir-contracts/contracts/ecdsa_k_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/ecdsa_k_account_contract/src/main.nr index 8f2dc055f33..5a486330c9b 100644 --- a/noir-projects/noir-contracts/contracts/ecdsa_k_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/ecdsa_k_account_contract/src/main.nr @@ -39,6 +39,7 @@ contract EcdsaKAccount { &mut context, this_ovpk_m, this, + this, )); } diff --git a/noir-projects/noir-contracts/contracts/ecdsa_r_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/ecdsa_r_account_contract/src/main.nr index b8e2e18b08a..94e86352fda 100644 --- a/noir-projects/noir-contracts/contracts/ecdsa_r_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/ecdsa_r_account_contract/src/main.nr @@ -37,6 +37,7 @@ contract EcdsaRAccount { &mut context, this_ovpk_m, this, + this, )); } diff --git a/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr b/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr index b5438144c2a..4aa3759433a 100644 --- a/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr @@ -32,6 +32,7 @@ contract Escrow { &mut context, msg_sender_ovpk_m, owner, + context.msg_sender(), )); } diff --git a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr index cdb13bbe4a6..8be4ea12343 100644 --- a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr @@ -40,6 +40,7 @@ contract InclusionProofs { &mut context, msg_sender_ovpk_m, owner, + context.msg_sender(), )); } // docs:end:create_note diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr index 114b7532ab9..401e5b015ed 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr @@ -188,7 +188,8 @@ contract NFT { // We set the ovpk to the message sender's ovpk and we encrypt the log. let from_ovpk = get_public_keys(context.msg_sender()).ovpk_m; - let setup_log = note_setup_payload.encrypt_log(context, from_ovpk, to); + let setup_log = + note_setup_payload.encrypt_log(context, from_ovpk, to, context.msg_sender()); // Using the x-coordinate as a hiding point slot is safe against someone else interfering with it because // we have a guarantee that the public functions of the transaction are executed right after the private ones @@ -307,6 +308,7 @@ contract NFT { &mut context, from_ovpk_m, to, + from, )); } diff --git a/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr b/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr index 5858192c8d8..f1211f87b3b 100644 --- a/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr @@ -47,6 +47,7 @@ contract PendingNoteHashes { &mut context, outgoing_viewer_ovpk_m, owner, + context.msg_sender(), )); let options = NoteGetterOptions::with_filter(filter_notes_min_sum, amount); @@ -96,6 +97,7 @@ contract PendingNoteHashes { &mut context, outgoing_viewer_ovpk_m, owner, + context.msg_sender(), )); } @@ -120,6 +122,7 @@ contract PendingNoteHashes { &mut context, outgoing_viewer_ovpk_m, owner, + context.msg_sender(), )); } @@ -136,10 +139,20 @@ contract PendingNoteHashes { // Insert note let emission = owner_balance.insert(&mut note); - emission.emit(encode_and_encrypt_note(&mut context, outgoing_viewer_ovpk_m, owner)); + emission.emit(encode_and_encrypt_note( + &mut context, + outgoing_viewer_ovpk_m, + owner, + context.msg_sender(), + )); // Emit note again - emission.emit(encode_and_encrypt_note(&mut context, outgoing_viewer_ovpk_m, owner)); + emission.emit(encode_and_encrypt_note( + &mut context, + outgoing_viewer_ovpk_m, + owner, + context.msg_sender(), + )); } // Nested/inner function to get a note and confirm it matches the expected value @@ -359,6 +372,7 @@ contract PendingNoteHashes { &mut context, outgoing_viewer_ovpk_m, owner, + context.msg_sender(), )); // We will emit a note log with an incorrect preimage to ensure the pxe throws @@ -372,6 +386,7 @@ contract PendingNoteHashes { &mut context, outgoing_viewer_ovpk_m, owner, + context.msg_sender(), )); } @@ -392,6 +407,7 @@ contract PendingNoteHashes { context, outgoing_viewer_ovpk_m, owner, + context.msg_sender(), )); } } diff --git a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr index 325932f6206..84379b702a1 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr @@ -44,6 +44,7 @@ contract SchnorrAccount { &mut context, this_ovpk_m, this, + this, )); } diff --git a/noir-projects/noir-contracts/contracts/spam_contract/src/main.nr b/noir-projects/noir-contracts/contracts/spam_contract/src/main.nr index b1bfe78f565..3523cc6e3f7 100644 --- a/noir-projects/noir-contracts/contracts/spam_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/spam_contract/src/main.nr @@ -36,7 +36,7 @@ contract Spam { for _ in 0..MAX_NOTE_HASHES_PER_CALL { storage.balances.at(caller).add(caller, U128::from_integer(amount)).emit( - encode_and_encrypt_note_unconstrained(&mut context, caller_ovpk_m, caller), + encode_and_encrypt_note_unconstrained(&mut context, caller_ovpk_m, caller, caller), ); } diff --git a/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr index 958386d0803..776e6bb0641 100644 --- a/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr @@ -45,7 +45,7 @@ contract StatefulTest { fn create_note(owner: AztecAddress, outgoing_viewer: AztecAddress, value: Field) { if (value != 0) { let loc = storage.notes.at(owner); - increment(loc, value, owner, outgoing_viewer); + increment(loc, value, owner, outgoing_viewer, context.msg_sender()); } } @@ -54,7 +54,7 @@ contract StatefulTest { fn create_note_no_init_check(owner: AztecAddress, outgoing_viewer: AztecAddress, value: Field) { if (value != 0) { let loc = storage.notes.at(owner); - increment(loc, value, owner, outgoing_viewer); + increment(loc, value, owner, outgoing_viewer, context.msg_sender()); } } @@ -64,10 +64,10 @@ contract StatefulTest { let sender = context.msg_sender(); let sender_notes = storage.notes.at(sender); - decrement(sender_notes, amount, sender, context.msg_sender()); + decrement(sender_notes, amount, sender, sender, sender); let recipient_notes = storage.notes.at(recipient); - increment(recipient_notes, amount, recipient, context.msg_sender()); + increment(recipient_notes, amount, recipient, sender, sender); } #[private] @@ -76,10 +76,10 @@ contract StatefulTest { let sender = context.msg_sender(); let sender_notes = storage.notes.at(sender); - decrement(sender_notes, amount, sender, context.msg_sender()); + decrement(sender_notes, amount, sender, sender, sender); let recipient_notes = storage.notes.at(recipient); - increment(recipient_notes, amount, recipient, context.msg_sender()); + increment(recipient_notes, amount, recipient, sender, sender); } #[public] diff --git a/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr b/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr index 418c6a9c281..b45d67c5126 100644 --- a/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr @@ -52,6 +52,7 @@ contract StaticChild { &mut context, msg_sender_ovpk_m, owner, + context.msg_sender(), )); new_value } @@ -70,6 +71,7 @@ contract StaticChild { &mut context, outgoing_viewer_ovpk_m, owner, + context.msg_sender(), )); new_value } diff --git a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr index f6565b2a80f..64b0c6a8fe3 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr @@ -109,6 +109,7 @@ contract Test { &mut context, outgoing_viewer_ovpk_m, owner, + context.msg_sender(), )); } @@ -303,6 +304,7 @@ contract Test { 5, outgoing_viewer_ovpk_m, owner, + outgoing_viewer, )); // this contract has reached max number of functions, so using this one fn @@ -320,6 +322,7 @@ contract Test { 0, outgoing_viewer_ovpk_m, owner, + outgoing_viewer, )); } } @@ -343,6 +346,7 @@ contract Test { &mut context, msg_sender_ovpk_m, owner, + context.msg_sender(), )); storage_slot += 1; Test::at(context.this_address()) diff --git a/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr index 8c3f65d38fd..19e5c109243 100644 --- a/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr @@ -48,6 +48,7 @@ contract TestLog { // outgoing is set to other, incoming is set to msg sender other_ovpk_m, context.msg_sender(), + other, )); // We duplicate the emission, but specifying different incoming and outgoing parties @@ -57,6 +58,7 @@ contract TestLog { // outgoing is set to msg sender, incoming is set to other msg_sender_ovpk_m, other, + context.msg_sender(), )); let event1 = ExampleEvent1 { @@ -70,6 +72,7 @@ contract TestLog { // outgoing is set to other, incoming is set to msg sender other_ovpk_m, context.msg_sender(), + other, )); } diff --git a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr index 765d8530ae9..bdb3b8bd0a9 100644 --- a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr @@ -191,7 +191,12 @@ contract TokenBlacklist { // TODO: constrain encryption below - we are using unconstrained here only becuase of the following Noir issue // https://github.com/noir-lang/noir/issues/5771 storage.balances.add(to, U128::from_integer(amount)).emit( - encode_and_encrypt_note_unconstrained(&mut context, msg_sender_ovpk_m, to), + encode_and_encrypt_note_unconstrained( + &mut context, + msg_sender_ovpk_m, + to, + context.msg_sender(), + ), ); } @@ -212,7 +217,7 @@ contract TokenBlacklist { // TODO: constrain encryption below - we are using unconstrained here only becuase of the following Noir issue // https://github.com/noir-lang/noir/issues/5771 storage.balances.sub(from, U128::from_integer(amount)).emit( - encode_and_encrypt_note_unconstrained(&mut context, from_ovpk_m, from), + encode_and_encrypt_note_unconstrained(&mut context, from_ovpk_m, from, from), ); TokenBlacklist::at(context.this_address())._increase_public_balance(to, amount).enqueue( @@ -241,11 +246,13 @@ contract TokenBlacklist { &mut context, from_ovpk_m, from, + from, )); storage.balances.add(to, amount).emit(encode_and_encrypt_note_unconstrained( &mut context, from_ovpk_m, to, + from, )); } @@ -264,7 +271,7 @@ contract TokenBlacklist { // TODO: constrain encryption below - we are using unconstrained here only becuase of the following Noir issue // https://github.com/noir-lang/noir/issues/5771 storage.balances.sub(from, U128::from_integer(amount)).emit( - encode_and_encrypt_note_unconstrained(&mut context, from_ovpk_m, from), + encode_and_encrypt_note_unconstrained(&mut context, from_ovpk_m, from, from), ); TokenBlacklist::at(context.this_address())._reduce_total_supply(amount).enqueue(&mut context); diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index 277b30864fa..f08ef9fb987 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -227,7 +227,7 @@ contract Token { let caller = context.msg_sender(); let caller_ovpk_m = get_public_keys(caller).ovpk_m; storage.balances.at(caller).add(caller, U128::from_integer(amount)).emit( - encode_and_encrypt_note(&mut context, caller_ovpk_m, caller), + encode_and_encrypt_note(&mut context, caller_ovpk_m, caller, caller), ); Token::at(context.this_address()) .assert_minter_and_mint(context.msg_sender(), amount) @@ -311,6 +311,7 @@ contract Token { &mut context, from_ovpk_m, to, + context.msg_sender(), )); } // docs:end:redeem_shield @@ -327,7 +328,7 @@ contract Token { // TODO: constrain encryption below - we are using unconstrained here only becuase of the following Noir issue // https://github.com/noir-lang/noir/issues/5771 storage.balances.at(from).sub(from, U128::from_integer(amount)).emit( - encode_and_encrypt_note_unconstrained(&mut context, from_ovpk_m, from), + encode_and_encrypt_note_unconstrained(&mut context, from_ovpk_m, from, from), ); Token::at(context.this_address())._increase_public_balance(to, amount).enqueue(&mut context); } @@ -357,18 +358,20 @@ contract Token { &mut context, from_ovpk_m, from, + from, )); storage.balances.at(to).add(to, amount).emit(encode_and_encrypt_note_unconstrained( &mut context, from_ovpk_m, to, + from, )); // We don't constrain encryption of the note log in `transfer` (unlike in `transfer_from`) because the transfer // function is only designed to be used in situations where the event is not strictly necessary (e.g. payment to // another person where the payment is considered to be successful when the other party successfully decrypts a // note). Transfer { from, to, amount: amount.to_field() }.emit( - encode_and_encrypt_event_unconstrained(&mut context, from_ovpk_m, to), + encode_and_encrypt_event_unconstrained(&mut context, from_ovpk_m, to, from), ); } // docs:end:transfer @@ -453,6 +456,7 @@ contract Token { &mut context, from_ovpk_m, from, + from, )); // docs:end:encrypted // docs:end:increase_private_balance @@ -462,6 +466,7 @@ contract Token { &mut context, from_ovpk_m, to, + from, )); } // docs:end:transfer_from @@ -477,7 +482,7 @@ contract Token { // TODO: constrain encryption below - we are using unconstrained here only becuase of the following Noir issue // https://github.com/noir-lang/noir/issues/5771 storage.balances.at(from).sub(from, U128::from_integer(amount)).emit( - encode_and_encrypt_note_unconstrained(&mut context, from_ovpk_m, from), + encode_and_encrypt_note_unconstrained(&mut context, from_ovpk_m, from, from), ); Token::at(context.this_address())._reduce_total_supply(amount).enqueue(&mut context); } @@ -529,7 +534,8 @@ contract Token { // We set the ovpk to the message sender's ovpk and we encrypt the log. let from_ovpk = get_public_keys(context.msg_sender()).ovpk_m; - let setup_log = note_setup_payload.encrypt_log(context, from_ovpk, to); + let setup_log = + note_setup_payload.encrypt_log(context, from_ovpk, to, context.msg_sender()); // Using the x-coordinate as a hiding point slot is safe against someone else interfering with it because // we have a guarantee that the public functions of the transaction are executed right after the private ones @@ -710,6 +716,7 @@ contract Token { &mut context, user_ovpk, user, + user, )); // 4. Now we get the partial payloads @@ -747,8 +754,9 @@ contract Token { // 6. We compute setup logs let fee_payer_setup_log = - fee_payer_setup_payload.encrypt_log(&mut context, user_ovpk, fee_payer); - let user_setup_log = user_setup_payload.encrypt_log(&mut context, user_ovpk, user); + fee_payer_setup_payload.encrypt_log(&mut context, user_ovpk, fee_payer, fee_payer); + let user_setup_log = + user_setup_payload.encrypt_log(&mut context, user_ovpk, user, fee_payer); // 7. We store the hiding points an logs in transients storage Token::at(context.this_address()) From 1c53459ff31b50ba808e204c532885c9b0f69e39 Mon Sep 17 00:00:00 2001 From: ludamad Date: Thu, 31 Oct 2024 09:11:39 -0400 Subject: [PATCH 17/21] chore: pass on docker_fast.sh (#9615) - Should be faster, stable cache layers & end-to-end image now available. - No longer need aws cli tool for cache download What's not done: - Make the images not huge - Most of yarn projects install could be in cache fixes: https://github.com/AztecProtocol/aztec-packages/issues/9503 --------- Co-authored-by: Maddiaa <47148561+Maddiaa0@users.noreply.github.com> --- Dockerfile.end-to-end.fast | 25 +++++++ Dockerfile.fast | 66 +++++++------------ barretenberg/bootstrap_cache.sh | 17 +++++ .../s3-cache-scripts/cache-download.sh | 11 +++- docker_fast.sh | 49 ++++++-------- 5 files changed, 97 insertions(+), 71 deletions(-) create mode 100644 Dockerfile.end-to-end.fast create mode 100755 barretenberg/bootstrap_cache.sh diff --git a/Dockerfile.end-to-end.fast b/Dockerfile.end-to-end.fast new file mode 100644 index 00000000000..b6ca6c9e99f --- /dev/null +++ b/Dockerfile.end-to-end.fast @@ -0,0 +1,25 @@ +# Use an ARG to define the architecture, defaulting to amd64 +ARG ARCH=amd64 +# aztec must be built from Dockerfile.fast +FROM aztecprotocol/aztec AS aztec +FROM aztecprotocol/build:1.0-${ARCH} + +# Install additional dependencies +RUN apt-get update && apt-get install -y software-properties-common \ + && add-apt-repository ppa:xtradeb/apps -y && apt-get update \ + && apt-get install -y wget gnupg \ + && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ + && echo "deb [arch=$(dpkg --print-architecture)] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list \ + && apt update && apt install -y curl chromium netcat-openbsd \ + && rm -rf /var/lib/apt/lists/* + +ENV CHROME_BIN="/usr/bin/chromium" +ENV PATH=/opt/foundry/bin:$PATH +ENV HARDWARE_CONCURRENCY="" +ENV FAKE_PROOFS="" +ENV PROVER_AGENT_CONCURRENCY=8 + +COPY --from=aztec /usr/src/ /usr/src/ +WORKDIR /usr/src/yarn-project/end-to-end + +ENTRYPOINT ["yarn", "test"] diff --git a/Dockerfile.fast b/Dockerfile.fast index dee0f5ff6f8..c91529a1043 100644 --- a/Dockerfile.fast +++ b/Dockerfile.fast @@ -20,99 +20,83 @@ RUN git init -b master \ && git config user.email 'tech@aztecprotocol.com' # ---------- EXTRACT BUILD-SYSTEM ---------- -COPY build-system.tar.gz . -RUN tar -xzf build-system.tar.gz \ - && rm build-system.tar.gz && git add . \ +COPY build-system build-system +RUN git add . \ && git commit -m "Update git metadata" >/dev/null # ---------- BUILD BARRETENBERG ---------- -COPY barretenberg.tar.gz . -RUN tar -xzf barretenberg.tar.gz \ - && rm barretenberg.tar.gz && git add . \ +COPY barretenberg barretenberg +RUN git add . \ && git commit -m "Update git metadata" >/dev/null # Bootstrap cache for barretenberg/cpp RUN --mount=type=secret,id=aws_access_key_id \ --mount=type=secret,id=aws_secret_access_key \ - cd barretenberg/cpp \ + bash -c 'cd barretenberg \ && AWS_ACCESS_KEY_ID=$(cat /run/secrets/aws_access_key_id) \ AWS_SECRET_ACCESS_KEY=$(cat /run/secrets/aws_secret_access_key) \ - ./bootstrap_cache.sh \ - && echo "barretenberg/cpp: Success" - -# Bootstrap cache for barretenberg/ts -RUN --mount=type=secret,id=aws_access_key_id \ - --mount=type=secret,id=aws_secret_access_key \ - cd barretenberg/ts \ - && AWS_ACCESS_KEY_ID=$(cat /run/secrets/aws_access_key_id) \ - AWS_SECRET_ACCESS_KEY=$(cat /run/secrets/aws_secret_access_key) \ - ./bootstrap_cache.sh \ - && echo "barretenberg/ts: Success" + ./bootstrap_cache.sh' \ + && echo "barretenberg: Success" # ---------- BUILD NOIR ---------- -COPY noir.tar.gz . -RUN tar -xzf noir.tar.gz \ - && rm noir.tar.gz && git add . \ +ADD noir noir +RUN git add . \ && git commit -m "Update git metadata" >/dev/null # Bootstrap cache for Noir RUN --mount=type=secret,id=aws_access_key_id \ --mount=type=secret,id=aws_secret_access_key \ - cd noir \ + bash -c 'cd noir \ && AWS_ACCESS_KEY_ID=$(cat /run/secrets/aws_access_key_id) \ AWS_SECRET_ACCESS_KEY=$(cat /run/secrets/aws_secret_access_key) \ - ./bootstrap_cache.sh \ + ./bootstrap_cache.sh' \ && echo "noir: Success" # ---------- BUILD L1 CONTRACTS ---------- -COPY l1-contracts.tar.gz . -RUN tar -xzf l1-contracts.tar.gz \ - && rm l1-contracts.tar.gz && git add . \ +ADD l1-contracts l1-contracts +RUN git add . \ && git commit -m "Update git metadata" >/dev/null # Bootstrap cache for L1 Contracts RUN --mount=type=secret,id=aws_access_key_id \ --mount=type=secret,id=aws_secret_access_key \ - cd l1-contracts \ + bash -c 'cd l1-contracts \ && AWS_ACCESS_KEY_ID=$(cat /run/secrets/aws_access_key_id) \ AWS_SECRET_ACCESS_KEY=$(cat /run/secrets/aws_secret_access_key) \ - ./bootstrap_cache.sh \ + ./bootstrap_cache.sh' \ && echo "l1-contracts: Success" # ---------- BUILD AVM TRANSPILER ---------- -COPY avm-transpiler.tar.gz . -RUN tar -xzf avm-transpiler.tar.gz \ - && rm avm-transpiler.tar.gz && git add . \ +ADD avm-transpiler avm-transpiler +RUN git add . \ && git commit -m "Update git metadata" >/dev/null # Bootstrap cache for AVM Transpiler RUN --mount=type=secret,id=aws_access_key_id \ --mount=type=secret,id=aws_secret_access_key \ - cd avm-transpiler \ + bash -c 'cd avm-transpiler \ && AWS_ACCESS_KEY_ID=$(cat /run/secrets/aws_access_key_id) \ AWS_SECRET_ACCESS_KEY=$(cat /run/secrets/aws_secret_access_key) \ - ./bootstrap_cache.sh \ + ./bootstrap_cache.sh' \ && echo "avm-transpiler: Success" # ---------- BUILD NOIR PROJECTS ---------- -COPY noir-projects.tar.gz . -RUN tar -xzf noir-projects.tar.gz \ - && rm noir-projects.tar.gz && git add . \ +ADD noir-projects noir-projects +RUN git add . \ && git commit -m "Update git metadata" >/dev/null # Bootstrap cache for Noir Projects RUN --mount=type=secret,id=aws_access_key_id \ --mount=type=secret,id=aws_secret_access_key \ - cd noir-projects \ + bash -c 'cd noir-projects \ && AWS_ACCESS_KEY_ID=$(cat /run/secrets/aws_access_key_id) \ AWS_SECRET_ACCESS_KEY=$(cat /run/secrets/aws_secret_access_key) \ - ./bootstrap_cache.sh \ + ./bootstrap_cache.sh' \ && echo "noir-projects: Success" # ---------- BUILD YARN PROJECT ---------- -COPY yarn-project.tar.gz . -RUN tar -xzf yarn-project.tar.gz \ - && rm yarn-project.tar.gz && git add . \ +ADD yarn-project yarn-project +RUN git add . \ && git commit -m "Update git metadata" >/dev/null # Build yarn-project directly (no cache script) diff --git a/barretenberg/bootstrap_cache.sh b/barretenberg/bootstrap_cache.sh new file mode 100755 index 00000000000..252e25f2e9f --- /dev/null +++ b/barretenberg/bootstrap_cache.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -eu + +cd "$(dirname "$0")" + +# Run both tasks in the background +(cd cpp && ./bootstrap_cache.sh "$@") & +pid_cpp=$! +(cd ts && ./bootstrap_cache.sh "$@") & +pid_ts=$! + +# Wait for both processes and capture any non-zero exit codes +wait $pid_cpp || exit_code=$? +wait $pid_ts || exit_code=$? + +# Exit with the first non-zero exit code, if any +exit ${exit_code:-0} diff --git a/build-system/s3-cache-scripts/cache-download.sh b/build-system/s3-cache-scripts/cache-download.sh index 8d72f860460..31c62bfe3ca 100755 --- a/build-system/s3-cache-scripts/cache-download.sh +++ b/build-system/s3-cache-scripts/cache-download.sh @@ -17,8 +17,17 @@ function on_exit() { # Run on any exit trap on_exit EXIT +# Extract endpoint URL if S3_BUILD_CACHE_AWS_PARAMS is set +if [[ -n "${S3_BUILD_CACHE_AWS_PARAMS:-}" ]]; then + # Extract URL from S3_BUILD_CACHE_AWS_PARAMS (assumes the format "--endpoint-url ") + # TODO stop passing with endpoint url + S3_ENDPOINT=$(echo "$S3_BUILD_CACHE_AWS_PARAMS" | sed -n 's/--endpoint-url \([^ ]*\)/\1/p') +else + # Default to AWS S3 URL if no custom endpoint is set + S3_ENDPOINT="http://aztec-ci-artifacts.s3.amazonaws.com" +fi # Attempt to download the cache file -aws ${S3_BUILD_CACHE_AWS_PARAMS:-} s3 cp "s3://aztec-ci-artifacts/build-cache/$TAR_FILE" "$TAR_FILE" --quiet --no-sign-request || (echo "Cache download of $TAR_FILE failed." && exit 1) +curl -s -f -O "${S3_ENDPOINT}/build-cache/$TAR_FILE" || (echo "Cache download of $TAR_FILE failed." && exit 1) # Extract the cache file mkdir -p "$OUT_DIR" diff --git a/docker_fast.sh b/docker_fast.sh index 2c24eceac12..49d30243be6 100755 --- a/docker_fast.sh +++ b/docker_fast.sh @@ -1,43 +1,24 @@ #!/usr/bin/env bash # TODO eventually rename this docker.sh when we've moved to it entirely -set -eux +set -eu -function start_minio() { - if nc -z 127.0.0.1 12000 2>/dev/null >/dev/null ; then - # Already started - return - fi - docker run -d -p 12000:9000 -p 12001:12001 -v minio-data:/data \ - quay.io/minio/minio server /data --console-address ":12001" - # Make our cache bucket - AWS_ACCESS_KEY_ID="minioadmin" AWS_SECRET_ACCESS_KEY="minioadmin" aws --endpoint-url http://localhost:12000 s3 mb s3://aztec-ci-artifacts 2>/dev/null || true -} +MAKE_END_TO_END=${1:-false} S3_BUILD_CACHE_UPLOAD=${S3_BUILD_CACHE_UPLOAD:-false} S3_BUILD_CACHE_MINIO_URL="http://$(hostname -I | awk '{print $1}'):12000" -# Start local file server for a quicker cache layer -start_minio - if ! git diff-index --quiet HEAD --; then - echo "Warning: You have unstaged changes. Disabling S3 caching and local MinIO caching to avoid polluting cache (which uses Git data)." >&2 + echo "Warning: You have unstaged changes. For now this is a fatal error as this script relies on git metadata." >&2 S3_BUILD_CACHE_UPLOAD=false S3_BUILD_CACHE_DOWNLOAD=false - S3_BUILD_CACHE_MINIO_URL="" - echo "Fatal: For now, this is a fatal error as it would defeat the purpose of 'fast'." >&2 + S3_BUILD_CACHE_MINIO_URL=""A exit 1 elif [ ! -z "${AWS_ACCESS_KEY_ID:-}" ] ; then S3_BUILD_CACHE_DOWNLOAD=true elif [ -f ~/.aws/credentials ]; then # Retrieve credentials if available in AWS config - - # Do not trace this information - set +x AWS_ACCESS_KEY_ID=$(aws configure get default.aws_access_key_id) AWS_SECRET_ACCESS_KEY=$(aws configure get default.aws_secret_access_key) - - # Resume tracing - set -x S3_BUILD_CACHE_DOWNLOAD=true else S3_BUILD_CACHE_UPLOAD=false @@ -52,11 +33,8 @@ function on_exit() { trap on_exit EXIT # Save each secret environment variable into a separate file in $TMP directory -set +x echo "${AWS_ACCESS_KEY_ID:-}" > "$TMP/aws_access_key_id.txt" echo "${AWS_SECRET_ACCESS_KEY:-}" > "$TMP/aws_secret_access_key.txt" -set -x - echo "${S3_BUILD_CACHE_MINIO_URL:-}" > "$TMP/s3_build_cache_minio_url.txt" echo "${S3_BUILD_CACHE_UPLOAD:-}" > "$TMP/s3_build_cache_upload.txt" echo "${S3_BUILD_CACHE_DOWNLOAD:-}" > "$TMP/s3_build_cache_download.txt" @@ -73,10 +51,19 @@ PROJECTS=( yarn-project ) +function copy() { + local project=$1 + git archive --format=tar.gz --mtime='1970-01-01T00:00Z' -o "$TMP/$project.tar.gz" $(git rev-parse HEAD) $project + cd "$TMP" + tar -xzf $project.tar.gz + rm $project.tar.gz +} +# Write the git archives in parallel for project in "${PROJECTS[@]}"; do - # Archive Git-tracked files per project into a tar.gz file - git archive --format=tar.gz -o "$TMP/$project.tar.gz" HEAD $project + # Copy over JUST the git version of files over (bail if any fail) + copy $project || kill $0 & done +wait # Run Docker build with secrets in the folder with our archive DOCKER_BUILDKIT=1 docker build -t aztecprotocol/aztec -f Dockerfile.fast --progress=plain \ @@ -85,4 +72,8 @@ DOCKER_BUILDKIT=1 docker build -t aztecprotocol/aztec -f Dockerfile.fast --progr --secret id=s3_build_cache_minio_url,src=$TMP/s3_build_cache_minio_url.txt \ --secret id=s3_build_cache_upload,src=$TMP/s3_build_cache_upload.txt \ --secret id=s3_build_cache_download,src=$TMP/s3_build_cache_download.txt \ - "$TMP" \ No newline at end of file + "$TMP" + +if [ $MAKE_END_TO_END != "false" ] ; then + DOCKER_BUILDKIT=1 docker build -t aztecprotocol/end-to-end -f Dockerfile.end-to-end.fast --progress=plain "$TMP" +fi From 737c5732165b9fc339ab6a15838029481dcfdbf2 Mon Sep 17 00:00:00 2001 From: spypsy Date: Thu, 31 Oct 2024 13:52:50 +0000 Subject: [PATCH 18/21] fix: e2e event logs test (#9621) --- .../end-to-end/src/e2e_event_logs.test.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_event_logs.test.ts b/yarn-project/end-to-end/src/e2e_event_logs.test.ts index 53e0c629575..8a921af5097 100644 --- a/yarn-project/end-to-end/src/e2e_event_logs.test.ts +++ b/yarn-project/end-to-end/src/e2e_event_logs.test.ts @@ -37,7 +37,7 @@ describe('Logs', () => { afterAll(() => teardown()); describe('functionality around emitting an encrypted log', () => { - it.skip('emits multiple events as encrypted logs and decodes them one manually', async () => { + it('emits multiple events as encrypted logs and decodes them one manually', async () => { const randomness = makeTuple(2, Fr.random); const preimage = makeTuple(4, Fr.random); @@ -68,31 +68,31 @@ describe('Logs', () => { expect(event0?.value0).toStrictEqual(preimage[0].toBigInt()); expect(event0?.value1).toStrictEqual(preimage[1].toBigInt()); - // We check that an event that does not match, is not decoded correctly due to an event type id mismatch - const badEvent0 = event0Metadata.decode(decryptedEvent0); - expect(badEvent0).toBe(undefined); - const decryptedEvent1 = L1EventPayload.decryptAsIncoming(encryptedLogs[2], wallets[0].getEncryptionSecret())!; - expect(decryptedEvent1.contractAddress).toStrictEqual(testLogContract.address); - expect(decryptedEvent1.randomness).toStrictEqual(randomness[1]); - expect(decryptedEvent1.eventTypeId).toStrictEqual(EventSelector.fromSignature('ExampleEvent1((Field),u8)')); - - // We check our second event, which is a different type const event1Metadata = new EventMetadata( EventType.Encrypted, TestLogContract.events.ExampleEvent1, ); + // We check our second event, which is a different type const event1 = event1Metadata.decode(decryptedEvent1); + // We check that an event that does not match, is not decoded correctly due to an event type id mismatch + const badEvent0 = event1Metadata.decode(decryptedEvent0); + expect(badEvent0).toBe(undefined); + + expect(decryptedEvent1.contractAddress).toStrictEqual(testLogContract.address); + expect(decryptedEvent1.randomness).toStrictEqual(randomness[1]); + expect(decryptedEvent1.eventTypeId).toStrictEqual(EventSelector.fromSignature('ExampleEvent1((Field),u8)')); + // We expect the fields to have been populated correctly expect(event1?.value2).toStrictEqual(preimage[2]); // We get the last byte here because value3 is of type u8 expect(event1?.value3).toStrictEqual(BigInt(preimage[3].toBuffer().subarray(31).readUint8())); // Again, trying to decode another event with mismatching data does not yield anything - const badEvent1 = event1Metadata.decode(decryptedEvent1); + const badEvent1 = event0Metadata.decode(decryptedEvent1); expect(badEvent1).toBe(undefined); }); From bf53f5e0b792e00a45013a16adb72a952e527cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bene=C5=A1?= Date: Thu, 31 Oct 2024 08:22:24 -0600 Subject: [PATCH 19/21] refactor: nuking `Token::privately_mint_private_note(...)` (#9616) --- .../contracts/token_contract/src/main.nr | 19 ------------------- yarn-project/bot/src/factory.ts | 2 +- .../src/benchmarks/bench_prover.test.ts | 2 +- .../src/benchmarks/bench_tx_size_fees.test.ts | 2 +- .../end-to-end/src/e2e_fees/fees_test.ts | 2 +- .../src/e2e_fees/private_payments.test.ts | 4 ++-- .../private_transfer_recursion.test.ts | 10 ++++++---- 7 files changed, 12 insertions(+), 29 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index f08ef9fb987..65968ff0622 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -221,25 +221,6 @@ contract Token { // docs:end:insert_from_public } // docs:end:mint_private - // TODO: Nuke this - test functions do not belong to token contract! - #[private] - fn privately_mint_private_note(amount: Field) { - let caller = context.msg_sender(); - let caller_ovpk_m = get_public_keys(caller).ovpk_m; - storage.balances.at(caller).add(caller, U128::from_integer(amount)).emit( - encode_and_encrypt_note(&mut context, caller_ovpk_m, caller, caller), - ); - Token::at(context.this_address()) - .assert_minter_and_mint(context.msg_sender(), amount) - .enqueue(&mut context); - } - #[public] - #[internal] - fn assert_minter_and_mint(minter: AztecAddress, amount: Field) { - assert(storage.minters.at(minter).read(), "caller is not minter"); - let supply = storage.total_supply.read() + U128::from_integer(amount); - storage.total_supply.write(supply); - } // docs:start:shield #[public] fn shield(from: AztecAddress, amount: Field, secret_hash: Field, nonce: Field) { diff --git a/yarn-project/bot/src/factory.ts b/yarn-project/bot/src/factory.ts index 541acc975da..053dfb8f04b 100644 --- a/yarn-project/bot/src/factory.ts +++ b/yarn-project/bot/src/factory.ts @@ -149,7 +149,7 @@ export class BotFactory { calls.push( isStandardToken - ? token.methods.privately_mint_private_note(MINT_BALANCE).request() + ? token.methods.mint_to_private(sender, MINT_BALANCE).request() : token.methods.mint(MINT_BALANCE, sender, sender).request(), ); } diff --git a/yarn-project/end-to-end/src/benchmarks/bench_prover.test.ts b/yarn-project/end-to-end/src/benchmarks/bench_prover.test.ts index 2d8254319b0..203cf8599f0 100644 --- a/yarn-project/end-to-end/src/benchmarks/bench_prover.test.ts +++ b/yarn-project/end-to-end/src/benchmarks/bench_prover.test.ts @@ -108,7 +108,7 @@ describe('benchmarks/proving', () => { await Promise.all([ initialGasContract.methods.claim(initialFpContract.address, 1e12, claimSecret, messageLeafIndex).send().wait(), initialTokenContract.methods.mint_public(initialSchnorrWallet.getAddress(), 1e12).send().wait(), - initialTokenContract.methods.privately_mint_private_note(1e12).send().wait(), + initialTokenContract.methods.mint_to_private(initialSchnorrWallet.getAddress(), 1e12).send().wait(), ]); recipient = CompleteAddress.random(); diff --git a/yarn-project/end-to-end/src/benchmarks/bench_tx_size_fees.test.ts b/yarn-project/end-to-end/src/benchmarks/bench_tx_size_fees.test.ts index e294bd3de89..fa5f17bd325 100644 --- a/yarn-project/end-to-end/src/benchmarks/bench_tx_size_fees.test.ts +++ b/yarn-project/end-to-end/src/benchmarks/bench_tx_size_fees.test.ts @@ -71,7 +71,7 @@ describe('benchmarks/tx_size_fees', () => { feeJuice.methods.claim(fpc.address, 100e9, fpcSecret, fpcLeafIndex).send().wait(), feeJuice.methods.claim(aliceWallet.getAddress(), 100e9, aliceSecret, aliceLeafIndex).send().wait(), ]); - await token.methods.privately_mint_private_note(100e9).send().wait(); + await token.methods.mint_to_private(aliceWallet.getAddress(), 100e9).send().wait(); await token.methods.mint_public(aliceWallet.getAddress(), 100e9).send().wait(); }); diff --git a/yarn-project/end-to-end/src/e2e_fees/fees_test.ts b/yarn-project/end-to-end/src/e2e_fees/fees_test.ts index 3ce49b5aa87..ade885f5234 100644 --- a/yarn-project/end-to-end/src/e2e_fees/fees_test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/fees_test.ts @@ -116,7 +116,7 @@ export class FeesTest { /** Alice mints Token */ async mintToken(amount: bigint) { const balanceBefore = await this.token.methods.balance_of_private(this.aliceAddress).simulate(); - await this.token.methods.privately_mint_private_note(amount).send().wait(); + await this.token.methods.mint_to_private(this.aliceAddress, amount).send().wait(); const balanceAfter = await this.token.methods.balance_of_private(this.aliceAddress).simulate(); expect(balanceAfter).toEqual(balanceBefore + amount); } diff --git a/yarn-project/end-to-end/src/e2e_fees/private_payments.test.ts b/yarn-project/end-to-end/src/e2e_fees/private_payments.test.ts index 02b6d3c7180..23fe8817f8f 100644 --- a/yarn-project/end-to-end/src/e2e_fees/private_payments.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/private_payments.test.ts @@ -196,7 +196,7 @@ describe('e2e_fees private_payment', () => { */ const newlyMintedBananas = 10n; const tx = await bananaCoin.methods - .privately_mint_private_note(newlyMintedBananas) + .mint_to_private(aliceAddress, newlyMintedBananas) .send({ fee: { gasSettings, @@ -373,7 +373,7 @@ describe('e2e_fees private_payment', () => { await expect( bananaCoin.methods - .privately_mint_private_note(10) + .mint_to_private(aliceAddress, 10) .send({ // we need to skip public simulation otherwise the PXE refuses to accept the TX skipPublicSimulation: true, diff --git a/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts index 1425c0b16f0..dbc2ab59820 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts @@ -18,10 +18,12 @@ describe('e2e_token_contract private transfer recursion', () => { }); async function mintNotes(noteAmounts: bigint[]): Promise { - // Mint all notes, 4 at a time - for (let mintedNotes = 0; mintedNotes < noteAmounts.length; mintedNotes += 4) { - const toMint = noteAmounts.slice(mintedNotes, mintedNotes + 4); // We mint 4 notes at a time - const actions = toMint.map(amt => asset.methods.privately_mint_private_note(amt).request()); + // We mint only 3 notes in 1 transaction as that is the maximum public data writes we can squeeze into a tx. + // --> Minting one note requires 19 public data writes (16 for the note encrypted log, 3 for note hiding point). + const notesPerIteration = 3; + for (let mintedNotes = 0; mintedNotes < noteAmounts.length; mintedNotes += notesPerIteration) { + const toMint = noteAmounts.slice(mintedNotes, mintedNotes + notesPerIteration); + const actions = toMint.map(amt => asset.methods.mint_to_private(wallets[0].getAddress(), amt).request()); await new BatchCall(wallets[0], actions).send().wait(); } From 392114a0a66bd580175ff7a07b0c6d899d69be8f Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Thu, 31 Oct 2024 14:43:45 +0000 Subject: [PATCH 20/21] feat: spartan proving (#9584) This PR adds K8s config to deploy a network with proving enabled --------- Co-authored-by: PhilWindle <60546371+PhilWindle@users.noreply.github.com> Co-authored-by: ludamad --- .../arithmetization/mega_arithmetization.hpp | 6 +- .../templates/deploy-l1-verifier.yaml | 58 +++++++++++++++ .../aztec-network/templates/prover-agent.yaml | 73 +++++++++++++++++++ .../aztec-network/templates/prover-node.yaml | 4 +- spartan/aztec-network/templates/pxe.yaml | 4 +- spartan/aztec-network/values.yaml | 22 +++++- .../values/1-validator-with-proving.yaml | 37 ++++++++++ .../aztec-network/values/1-validators.yaml | 1 - yarn-project/bot/src/config.ts | 2 +- .../cli/src/cmds/l1/deploy_l1_verifier.ts | 30 ++++++-- yarn-project/cli/src/cmds/l1/index.ts | 20 ++++- yarn-project/cli/src/utils/commands.ts | 11 ++- 12 files changed, 242 insertions(+), 26 deletions(-) create mode 100644 spartan/aztec-network/templates/deploy-l1-verifier.yaml create mode 100644 spartan/aztec-network/templates/prover-agent.yaml create mode 100644 spartan/aztec-network/values/1-validator-with-proving.yaml diff --git a/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/mega_arithmetization.hpp b/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/mega_arithmetization.hpp index 379602e2463..67a77dfa715 100644 --- a/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/mega_arithmetization.hpp +++ b/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/mega_arithmetization.hpp @@ -96,8 +96,8 @@ template class MegaArith { this->delta_range = 25000; this->elliptic = 80000; this->aux = 100000; - this->poseidon2_external = 30000; - this->poseidon2_internal = 150000; + this->poseidon2_external = 30128; + this->poseidon2_internal = 172000; this->lookup = 200000; } }; @@ -248,4 +248,4 @@ using MegaArithmetization = MegaArith; template concept HasAdditionalSelectors = IsAnyOf>; -} // namespace bb \ No newline at end of file +} // namespace bb diff --git a/spartan/aztec-network/templates/deploy-l1-verifier.yaml b/spartan/aztec-network/templates/deploy-l1-verifier.yaml new file mode 100644 index 00000000000..90530932fdc --- /dev/null +++ b/spartan/aztec-network/templates/deploy-l1-verifier.yaml @@ -0,0 +1,58 @@ +{{- if .Values.network.setupL2Contracts }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "aztec-network.fullname" . }}-deploy-l1-verifier + labels: + {{- include "aztec-network.labels" . | nindent 4 }} +spec: + template: + metadata: + labels: + {{- include "aztec-network.selectorLabels" . | nindent 8 }} + app: deploy-l1-verifier + spec: + restartPolicy: OnFailure + containers: + - name: deploy-l1-verifier + image: {{ .Values.images.aztec.image }} + command: + - /bin/bash + - -c + - | + set -e + + [ $ENABLE = "true" ] || exit 0 + + until curl -s -X GET "$AZTEC_NODE_URL/status"; do + echo "Waiting for Aztec node $AZTEC_NODE_URL..." + sleep 5 + done + echo "Boot node is ready!" + + export ROLLUP_CONTRACT_ADDRESS=$(curl -X POST -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","method":"node_getL1ContractAddresses","params":[],"id":1}' \ + "$AZTEC_NODE_URL" \ + | jq -r '.result.rollupAddress.data') + + echo "Rollup contract address: $ROLLUP_CONTRACT_ADDRESS" + node /usr/src/yarn-project/aztec/dest/bin/index.js deploy-l1-verifier --verifier real + echo "L1 verifier deployed" + env: + - name: ENABLE + value: {{ .Values.jobs.deployL1Verifier.enable | quote }} + - name: NODE_NO_WARNINGS + value: "1" + - name: DEBUG + value: "aztec:*" + - name: LOG_LEVEL + value: "debug" + - name: ETHEREUM_HOST + value: {{ include "aztec-network.ethereumHost" . | quote }} + - name: L1_CHAIN_ID + value: {{ .Values.ethereum.chainId | quote }} + - name: PRIVATE_KEY + value: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + - name: AZTEC_NODE_URL + value: {{ include "aztec-network.bootNodeUrl" . | quote }} +{{ end }} diff --git a/spartan/aztec-network/templates/prover-agent.yaml b/spartan/aztec-network/templates/prover-agent.yaml new file mode 100644 index 00000000000..2ded11498ee --- /dev/null +++ b/spartan/aztec-network/templates/prover-agent.yaml @@ -0,0 +1,73 @@ +{{- if .Values.proverAgent.enabled }} +apiVersion: apps/v1 +kind: ReplicaSet +metadata: + name: {{ include "aztec-network.fullname" . }}-prover-agent + labels: + {{- include "aztec-network.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.proverAgent.replicas }} + selector: + matchLabels: + {{- include "aztec-network.selectorLabels" . | nindent 6 }} + app: prover-agent + template: + metadata: + labels: + {{- include "aztec-network.selectorLabels" . | nindent 8 }} + app: prover-agent + spec: + initContainers: + - name: wait-for-prover-node + image: {{ .Values.images.curl.image }} + command: + - /bin/sh + - -c + - | + until curl -s -X POST "$PROVER_JOB_SOURCE_URL/status"; do + echo "Waiting for Prover node $PROVER_JOB_SOURCE_URL ..." + sleep 5 + done + echo "Prover node is ready!" + {{- if .Values.telemetry.enabled }} + until curl --head --silent {{ include "aztec-network.otelCollectorMetricsEndpoint" . }} > /dev/null; do + echo "Waiting for OpenTelemetry collector..." + sleep 5 + done + echo "OpenTelemetry collector is ready!" + {{- end }} + env: + - name: PROVER_JOB_SOURCE_URL + value: http://{{ include "aztec-network.fullname" . }}-prover-node.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.proverNode.service.nodePort }} + containers: + - name: prover-agent + image: "{{ .Values.images.aztec.image }}" + imagePullPolicy: {{ .Values.images.aztec.pullPolicy }} + command: + - "/bin/bash" + - "-c" + - "node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --prover" + env: + - name: LOG_LEVEL + value: "{{ .Values.proverAgent.logLevel }}" + - name: LOG_JSON + value: "1" + - name: DEBUG + value: "{{ .Values.proverAgent.debug }}" + - name: PROVER_REAL_PROOFS + value: "{{ .Values.proverAgent.realProofs }}" + - name: PROVER_JOB_SOURCE_URL + value: http://{{ include "aztec-network.fullname" . }}-prover-node.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.proverNode.service.nodePort }} + - name: PROVER_AGENT_ENABLED + value: "true" + - name: PROVER_AGENT_CONCURRENCY + value: {{ .Values.proverAgent.concurrency | quote }} + - name: HARDWARE_CONCURRENCY + value: {{ .Values.proverAgent.bb.hardwareConcurrency | quote }} + - name: OTEL_EXPORTER_OTLP_METRICS_ENDPOINT + value: {{ include "aztec-network.otelCollectorMetricsEndpoint" . | quote }} + - name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT + value: {{ include "aztec-network.otelCollectorTracesEndpoint" . | quote }} + - name: OTEL_EXPORTER_OTLP_LOGS_ENDPOINT + value: {{ include "aztec-network.otelCollectorLogsEndpoint" . | quote }} +{{- end }} diff --git a/spartan/aztec-network/templates/prover-node.yaml b/spartan/aztec-network/templates/prover-node.yaml index 6ec388150d3..3158f3145a5 100644 --- a/spartan/aztec-network/templates/prover-node.yaml +++ b/spartan/aztec-network/templates/prover-node.yaml @@ -67,7 +67,7 @@ spec: command: - "/bin/bash" - "-c" - - "source /shared/contracts.env && env && node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --prover-node --prover --archiver" + - "source /shared/contracts.env && env && node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js start --prover-node --archiver" volumeMounts: - name: shared-volume mountPath: /shared @@ -163,4 +163,4 @@ spec: - port: {{ .Values.proverNode.service.nodePort }} name: node {{ end }} -{{ end }} \ No newline at end of file +{{ end }} diff --git a/spartan/aztec-network/templates/pxe.yaml b/spartan/aztec-network/templates/pxe.yaml index 0ae3702943e..d885313a6f8 100644 --- a/spartan/aztec-network/templates/pxe.yaml +++ b/spartan/aztec-network/templates/pxe.yaml @@ -57,6 +57,8 @@ spec: value: "{{ .Values.pxe.logLevel }}" - name: DEBUG value: "{{ .Values.pxe.debug }}" + - name: PXE_PROVER_ENABLED + value: "{{ .Values.pxe.proverEnabled }}" ports: - name: http containerPort: {{ .Values.pxe.service.port }} @@ -119,4 +121,4 @@ spec: nodePort: {{ .Values.pxe.service.nodePort }} {{- end }} {{ end }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/spartan/aztec-network/values.yaml b/spartan/aztec-network/values.yaml index 8905fc155eb..30056d43827 100644 --- a/spartan/aztec-network/values.yaml +++ b/spartan/aztec-network/values.yaml @@ -30,7 +30,7 @@ bootNode: p2pUdpPort: 40400 nodePort: 8080 logLevel: "debug" - debug: "aztec:*,-aztec:avm_simulator*,-aztec:libp2p_service*,-aztec:circuits:artifact_hash,-json-rpc*" + debug: "aztec:*,-aztec:avm_simulator*,-aztec:libp2p_service*,-aztec:circuits:artifact_hash,-json-rpc*,-aztec:world-state:database,-aztec:l2_block_stream*" coinbaseAddress: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" sequencer: maxSecondsBetweenBlocks: 0 @@ -67,7 +67,7 @@ validator: p2pUdpPort: 40400 nodePort: 8080 logLevel: "debug" - debug: "aztec:*,-aztec:avm_simulator*,-aztec:libp2p_service*,-aztec:circuits:artifact_hash,-json-rpc*" + debug: "aztec:*,-aztec:avm_simulator*,-aztec:libp2p_service*,-aztec:circuits:artifact_hash,-json-rpc*,-aztec:world-state:database,-aztec:l2_block_stream*" sequencer: maxSecondsBetweenBlocks: 0 minTxsPerBlock: 1 @@ -88,9 +88,9 @@ proverNode: service: nodePort: 8080 logLevel: "debug" - debug: "aztec:*,-aztec:avm_simulator*,-aztec:libp2p_service*,-aztec:circuits:artifact_hash,-json-rpc*" + debug: "aztec:*,-aztec:avm_simulator*,-aztec:libp2p_service*,-aztec:circuits:artifact_hash,-json-rpc*,-aztec:world-state:database,-aztec:l2_block_stream*" realProofs: false - proverAgentEnabled: true + proverAgentEnabled: false resources: requests: memory: "2Gi" @@ -102,6 +102,7 @@ pxe: externalHost: "" logLevel: "debug" debug: "aztec:*,-aztec:avm_simulator*,-aztec:libp2p_service*,-aztec:circuits:artifact_hash,-json-rpc*" + proverEnable: false replicas: 1 service: port: 8080 @@ -176,3 +177,16 @@ ethereum: memory: "2Gi" cpu: "200m" storage: "8Gi" + +proverAgent: + enabled: true + replicas: 1 + debug: "aztec:*,-aztec:avm_simulator*,-aztec:libp2p_service*,-aztec:circuits:artifact_hash,-json-rpc*,-aztec:world-state:database,-aztec:l2_block_stream*" + realProofs: false + concurrency: 1 + bb: + hardwareConcurrency: "" + +jobs: + deployL1Verifier: + enable: false diff --git a/spartan/aztec-network/values/1-validator-with-proving.yaml b/spartan/aztec-network/values/1-validator-with-proving.yaml new file mode 100644 index 00000000000..ac7ca644a1f --- /dev/null +++ b/spartan/aztec-network/values/1-validator-with-proving.yaml @@ -0,0 +1,37 @@ +validator: + replicas: 1 + validatorKeys: + - 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + validatorAddresses: + - 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + validator: + disabled: false + +bootNode: + validator: + disabled: true + +proverNode: + realProofs: true + +proverAgent: + replicas: 6 + realProofs: true + bb: + hardwareConcurrency: 16 + +pxe: + proverEnabled: true + +bot: + enabled: true + pxeProverEnabled: true + txIntervalSeconds: 200 + +jobs: + deployL1Verifier: + enable: true + +telemetry: + enabled: true + otelCollectorEndpoint: http://metrics-opentelemetry-collector.metrics:4318 diff --git a/spartan/aztec-network/values/1-validators.yaml b/spartan/aztec-network/values/1-validators.yaml index 28bb6a0b621..53038895c1f 100644 --- a/spartan/aztec-network/values/1-validators.yaml +++ b/spartan/aztec-network/values/1-validators.yaml @@ -1,5 +1,4 @@ validator: - debug: "aztec:*,-aztec:avm_simulator:*,-aztec:libp2p_service" replicas: 1 validatorKeys: - 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 diff --git a/yarn-project/bot/src/config.ts b/yarn-project/bot/src/config.ts index a4f36f0ab8a..2cd14d0d78c 100644 --- a/yarn-project/bot/src/config.ts +++ b/yarn-project/bot/src/config.ts @@ -122,7 +122,7 @@ export const botConfigMappings: ConfigMappingsType = { description: 'Which chain the bot follows', defaultValue: 'NONE', parseEnv(val) { - if (!botFollowChain.includes(val as any)) { + if (!(botFollowChain as readonly string[]).includes(val.toUpperCase())) { throw new Error(`Invalid value for BOT_FOLLOW_CHAIN: ${val}`); } return val as BotFollowChain; diff --git a/yarn-project/cli/src/cmds/l1/deploy_l1_verifier.ts b/yarn-project/cli/src/cmds/l1/deploy_l1_verifier.ts index ab0ddb2dfcf..4150479fbd6 100644 --- a/yarn-project/cli/src/cmds/l1/deploy_l1_verifier.ts +++ b/yarn-project/cli/src/cmds/l1/deploy_l1_verifier.ts @@ -5,9 +5,10 @@ import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; import { InvalidOptionArgumentError } from 'commander'; // @ts-expect-error solc-js doesn't publish its types https://github.com/ethereum/solc-js/issues/689 import solc from 'solc'; -import { getContract } from 'viem'; +import { type Hex, getContract } from 'viem'; export async function deployUltraHonkVerifier( + rollupAddress: Hex | undefined, ethRpcUrl: string, l1ChainId: string, privateKey: string | undefined, @@ -32,14 +33,21 @@ export async function deployUltraHonkVerifier( createEthereumChain(ethRpcUrl, l1ChainId).chainInfo, ); - const pxe = await createCompatibleClient(pxeRpcUrl, debugLogger); - const { l1ContractAddresses } = await pxe.getNodeInfo(); + if (!rollupAddress && pxeRpcUrl) { + const pxe = await createCompatibleClient(pxeRpcUrl, debugLogger); + const { l1ContractAddresses } = await pxe.getNodeInfo(); + rollupAddress = l1ContractAddresses.rollupAddress.toString(); + } + + if (!rollupAddress) { + throw new InvalidOptionArgumentError('Missing rollup address'); + } const { RollupAbi } = await import('@aztec/l1-artifacts'); const rollup = getContract({ abi: RollupAbi, - address: l1ContractAddresses.rollupAddress.toString(), + address: rollupAddress, client: walletClient, }); @@ -64,6 +72,7 @@ export async function deployUltraHonkVerifier( } export async function deployMockVerifier( + rollupAddress: Hex | undefined, ethRpcUrl: string, l1ChainId: string, privateKey: string | undefined, @@ -87,12 +96,19 @@ export async function deployMockVerifier( ); log(`Deployed MockVerifier at ${mockVerifierAddress.toString()}`); - const pxe = await createCompatibleClient(pxeRpcUrl, debugLogger); - const { l1ContractAddresses } = await pxe.getNodeInfo(); + if (!rollupAddress && pxeRpcUrl) { + const pxe = await createCompatibleClient(pxeRpcUrl, debugLogger); + const { l1ContractAddresses } = await pxe.getNodeInfo(); + rollupAddress = l1ContractAddresses.rollupAddress.toString(); + } + + if (!rollupAddress) { + throw new InvalidOptionArgumentError('Missing rollup address'); + } const rollup = getContract({ abi: RollupAbi, - address: l1ContractAddresses.rollupAddress.toString(), + address: rollupAddress, client: walletClient, }); diff --git a/yarn-project/cli/src/cmds/l1/index.ts b/yarn-project/cli/src/cmds/l1/index.ts index 37cd7a7e7dd..80c0d514c3b 100644 --- a/yarn-project/cli/src/cmds/l1/index.ts +++ b/yarn-project/cli/src/cmds/l1/index.ts @@ -1,12 +1,13 @@ import { EthAddress } from '@aztec/foundation/eth-address'; import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; -import { type Command } from 'commander'; +import { type Command, Option } from 'commander'; import { ETHEREUM_HOST, PRIVATE_KEY, l1ChainIdOption, + makePxeOption, parseAztecAddress, parseBigint, parseEthereumAddress, @@ -207,8 +208,19 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: DebugL 'Url of the ethereum host. Chain identifiers localhost and testnet can be used', ETHEREUM_HOST, ) - .requiredOption('--l1-chain-id ', 'The chain id of the L1 network', '31337') - .addOption(pxeOption) + .addOption( + new Option('--l1-chain-id ', 'The chain id of the L1 network') + .env('L1_CHAIN_ID') + .default('31337') + .makeOptionMandatory(true), + ) + .addOption(makePxeOption(false).conflicts('rollup-address')) + .addOption( + new Option('--rollup-address ', 'The address of the rollup contract') + .env('ROLLUP_CONTRACT_ADDRESS') + .argParser(parseEthereumAddress) + .conflicts('rpc-url'), + ) .option('--l1-private-key ', 'The L1 private key to use for deployment', PRIVATE_KEY) .option( '-m, --mnemonic ', @@ -222,6 +234,7 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: DebugL const { deployMockVerifier, deployUltraHonkVerifier } = await import('./deploy_l1_verifier.js'); if (options.verifier === 'mock') { await deployMockVerifier( + options.rollupAddress?.toString(), options.l1RpcUrl, options.l1ChainId, options.l1PrivateKey, @@ -232,6 +245,7 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: DebugL ); } else { await deployUltraHonkVerifier( + options.rollupAddress?.toString(), options.l1RpcUrl, options.l1ChainId, options.l1PrivateKey, diff --git a/yarn-project/cli/src/utils/commands.ts b/yarn-project/cli/src/utils/commands.ts index 656f07282f8..5bc66a8a5c0 100644 --- a/yarn-project/cli/src/utils/commands.ts +++ b/yarn-project/cli/src/utils/commands.ts @@ -29,10 +29,13 @@ export function addOptions(program: Command, options: Option[]) { return program; } -export const pxeOption = new Option('-u, --rpc-url ', 'URL of the PXE') - .env('PXE_URL') - .default(`http://${LOCALHOST}:8080`) - .makeOptionMandatory(true); +export const makePxeOption = (mandatory: boolean) => + new Option('-u, --rpc-url ', 'URL of the PXE') + .env('PXE_URL') + .default(`http://${LOCALHOST}:8080`) + .makeOptionMandatory(mandatory); + +export const pxeOption = makePxeOption(true); export const l1ChainIdOption = new Option('-c, --l1-chain-id ', 'Chain ID of the ethereum host') .env('L1_CHAIN_ID') From feace70727f9e5a971809955030a8ea88ce84f4a Mon Sep 17 00:00:00 2001 From: Innokentii Sennovskii Date: Thu, 31 Oct 2024 14:44:03 +0000 Subject: [PATCH 21/21] fix: Resolution of bugs from bigfield audits (#9547) This PR resolves Critical, High, Medium, Low and some informational issues from the bigfield test audits by ZKSecurity, Zellic and Spearbit --------- Co-authored-by: Sarkoxed --- .../ultra_circuit_builder.test.cpp | 16 +- .../cpp/src/barretenberg/dsl/CMakeLists.txt | 2 +- .../dsl/acir_format/recursion_constraint.cpp | 11 +- .../plonk/composer/ultra_composer.test.cpp | 8 +- .../stdlib/encryption/ecdsa/ecdsa_impl.hpp | 5 +- .../ultra_recursive_verifier.cpp | 4 +- .../aggregation_state/aggregation_state.hpp | 10 +- .../plonk_recursion/verifier/verifier.hpp | 2 +- .../stdlib/primitives/bigfield/bigfield.hpp | 185 ++++- .../primitives/bigfield/bigfield.test.cpp | 269 ++++++- .../primitives/bigfield/bigfield_impl.hpp | 738 +++++++++++------- .../primitives/bigfield/goblin_field.hpp | 10 +- .../primitives/biggroup/biggroup_impl.hpp | 4 +- .../primitives/biggroup/biggroup_tables.hpp | 11 +- .../stdlib/primitives/bool/bool.hpp | 2 + .../stdlib/primitives/databus/databus.hpp | 2 +- .../stdlib/primitives/field/field.cpp | 25 +- .../stdlib/primitives/field/field.hpp | 20 +- .../stdlib/primitives/field/field.test.cpp | 16 + .../primitives/plookup/plookup.test.cpp | 12 +- .../translator_recursive_verifier.cpp | 3 +- .../ultra_circuit_builder.cpp | 37 +- .../ultra_circuit_builder.hpp | 47 +- .../ultra_honk/ultra_honk.test.cpp | 8 +- 24 files changed, 991 insertions(+), 456 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/circuit_checker/ultra_circuit_builder.test.cpp b/barretenberg/cpp/src/barretenberg/circuit_checker/ultra_circuit_builder.test.cpp index e7a3b8eaf52..ae2b514f434 100644 --- a/barretenberg/cpp/src/barretenberg/circuit_checker/ultra_circuit_builder.test.cpp +++ b/barretenberg/cpp/src/barretenberg/circuit_checker/ultra_circuit_builder.test.cpp @@ -611,22 +611,20 @@ TEST(UltraCircuitConstructor, NonNativeFieldMultiplication) const auto split_into_limbs = [&](const uint512_t& input) { constexpr size_t NUM_BITS = 68; - std::array limbs; + std::array limbs; limbs[0] = input.slice(0, NUM_BITS).lo; limbs[1] = input.slice(NUM_BITS * 1, NUM_BITS * 2).lo; limbs[2] = input.slice(NUM_BITS * 2, NUM_BITS * 3).lo; limbs[3] = input.slice(NUM_BITS * 3, NUM_BITS * 4).lo; - limbs[4] = fr(input.lo); return limbs; }; - const auto get_limb_witness_indices = [&](const std::array& limbs) { - std::array limb_indices; + const auto get_limb_witness_indices = [&](const std::array& limbs) { + std::array limb_indices; limb_indices[0] = circuit_constructor.add_variable(limbs[0]); limb_indices[1] = circuit_constructor.add_variable(limbs[1]); limb_indices[2] = circuit_constructor.add_variable(limbs[2]); limb_indices[3] = circuit_constructor.add_variable(limbs[3]); - limb_indices[4] = circuit_constructor.add_variable(limbs[4]); return limb_indices; }; const uint512_t BINARY_BASIS_MODULUS = uint512_t(1) << (68 * 4); @@ -671,22 +669,20 @@ TEST(UltraCircuitConstructor, NonNativeFieldMultiplicationSortCheck) const auto split_into_limbs = [&](const uint512_t& input) { constexpr size_t NUM_BITS = 68; - std::array limbs; + std::array limbs; limbs[0] = input.slice(0, NUM_BITS).lo; limbs[1] = input.slice(NUM_BITS * 1, NUM_BITS * 2).lo; limbs[2] = input.slice(NUM_BITS * 2, NUM_BITS * 3).lo; limbs[3] = input.slice(NUM_BITS * 3, NUM_BITS * 4).lo; - limbs[4] = fr(input.lo); return limbs; }; - const auto get_limb_witness_indices = [&](const std::array& limbs) { - std::array limb_indices; + const auto get_limb_witness_indices = [&](const std::array& limbs) { + std::array limb_indices; limb_indices[0] = circuit_constructor.add_variable(limbs[0]); limb_indices[1] = circuit_constructor.add_variable(limbs[1]); limb_indices[2] = circuit_constructor.add_variable(limbs[2]); limb_indices[3] = circuit_constructor.add_variable(limbs[3]); - limb_indices[4] = circuit_constructor.add_variable(limbs[4]); return limb_indices; }; const uint512_t BINARY_BASIS_MODULUS = uint512_t(1) << (68 * 4); diff --git a/barretenberg/cpp/src/barretenberg/dsl/CMakeLists.txt b/barretenberg/cpp/src/barretenberg/dsl/CMakeLists.txt index 2ddd66ceb49..1f44c3c2746 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/CMakeLists.txt +++ b/barretenberg/cpp/src/barretenberg/dsl/CMakeLists.txt @@ -13,7 +13,7 @@ set(DSL_DEPENDENCIES stdlib_schnorr stdlib_honk_verifier) -if (NOT WASM) +if (NOT WASM AND NOT DISABLE_AZTEC_VM) list(APPEND DSL_DEPENDENCIES libdeflate::libdeflate_static vm) endif() diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp index f0f1fe1bff2..0015e9390ae 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -114,11 +114,12 @@ AggregationObjectIndices create_recursion_constraints(Builder& builder, if (!inner_aggregation_indices_all_zero) { std::array aggregation_elements; for (size_t i = 0; i < 4; ++i) { - aggregation_elements[i] = - bn254::BaseField(field_ct::from_witness_index(&builder, aggregation_input[4 * i]), - field_ct::from_witness_index(&builder, aggregation_input[4 * i + 1]), - field_ct::from_witness_index(&builder, aggregation_input[4 * i + 2]), - field_ct::from_witness_index(&builder, aggregation_input[4 * i + 3])); + aggregation_elements[i] = bn254::BaseField::construct_from_limbs( + field_ct::from_witness_index(&builder, aggregation_input[4 * i]), + field_ct::from_witness_index(&builder, aggregation_input[4 * i + 1]), + field_ct::from_witness_index(&builder, aggregation_input[4 * i + 2]), + field_ct::from_witness_index(&builder, aggregation_input[4 * i + 3])); + aggregation_elements[i].assert_is_in_field(); } // If we have a previous aggregation object, assign it to `previous_aggregation` so that it is included diff --git a/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.test.cpp b/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.test.cpp index e46237a4306..bf20e3800ca 100644 --- a/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.test.cpp +++ b/barretenberg/cpp/src/barretenberg/plonk/composer/ultra_composer.test.cpp @@ -593,22 +593,20 @@ TYPED_TEST(ultra_plonk_composer, non_native_field_multiplication) const auto split_into_limbs = [&](const uint512_t& input) { constexpr size_t NUM_BITS = 68; - std::array limbs; + std::array limbs; limbs[0] = input.slice(0, NUM_BITS).lo; limbs[1] = input.slice(NUM_BITS * 1, NUM_BITS * 2).lo; limbs[2] = input.slice(NUM_BITS * 2, NUM_BITS * 3).lo; limbs[3] = input.slice(NUM_BITS * 3, NUM_BITS * 4).lo; - limbs[4] = fr(input.lo); return limbs; }; - const auto get_limb_witness_indices = [&](const std::array& limbs) { - std::array limb_indices; + const auto get_limb_witness_indices = [&](const std::array& limbs) { + std::array limb_indices; limb_indices[0] = builder.add_variable(limbs[0]); limb_indices[1] = builder.add_variable(limbs[1]); limb_indices[2] = builder.add_variable(limbs[2]); limb_indices[3] = builder.add_variable(limbs[3]); - limb_indices[4] = builder.add_variable(limbs[4]); return limb_indices; }; const uint512_t BINARY_BASIS_MODULUS = uint512_t(1) << (68 * 4); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa_impl.hpp b/barretenberg/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa_impl.hpp index 02c69d8f770..aefc1a2316b 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa_impl.hpp @@ -82,8 +82,9 @@ bool_t ecdsa_verify_signature(const stdlib::byte_array& messag // Read more about this at: https://www.derpturkey.com/inherent-malleability-of-ecdsa-signatures/amp/ s.assert_less_than((Fr::modulus + 1) / 2); - Fr u1 = z / s; - Fr u2 = r / s; + // We already checked that s is nonzero + Fr u1 = z.div_without_denominator_check(s); + Fr u2 = r.div_without_denominator_check(s); public_key.validate_on_curve(); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.cpp b/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.cpp index bf1ac000ac9..aa2c057b8b9 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.cpp @@ -70,8 +70,8 @@ UltraRecursiveVerifier_::AggregationObject UltraRecursiveVerifier_public_inputs[key->recursive_proof_public_input_indices[idx]]; idx++; } - base_field_vals[j] = - typename Curve::BaseField(bigfield_limbs[0], bigfield_limbs[1], bigfield_limbs[2], bigfield_limbs[3]); + base_field_vals[j] = Curve::BaseField::construct_from_limbs( + bigfield_limbs[0], bigfield_limbs[1], bigfield_limbs[2], bigfield_limbs[3]); } nested_pairing_points[i] = typename Curve::Group(base_field_vals[0], base_field_vals[1]); } diff --git a/barretenberg/cpp/src/barretenberg/stdlib/plonk_recursion/aggregation_state/aggregation_state.hpp b/barretenberg/cpp/src/barretenberg/stdlib/plonk_recursion/aggregation_state/aggregation_state.hpp index 52bbdd10dab..ced1a04cdb7 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/plonk_recursion/aggregation_state/aggregation_state.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/plonk_recursion/aggregation_state/aggregation_state.hpp @@ -109,11 +109,11 @@ aggregation_state convert_witness_indices_to_agg_obj(Builder& builder, { std::array aggregation_elements; for (size_t i = 0; i < 4; ++i) { - aggregation_elements[i] = - typename Curve::BaseField(Curve::ScalarField::from_witness_index(&builder, witness_indices[4 * i]), - Curve::ScalarField::from_witness_index(&builder, witness_indices[4 * i + 1]), - Curve::ScalarField::from_witness_index(&builder, witness_indices[4 * i + 2]), - Curve::ScalarField::from_witness_index(&builder, witness_indices[4 * i + 3])); + aggregation_elements[i] = Curve::BaseField::construct_from_limbs( + Curve::ScalarField::from_witness_index(&builder, witness_indices[4 * i]), + Curve::ScalarField::from_witness_index(&builder, witness_indices[4 * i + 1]), + Curve::ScalarField::from_witness_index(&builder, witness_indices[4 * i + 2]), + Curve::ScalarField::from_witness_index(&builder, witness_indices[4 * i + 3])); aggregation_elements[i].assert_is_in_field(); } diff --git a/barretenberg/cpp/src/barretenberg/stdlib/plonk_recursion/verifier/verifier.hpp b/barretenberg/cpp/src/barretenberg/stdlib/plonk_recursion/verifier/verifier.hpp index df0bffaf55e..b1fa8239519 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/plonk_recursion/verifier/verifier.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/plonk_recursion/verifier/verifier.hpp @@ -345,7 +345,7 @@ aggregation_state verify_proof_(typename Curve::Builder* context, l1.create_range_constraint(fq_ct::NUM_LIMB_BITS, "l1"); l2.create_range_constraint(fq_ct::NUM_LIMB_BITS, "l2"); l3.create_range_constraint(fq_ct::NUM_LAST_LIMB_BITS, "l3"); - return fq_ct(l0, l1, l2, l3, false); + return fq_ct::unsafe_construct_from_limbs(l0, l1, l2, l3, false); }; fr_ct recursion_separator_challenge = transcript.get_challenge_field_element("separator", 2); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp index 0eb5d90a7fe..dd3011149d8 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp @@ -25,15 +25,14 @@ template class bigfield { struct Limb { Limb() {} - Limb(const field_t& input, const uint256_t max = uint256_t(0)) + Limb(const field_t& input, const uint256_t& max = DEFAULT_MAXIMUM_LIMB) : element(input) { - if (input.witness_index == IS_CONSTANT) { - maximum_value = uint256_t(input.additive_constant) + 1; - } else if (max != uint256_t(0)) { - maximum_value = max; + if (input.is_constant()) { + maximum_value = uint256_t(input.additive_constant); + ASSERT(maximum_value <= max); } else { - maximum_value = DEFAULT_MAXIMUM_LIMB; + maximum_value = max; } } friend std::ostream& operator<<(std::ostream& os, const Limb& a) @@ -95,40 +94,104 @@ template class bigfield { : bigfield(nullptr, uint256_t(value)) {} - // we assume the limbs have already been normalized! - bigfield(const field_t& a, - const field_t& b, - const field_t& c, - const field_t& d, - const bool can_overflow = false) + /** + * @brief Construct a bigfield element from binary limbs that are already reduced + * + * @details This API should only be used by bigfield and other stdlib members for efficiency and with extreme care. + * We need it in cases where we precompute and reduce the elements, for example, and then put them in a table + * + */ + static bigfield unsafe_construct_from_limbs(const field_t& a, + const field_t& b, + const field_t& c, + const field_t& d, + const bool can_overflow = false) { - context = a.context; - binary_basis_limbs[0] = Limb(field_t(a)); - binary_basis_limbs[1] = Limb(field_t(b)); - binary_basis_limbs[2] = Limb(field_t(c)); - binary_basis_limbs[3] = + ASSERT(a.is_constant() == b.is_constant() && b.is_constant() == c.is_constant() && + c.is_constant() == d.is_constant()); + bigfield result; + result.context = a.context; + result.binary_basis_limbs[0] = Limb(field_t(a)); + result.binary_basis_limbs[1] = Limb(field_t(b)); + result.binary_basis_limbs[2] = Limb(field_t(c)); + result.binary_basis_limbs[3] = Limb(field_t(d), can_overflow ? DEFAULT_MAXIMUM_LIMB : DEFAULT_MAXIMUM_MOST_SIGNIFICANT_LIMB); - prime_basis_limb = - (binary_basis_limbs[3].element * shift_3) - .add_two(binary_basis_limbs[2].element * shift_2, binary_basis_limbs[1].element * shift_1); - prime_basis_limb += (binary_basis_limbs[0].element); + result.prime_basis_limb = (result.binary_basis_limbs[3].element * shift_3) + .add_two(result.binary_basis_limbs[2].element * shift_2, + result.binary_basis_limbs[1].element * shift_1); + result.prime_basis_limb += (result.binary_basis_limbs[0].element); + return result; }; - // we assume the limbs have already been normalized! - bigfield(const field_t& a, - const field_t& b, - const field_t& c, - const field_t& d, - const field_t& prime_limb, - const bool can_overflow = false) + /** + * @brief Construct a bigfield element from binary limbs that are already reduced and ensure they are range + * constrained + * + */ + static bigfield construct_from_limbs(const field_t& a, + const field_t& b, + const field_t& c, + const field_t& d, + const bool can_overflow = false) { - context = a.context; - binary_basis_limbs[0] = Limb(field_t(a)); - binary_basis_limbs[1] = Limb(field_t(b)); - binary_basis_limbs[2] = Limb(field_t(c)); - binary_basis_limbs[3] = + ASSERT(a.is_constant() == b.is_constant() && b.is_constant() == c.is_constant() && + c.is_constant() == d.is_constant()); + bigfield result; + auto ctx = a.context; + result.context = a.context; + result.binary_basis_limbs[0] = Limb(field_t(a)); + result.binary_basis_limbs[1] = Limb(field_t(b)); + result.binary_basis_limbs[2] = Limb(field_t(c)); + result.binary_basis_limbs[3] = Limb(field_t(d), can_overflow ? DEFAULT_MAXIMUM_LIMB : DEFAULT_MAXIMUM_MOST_SIGNIFICANT_LIMB); - prime_basis_limb = prime_limb; + result.prime_basis_limb = (result.binary_basis_limbs[3].element * shift_3) + .add_two(result.binary_basis_limbs[2].element * shift_2, + result.binary_basis_limbs[1].element * shift_1); + result.prime_basis_limb += (result.binary_basis_limbs[0].element); + const size_t num_last_limb_bits = (can_overflow) ? NUM_LIMB_BITS : NUM_LAST_LIMB_BITS; + if constexpr (HasPlookup) { + ctx->range_constrain_two_limbs(result.binary_basis_limbs[0].element.get_normalized_witness_index(), + result.binary_basis_limbs[1].element.get_normalized_witness_index(), + static_cast(NUM_LIMB_BITS), + static_cast(NUM_LIMB_BITS)); + ctx->range_constrain_two_limbs(result.binary_basis_limbs[2].element.get_normalized_witness_index(), + result.binary_basis_limbs[3].element.get_normalized_witness_index(), + static_cast(NUM_LIMB_BITS), + static_cast(num_last_limb_bits)); + + } else { + a.create_range_constraint(NUM_LIMB_BITS); + b.create_range_constraint(NUM_LIMB_BITS); + c.create_range_constraint(NUM_LIMB_BITS); + d.create_range_constraint(num_last_limb_bits); + } + return result; + }; + /** + * @brief Construct a bigfield element from binary limbs and a prime basis limb that are already reduced + * + * @details This API should only be used by bigfield and other stdlib members for efficiency and with extreme care. + * We need it in cases where we precompute and reduce the elements, for example, and then put them in a table + * + */ + static bigfield unsafe_construct_from_limbs(const field_t& a, + const field_t& b, + const field_t& c, + const field_t& d, + const field_t& prime_limb, + const bool can_overflow = false) + { + ASSERT(a.is_constant() == b.is_constant() && b.is_constant() == c.is_constant() && + c.is_constant() == d.is_constant() && d.is_constant() == prime_limb.is_constant()); + bigfield result; + result.context = a.context; + result.binary_basis_limbs[0] = Limb(field_t(a)); + result.binary_basis_limbs[1] = Limb(field_t(b)); + result.binary_basis_limbs[2] = Limb(field_t(c)); + result.binary_basis_limbs[3] = + Limb(field_t(d), can_overflow ? DEFAULT_MAXIMUM_LIMB : DEFAULT_MAXIMUM_MOST_SIGNIFICANT_LIMB); + result.prime_basis_limb = prime_limb; + return result; }; bigfield(const byte_array& bytes); @@ -155,6 +218,9 @@ template class bigfield { static constexpr uint512_t modulus_u512 = uint512_t(modulus); static constexpr uint64_t NUM_LIMB_BITS = NUM_LIMB_BITS_IN_FIELD_SIMULATION; static constexpr uint64_t NUM_LAST_LIMB_BITS = modulus_u512.get_msb() + 1 - (NUM_LIMB_BITS * 3); + // The quotient reduction checks currently only support >=250 bit moduli and moduli >256 have never been tested + // (Check zkSecurity audit report issue #12 for explanation) + static_assert(modulus_u512.get_msb() + 1 >= 250 && modulus_u512.get_msb() + 1 <= 256); static constexpr uint1024_t DEFAULT_MAXIMUM_REMAINDER = (uint1024_t(1) << (NUM_LIMB_BITS * 3 + NUM_LAST_LIMB_BITS)) - uint1024_t(1); static constexpr uint256_t DEFAULT_MAXIMUM_LIMB = (uint256_t(1) << NUM_LIMB_BITS) - uint256_t(1); @@ -163,6 +229,10 @@ template class bigfield { static constexpr uint64_t LOG2_BINARY_MODULUS = NUM_LIMB_BITS * NUM_LIMBS; static constexpr bool is_composite = true; // false only when fr is native + // This limits the size of all vectors that are being used to 16 (we don't really need more) + static constexpr size_t MAXIMUM_SUMMAND_COUNT_LOG = 4; + static constexpr size_t MAXIMUM_SUMMAND_COUNT = 1 << MAXIMUM_SUMMAND_COUNT_LOG; + static constexpr uint256_t prime_basis_maximum_limb = uint256_t(modulus_u512.slice(NUM_LIMB_BITS * (NUM_LIMBS - 1), NUM_LIMB_BITS* NUM_LIMBS)); static constexpr Basis prime_basis{ uint512_t(bb::fr::modulus), bb::fr::modulus.get_msb() + 1 }; @@ -191,6 +261,8 @@ template class bigfield { byte_array to_byte_array() const { byte_array result(get_context()); + // Prevents aliases + assert_is_in_field(); field_t lo = binary_basis_limbs[0].element + (binary_basis_limbs[1].element * shift_1); field_t hi = binary_basis_limbs[2].element + (binary_basis_limbs[3].element * shift_1); // n.b. this only works if NUM_LIMB_BITS * 2 is divisible by 8 @@ -285,6 +357,7 @@ template class bigfield { bool check_for_zero); static bigfield div_without_denominator_check(const std::vector& numerators, const bigfield& denominator); + bigfield div_without_denominator_check(const bigfield& denominator); static bigfield div_check_denominator_nonzero(const std::vector& numerators, const bigfield& denominator); bigfield conditional_negate(const bool_t& predicate) const; @@ -392,14 +465,25 @@ template class bigfield { prime_basis_limb.tag); } - static constexpr uint512_t get_maximum_unreduced_value(const size_t num_products = 1) + static constexpr uint512_t get_maximum_unreduced_value() { - // return (uint512_t(1) << 256); - uint1024_t maximum_product = uint1024_t(binary_basis.modulus) * uint1024_t(prime_basis.modulus) / - uint1024_t(static_cast(num_products)); + // This = `T * n = 2^272 * |BN(Fr)|` So this equals n*2^t + + uint1024_t maximum_product = uint1024_t(binary_basis.modulus) * uint1024_t(prime_basis.modulus); // TODO: compute square root (the following is a lower bound, so good for the CRT use) + // We use -1 to stay safer, because it provides additional space to avoid the overflow, but get_msb() by itself + // should be enough + uint64_t maximum_product_bits = maximum_product.get_msb() - 1; + return (uint512_t(1) << (maximum_product_bits >> 1)); + } + + // If we encounter this maximum value of a bigfield we stop execution + static constexpr uint512_t get_prohibited_maximum_value() + { + uint1024_t maximum_product = uint1024_t(binary_basis.modulus) * uint1024_t(prime_basis.modulus); uint64_t maximum_product_bits = maximum_product.get_msb() - 1; - return (uint512_t(1) << (maximum_product_bits >> 1)) - uint512_t(1); + const size_t arbitrary_secure_margin = 20; + return (uint512_t(1) << ((maximum_product_bits >> 1) + arbitrary_secure_margin)) - uint512_t(1); } static constexpr uint1024_t get_maximum_crt_product() @@ -501,11 +585,24 @@ template class bigfield { static constexpr uint64_t MAX_ADDITION_LOG = 10; // the rationale of the expression is we should not overflow Fr when applying any bigfield operation (e.g. *) and // starting with this max limb size - static constexpr uint64_t MAX_UNREDUCED_LIMB_SIZE = (bb::fr::modulus.get_msb() + 1) / 2 - MAX_ADDITION_LOG; - static constexpr uint256_t get_maximum_unreduced_limb_value() { return uint256_t(1) << MAX_UNREDUCED_LIMB_SIZE; } + static constexpr uint64_t MAXIMUM_SIZE_THAT_WOULDNT_OVERFLOW = + (bb::fr::modulus.get_msb() - MAX_ADDITION_LOG - NUM_LIMB_BITS) / 2; + + // If the logarithm of the maximum value of a limb is more than this, we need to reduce + static constexpr uint64_t MAX_UNREDUCED_LIMB_BITS = + NUM_LIMB_BITS + 10; // We allowa an element to be added to itself 10 times. There is no actual usecase - static_assert(MAX_UNREDUCED_LIMB_SIZE < (NUM_LIMB_BITS * 2)); + static constexpr uint64_t PROHIBITED_LIMB_BITS = + MAX_UNREDUCED_LIMB_BITS + + 5; // Shouldn't be reachable through addition, reduction should happen earlier. If we detect this, we stop + + static constexpr uint256_t get_maximum_unreduced_limb_value() { return uint256_t(1) << MAX_UNREDUCED_LIMB_BITS; } + + // If we encounter this maximum value of a limb we stop execution + static constexpr uint256_t get_prohibited_maximum_limb_value() { return uint256_t(1) << PROHIBITED_LIMB_BITS; } + + static_assert(PROHIBITED_LIMB_BITS < MAXIMUM_SIZE_THAT_WOULDNT_OVERFLOW); private: static std::pair compute_quotient_remainder_values(const bigfield& a, @@ -560,7 +657,9 @@ template class bigfield { const bigfield& right, const bigfield& quotient, const bigfield& remainder); - void reduction_check(const size_t num_products = 1) const; + void reduction_check() const; + + void sanity_check() const; }; // namespace stdlib diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.test.cpp index b2194698bf8..81949bb40e9 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.test.cpp @@ -41,6 +41,35 @@ template class stdlib_bigfield : public testing::Test { typedef typename bn254::witness_ct witness_ct; public: + static void test_add_to_lower_limb_regression() + { + auto builder = Builder(); + fq_ct constant = fq_ct(1); + fq_ct var = fq_ct::create_from_u512_as_witness(&builder, 1); + fr_ct small_var = witness_ct(&builder, fr(1)); + fq_ct mixed = fq_ct(1).add_to_lower_limb(small_var, 1); + fq_ct r; + + r = mixed + mixed; + r = mixed - mixed; + r = mixed + var; + r = mixed + constant; + r = mixed - var; + r = mixed - constant; + r = var - mixed; + + r = var * constant; + r = constant / var; + r = constant * constant; + r = constant / constant; + + r = mixed * var; + r = mixed / var; + r = mixed * mixed; + r = mixed * constant; + bool result = CircuitChecker::check(builder); + EXPECT_EQ(result, true); + } // The bug happens when we are applying the CRT formula to a*b < r, which can happen when using the division // operator static void test_division_formula_bug() @@ -254,7 +283,7 @@ template class stdlib_bigfield : public testing::Test { { auto builder = Builder(); size_t num_repetitions = 1; - const size_t number_of_madds = 32; + const size_t number_of_madds = 16; for (size_t i = 0; i < num_repetitions; ++i) { fq mul_left_values[number_of_madds]; fq mul_right_values[number_of_madds]; @@ -973,10 +1002,10 @@ template class stdlib_bigfield : public testing::Test { witness_ct(&builder, fr(uint256_t(inputs[3]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct two(witness_ct(&builder, fr(2)), - witness_ct(&builder, fr(0)), - witness_ct(&builder, fr(0)), - witness_ct(&builder, fr(0))); + fq_ct two = fq_ct::unsafe_construct_from_limbs(witness_ct(&builder, fr(2)), + witness_ct(&builder, fr(0)), + witness_ct(&builder, fr(0)), + witness_ct(&builder, fr(0))); fq_ct t0 = a + a; fq_ct t1 = a * two; @@ -998,31 +1027,219 @@ template class stdlib_bigfield : public testing::Test { fq base_val(engine.get_random_uint256()); uint32_t exponent_val = engine.get_random_uint32(); - fq expected = base_val.pow(exponent_val); + // Set the high bit + exponent_val |= static_cast(1) << 31; + fq_ct base_constant(&builder, static_cast(base_val)); + auto base_witness = fq_ct::from_witness(&builder, static_cast(base_val)); + // This also tests for the case where the exponent is zero + for (size_t i = 0; i <= 32; i += 4) { + auto current_exponent_val = exponent_val >> i; + fq expected = base_val.pow(current_exponent_val); + + // Check for constant bigfield element with constant exponent + fq_ct result_constant_base = base_constant.pow(current_exponent_val); + EXPECT_EQ(fq(result_constant_base.get_value()), expected); + + // Check for witness exponent with constant base + fr_ct witness_exponent = witness_ct(&builder, current_exponent_val); + auto result_witness_exponent = base_constant.pow(witness_exponent); + EXPECT_EQ(fq(result_witness_exponent.get_value()), expected); + + // Check for witness base with constant exponent + fq_ct result_witness_base = base_witness.pow(current_exponent_val); + EXPECT_EQ(fq(result_witness_base.get_value()), expected); + + // Check for all witness + base_witness.set_origin_tag(submitted_value_origin_tag); + witness_exponent.set_origin_tag(challenge_origin_tag); + + fq_ct result_all_witness = base_witness.pow(witness_exponent); + EXPECT_EQ(fq(result_all_witness.get_value()), expected); + EXPECT_EQ(result_all_witness.get_origin_tag(), first_two_merged_tag); + } + + bool check_result = CircuitChecker::check(builder); + EXPECT_EQ(check_result, true); + } + + static void test_pow_one() + { + Builder builder; + + fq base_val(engine.get_random_uint256()); + + uint32_t current_exponent_val = 1; + fq_ct base_constant(&builder, static_cast(base_val)); + auto base_witness = fq_ct::from_witness(&builder, static_cast(base_val)); + fq expected = base_val.pow(current_exponent_val); + + // Check for constant bigfield element with constant exponent + fq_ct result_constant_base = base_constant.pow(current_exponent_val); + EXPECT_EQ(fq(result_constant_base.get_value()), expected); - fq_ct base(&builder, static_cast(base_val)); - fq_ct result = base.pow(exponent_val); - EXPECT_EQ(fq(result.get_value()), expected); + // Check for witness exponent with constant base + fr_ct witness_exponent = witness_ct(&builder, current_exponent_val); + auto result_witness_exponent = base_constant.pow(witness_exponent); + EXPECT_EQ(fq(result_witness_exponent.get_value()), expected); - fr_ct exponent = witness_ct(&builder, exponent_val); - base.set_origin_tag(submitted_value_origin_tag); - exponent.set_origin_tag(challenge_origin_tag); - result = base.pow(exponent); - EXPECT_EQ(fq(result.get_value()), expected); + // Check for witness base with constant exponent + fq_ct result_witness_base = base_witness.pow(current_exponent_val); + EXPECT_EQ(fq(result_witness_base.get_value()), expected); - EXPECT_EQ(result.get_origin_tag(), first_two_merged_tag); + // Check for all witness + fq_ct result_all_witness = base_witness.pow(witness_exponent); + EXPECT_EQ(fq(result_all_witness.get_value()), expected); - info("num gates = ", builder.get_estimated_num_finalized_gates()); bool check_result = CircuitChecker::check(builder); EXPECT_EQ(check_result, true); } + + static void test_nonnormalized_field_bug_regression() + { + auto builder = Builder(); + fr_ct zero = witness_ct::create_constant_witness(&builder, fr::zero()); + uint256_t two_to_68 = uint256_t(1) << fq_ct::NUM_LIMB_BITS; + // construct bigfield where the low limb has a non-trivial `additive_constant` + fq_ct z(zero + two_to_68, zero); + // assert invariant for every limb: actual value <= maximum value + // Failed in the past for for StandardCircuitBuilder + for (auto zi : z.binary_basis_limbs) { + EXPECT_LE(uint256_t(zi.element.get_value()), zi.maximum_value); + } + } + + static void test_msub_div_ctx_crash_regression() + { + auto builder = Builder(); + fq_ct witness_one = fq_ct::create_from_u512_as_witness(&builder, uint256_t(1)); + fq_ct constant_one(1); + fq_ct::msub_div({ witness_one }, { witness_one }, constant_one, { witness_one }, true); + bool result = CircuitChecker::check(builder); + EXPECT_EQ(result, true); + } + + static void test_internal_div_regression() + { + typedef stdlib::bool_t bool_t; + auto builder = Builder(); + + fq_ct w0 = fq_ct::from_witness(&builder, 1); + w0 = w0.conditional_negate(bool_t(&builder, true)); + w0 = w0.conditional_negate(bool_t(&builder, false)); + w0 = w0.conditional_negate(bool_t(&builder, true)); + w0 = w0.conditional_negate(bool_t(&builder, true)); + fq_ct w4 = w0.conditional_negate(bool_t(&builder, false)); + w4 = w4.conditional_negate(bool_t(&builder, true)); + w4 = w4.conditional_negate(bool_t(&builder, true)); + fq_ct w5 = w4 - w0; + fq_ct w6 = w5 / 1; + (void)(w6); + EXPECT_TRUE(CircuitChecker::check(builder)); + } + + static void test_internal_div_regression2() + { + auto builder = Builder(); + + fq_ct numerator = fq_ct::create_from_u512_as_witness(&builder, uint256_t(1) << (68 + 67)); + numerator.binary_basis_limbs[0].maximum_value = 0; + numerator.binary_basis_limbs[1].maximum_value = uint256_t(1) << 67; + numerator.binary_basis_limbs[2].maximum_value = 0; + numerator.binary_basis_limbs[3].maximum_value = 0; + + for (size_t i = 0; i < 9; i++) { + numerator = numerator + numerator; + } + fq_ct denominator = fq_ct::create_from_u512_as_witness(&builder, uint256_t(1)); + fq_ct result = numerator / denominator; + (void)(result); + EXPECT_TRUE(CircuitChecker::check(builder)); + } + + static void test_internal_div_regression3() + { + Builder builder; + uint256_t dlimb0_value = uint256_t("0x00000000000000000000000000000000000000000000000bef7fa109038857fc"); + uint256_t dlimb0_max = uint256_t("0x00000000000000000000000000000000000000000000000fffffffffffffffff"); + uint256_t dlimb1_value = uint256_t("0x0000000000000000000000000000000000000000000000056f10535779f56339"); + uint256_t dlimb1_max = uint256_t("0x00000000000000000000000000000000000000000000000fffffffffffffffff"); + uint256_t dlimb2_value = uint256_t("0x00000000000000000000000000000000000000000000000c741f60a1ec4e114e"); + uint256_t dlimb2_max = uint256_t("0x00000000000000000000000000000000000000000000000fffffffffffffffff"); + uint256_t dlimb3_value = uint256_t("0x000000000000000000000000000000000000000000000000000286b3cd344d8b"); + uint256_t dlimb3_max = uint256_t("0x0000000000000000000000000000000000000000000000000003ffffffffffff"); + uint256_t dlimb_prime = uint256_t("0x286b3cd344d8bc741f60a1ec4e114e56f10535779f56339bef7fa109038857fc"); + + uint256_t nlimb0_value = uint256_t("0x00000000000000000000000000000000000000000000080a84d9bea2b012417c"); + uint256_t nlimb0_max = uint256_t("0x000000000000000000000000000000000000000000000ff7c7469df4081b61fc"); + uint256_t nlimb1_value = uint256_t("0x00000000000000000000000000000000000000000000080f50ee84526e8e5ba7"); + uint256_t nlimb1_max = uint256_t("0x000000000000000000000000000000000000000000000ffef965c67ba5d5893c"); + uint256_t nlimb2_value = uint256_t("0x00000000000000000000000000000000000000000000080aba136ca8eaf6dc1b"); + uint256_t nlimb2_max = uint256_t("0x000000000000000000000000000000000000000000000ff8171d22fd607249ea"); + uint256_t nlimb3_value = uint256_t("0x00000000000000000000000000000000000000000000000001f0042419843c29"); + uint256_t nlimb3_max = uint256_t("0x00000000000000000000000000000000000000000000000003e00636264659ff"); + uint256_t nlimb_prime = uint256_t("0x000000000000000000000000000000474da776b8ee19a56b08186bdcf01240d8"); + + fq_ct w0 = fq_ct::from_witness(&builder, bb::fq(0)); + w0.binary_basis_limbs[0].element = witness_ct(&builder, dlimb0_value); + w0.binary_basis_limbs[1].element = witness_ct(&builder, dlimb1_value); + w0.binary_basis_limbs[2].element = witness_ct(&builder, dlimb2_value); + w0.binary_basis_limbs[3].element = witness_ct(&builder, dlimb3_value); + w0.binary_basis_limbs[0].maximum_value = dlimb0_max; + w0.binary_basis_limbs[1].maximum_value = dlimb1_max; + w0.binary_basis_limbs[2].maximum_value = dlimb2_max; + w0.binary_basis_limbs[3].maximum_value = dlimb3_max; + w0.prime_basis_limb = witness_ct(&builder, dlimb_prime); + + fq_ct w1 = fq_ct::from_witness(&builder, bb::fq(0)); + w1.binary_basis_limbs[0].element = witness_ct(&builder, nlimb0_value); + w1.binary_basis_limbs[1].element = witness_ct(&builder, nlimb1_value); + w1.binary_basis_limbs[2].element = witness_ct(&builder, nlimb2_value); + w1.binary_basis_limbs[3].element = witness_ct(&builder, nlimb3_value); + w1.binary_basis_limbs[0].maximum_value = nlimb0_max; + w1.binary_basis_limbs[1].maximum_value = nlimb1_max; + w1.binary_basis_limbs[2].maximum_value = nlimb2_max; + w1.binary_basis_limbs[3].maximum_value = nlimb3_max; + w1.prime_basis_limb = witness_ct(&builder, nlimb_prime); + + fq_ct w2 = w1 / w0; + (void)w2; + EXPECT_TRUE(CircuitChecker::check(builder)); + } + static void test_assert_not_equal_regression() + { + auto builder = Builder(); + fq_ct zero = fq_ct::create_from_u512_as_witness(&builder, uint256_t(0)); + fq_ct alsozero = fq_ct::create_from_u512_as_witness(&builder, fq_ct::modulus_u512); + for (size_t i = 0; i < 4; i++) { + zero.binary_basis_limbs[i].maximum_value = zero.binary_basis_limbs[i].element.get_value(); + alsozero.binary_basis_limbs[i].maximum_value = alsozero.binary_basis_limbs[i].element.get_value(); + } + zero.assert_is_not_equal(alsozero); + bool result = CircuitChecker::check(builder); + EXPECT_EQ(result, false); + } }; // Define types for which the above tests will be constructed. using CircuitTypes = testing::Types; // Define the suite of tests. TYPED_TEST_SUITE(stdlib_bigfield, CircuitTypes); + +TYPED_TEST(stdlib_bigfield, assert_not_equal_regression) +{ + TestFixture::test_assert_not_equal_regression(); +} + +TYPED_TEST(stdlib_bigfield, add_to_lower_limb_regression) +{ + TestFixture::test_add_to_lower_limb_regression(); +} TYPED_TEST(stdlib_bigfield, badmul) +{ + TestFixture::test_bad_mul(); +} + +TYPED_TEST(stdlib_bigfield, division_formula_regression) { TestFixture::test_division_formula_bug(); } @@ -1070,6 +1287,10 @@ TYPED_TEST(stdlib_bigfield, msub_div) { TestFixture::test_msub_div(); } +TYPED_TEST(stdlib_bigfield, msb_div_ctx_crash_regression) +{ + TestFixture::test_msub_div_ctx_crash_regression(); +} TYPED_TEST(stdlib_bigfield, conditional_negate) { TestFixture::test_conditional_negate(); @@ -1124,6 +1345,22 @@ TYPED_TEST(stdlib_bigfield, pow) TestFixture::test_pow(); } +TYPED_TEST(stdlib_bigfield, pow_one) +{ + TestFixture::test_pow_one(); +} +TYPED_TEST(stdlib_bigfield, nonnormalized_field_bug_regression) +{ + TestFixture::test_nonnormalized_field_bug_regression(); +} + +TYPED_TEST(stdlib_bigfield, internal_div_bug_regression) +{ + TestFixture::test_internal_div_regression(); + TestFixture::test_internal_div_regression2(); + TestFixture::test_internal_div_regression3(); +} + // // This test was disabled before the refactor to use TYPED_TEST's/ // TEST(stdlib_bigfield, DISABLED_test_div_against_constants) // { diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield_impl.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield_impl.hpp index 3f4513773b9..bf522e86ff2 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield_impl.hpp @@ -1,5 +1,6 @@ #pragma once +#include "barretenberg/common/assert.hpp" #include "barretenberg/common/zip_view.hpp" #include "barretenberg/numeric/uint256/uint256.hpp" #include "barretenberg/numeric/uintx/uintx.hpp" @@ -39,6 +40,7 @@ bigfield::bigfield(const field_t& low_bits_in, const bool can_overflow, const size_t maximum_bitlength) { + ASSERT(low_bits_in.is_constant() == high_bits_in.is_constant()); ASSERT((can_overflow == true && maximum_bitlength == 0) || (can_overflow == false && (maximum_bitlength == 0 || maximum_bitlength > (3 * NUM_LIMB_BITS)))); @@ -51,12 +53,12 @@ bigfield::bigfield(const field_t& low_bits_in, field_t limb_1(context); field_t limb_2(context); field_t limb_3(context); - if (low_bits_in.witness_index != IS_CONSTANT) { + if (!low_bits_in.is_constant()) { std::vector low_accumulator; if constexpr (HasPlookup) { // MERGE NOTE: this was the if constexpr block introduced in ecebe7643 const auto limb_witnesses = - context->decompose_non_native_field_double_width_limb(low_bits_in.normalize().witness_index); + context->decompose_non_native_field_double_width_limb(low_bits_in.get_normalized_witness_index()); limb_0.witness_index = limb_witnesses[0]; limb_1.witness_index = limb_witnesses[1]; field_t::evaluate_linear_identity(low_bits_in, -limb_0, -limb_1 * shift_1, field_t(0)); @@ -73,8 +75,9 @@ bigfield::bigfield(const field_t& low_bits_in, // limb_1 = (low_bits_in - limb_0) * shift_right_1; } else { size_t mid_index; - low_accumulator = context->decompose_into_base4_accumulators( - low_bits_in.witness_index, static_cast(NUM_LIMB_BITS * 2), "bigfield: low_bits_in too large."); + low_accumulator = context->decompose_into_base4_accumulators(low_bits_in.get_normalized_witness_index(), + static_cast(NUM_LIMB_BITS * 2), + "bigfield: low_bits_in too large."); mid_index = static_cast((NUM_LIMB_BITS / 2) - 1); // Range constraint returns an array of partial sums, midpoint will happen to hold the big limb // value @@ -103,23 +106,23 @@ bigfield::bigfield(const field_t& low_bits_in, } // We create the high limb values similar to the low limb ones above const uint64_t num_high_limb_bits = NUM_LIMB_BITS + num_last_limb_bits; - if (high_bits_in.witness_index != IS_CONSTANT) { + if (!high_bits_in.is_constant()) { std::vector high_accumulator; if constexpr (HasPlookup) { const auto limb_witnesses = context->decompose_non_native_field_double_width_limb( - high_bits_in.normalize().witness_index, (size_t)num_high_limb_bits); + high_bits_in.get_normalized_witness_index(), (size_t)num_high_limb_bits); limb_2.witness_index = limb_witnesses[0]; limb_3.witness_index = limb_witnesses[1]; field_t::evaluate_linear_identity(high_bits_in, -limb_2, -limb_3 * shift_1, field_t(0)); } else { - high_accumulator = context->decompose_into_base4_accumulators(high_bits_in.witness_index, + high_accumulator = context->decompose_into_base4_accumulators(high_bits_in.get_normalized_witness_index(), static_cast(num_high_limb_bits), "bigfield: high_bits_in too large."); if constexpr (!IsSimulator) { - limb_3.witness_index = high_accumulator[static_cast((num_last_limb_bits / 2) - 1)]; + limb_3.witness_index = high_accumulator[static_cast(((num_last_limb_bits + 1) / 2) - 1)]; } limb_2 = (high_bits_in - (limb_3 * shift_1)); } @@ -171,8 +174,8 @@ bigfield::bigfield(bigfield&& other) * @param ctx * @param value * @param can_overflow Can the input value have more than log2(modulus) bits? - * @param maximum_bitlength Provide the explicit maximum bitlength if known. Otherwise bigfield max value will be either - * log2(modulus) bits iff can_overflow = false, or (4 * NUM_LIMB_BITS) iff can_overflow = true + * @param maximum_bitlength Provide the explicit maximum bitlength if known. Otherwise bigfield max value will be + * either log2(modulus) bits iff can_overflow = false, or (4 * NUM_LIMB_BITS) iff can_overflow = true * @return bigfield * * @details This method is 1 gate more efficient than constructing from 2 field_ct elements. @@ -204,19 +207,20 @@ bigfield bigfield::create_from_u512_as_witness(Builder* prime_limb.witness_index = ctx->add_variable(limb_0.get_value() + limb_1.get_value() * shift_1 + limb_2.get_value() * shift_2 + limb_3.get_value() * shift_3); // evaluate prime basis limb with addition gate that taps into the 4th wire in the next gate - ctx->create_big_add_gate({ limb_1.witness_index, - limb_2.witness_index, - limb_3.witness_index, - prime_limb.witness_index, + ctx->create_big_add_gate({ limb_1.get_normalized_witness_index(), + limb_2.get_normalized_witness_index(), + limb_3.get_normalized_witness_index(), + prime_limb.get_normalized_witness_index(), shift_1, shift_2, shift_3, -1, 0 }, true); - // TODO(https://github.com/AztecProtocol/barretenberg/issues/879): dummy necessary for preceeding big add gate + // TODO(https://github.com/AztecProtocol/barretenberg/issues/879): dummy necessary for preceeding big add + // gate ctx->create_dummy_gate( - ctx->blocks.arithmetic, ctx->zero_idx, ctx->zero_idx, ctx->zero_idx, limb_0.witness_index); + ctx->blocks.arithmetic, ctx->zero_idx, ctx->zero_idx, ctx->zero_idx, limb_0.get_normalized_witness_index()); uint64_t num_last_limb_bits = (can_overflow) ? NUM_LIMB_BITS : NUM_LAST_LIMB_BITS; @@ -235,10 +239,14 @@ bigfield bigfield::create_from_u512_as_witness(Builder* result.binary_basis_limbs[3].maximum_value = max_limb_value; } result.prime_basis_limb = prime_limb; - ctx->range_constrain_two_limbs( - limb_0.witness_index, limb_1.witness_index, (size_t)NUM_LIMB_BITS, (size_t)NUM_LIMB_BITS); - ctx->range_constrain_two_limbs( - limb_2.witness_index, limb_3.witness_index, (size_t)NUM_LIMB_BITS, (size_t)num_last_limb_bits); + ctx->range_constrain_two_limbs(limb_0.get_normalized_witness_index(), + limb_1.get_normalized_witness_index(), + (size_t)NUM_LIMB_BITS, + (size_t)NUM_LIMB_BITS); + ctx->range_constrain_two_limbs(limb_2.get_normalized_witness_index(), + limb_3.get_normalized_witness_index(), + (size_t)NUM_LIMB_BITS, + (size_t)num_last_limb_bits); return result; } else { @@ -291,7 +299,7 @@ template bigfield::bigfield(const byt const auto [limb0, limb1] = reconstruct_two_limbs(ctx, lo_8_bytes, lolo_8_bytes, lo_split_byte); const auto [limb2, limb3] = reconstruct_two_limbs(ctx, hi_8_bytes, mid_8_bytes, mid_split_byte); - const auto res = bigfield(limb0, limb1, limb2, limb3, true); + const auto res = bigfield::unsafe_construct_from_limbs(limb0, limb1, limb2, limb3, true); const auto num_last_limb_bits = 256 - (NUM_LIMB_BITS * 3); res.binary_basis_limbs[3].maximum_value = (uint64_t(1) << num_last_limb_bits); @@ -340,13 +348,14 @@ template uint512_t bigfield::get_maxi } /** - * @brief Add a field element to the lower limb. CAUTION (the element has to be constrained before using this function) + * @brief Add a field element to the lower limb. CAUTION (the element has to be constrained before using this + * function) * - * @details Sometimes we need to add a small constrained value to a bigfield element (for example, a boolean value), but - * we don't want to construct a full bigfield element for that as it would take too many gates. If the maximum value of - * the field element being added is small enough, we can simply add it to the lowest limb and increase its maximum - * value. That will create 2 additional constraints instead of 5/3 needed to add 2 bigfield elements and several needed - * to construct a bigfield element. + * @details Sometimes we need to add a small constrained value to a bigfield element (for example, a boolean value), + * but we don't want to construct a full bigfield element for that as it would take too many gates. If the maximum + * value of the field element being added is small enough, we can simply add it to the lowest limb and increase its + * maximum value. That will create 2 additional constraints instead of 5/3 needed to add 2 bigfield elements and + * several needed to construct a bigfield element. * * @tparam Builder Builder * @tparam T Field Parameters @@ -359,14 +368,33 @@ bigfield bigfield::add_to_lower_limb(const field_t::from_witness(context, binary_basis_limbs[i].element.get_value()), + binary_basis_limbs[i].maximum_value); + // Ensure it is fixed + result.binary_basis_limbs[i].element.fix_witness(); + result.context = ctx; + } + } else { + + // if this element is a witness, then all limbs will be witnesses + result = *this; + } result.binary_basis_limbs[0].maximum_value = binary_basis_limbs[0].maximum_value + other_maximum_value; result.binary_basis_limbs[0].element = binary_basis_limbs[0].element + other; @@ -411,7 +439,11 @@ bigfield bigfield::operator+(const bigfield& other) cons limbconst = limbconst || other.binary_basis_limbs[2].element.is_constant(); limbconst = limbconst || other.binary_basis_limbs[3].element.is_constant(); limbconst = limbconst || other.prime_basis_limb.is_constant(); - limbconst = limbconst || (prime_basis_limb.witness_index == other.prime_basis_limb.witness_index); + limbconst = + limbconst || (prime_basis_limb.get_witness_index() == + other.prime_basis_limb + .get_witness_index()); // We are comparing if the bigfield elements are exactly the + // same object, so we compare the unnormalized witness indices if (!limbconst) { std::pair x0{ binary_basis_limbs[0].element.witness_index, binary_basis_limbs[0].element.multiplicative_constant }; @@ -441,7 +473,6 @@ bigfield bigfield::operator+(const bigfield& other) cons uint32_t xp(prime_basis_limb.witness_index); uint32_t yp(other.prime_basis_limb.witness_index); bb::fr cp(prime_basis_limb.additive_constant + other.prime_basis_limb.additive_constant); - const auto output_witnesses = ctx->evaluate_non_native_field_addition( { x0, y0, c0 }, { x1, y1, c1 }, { x2, y2, c2 }, { x3, y3, c3 }, { xp, yp, cp }); result.binary_basis_limbs[0].element = field_t::from_witness_index(ctx, output_witnesses[0]); @@ -572,7 +603,8 @@ bigfield bigfield::operator-(const bigfield& other) cons * Step 3: Compute offset terms t0, t1, t2, t3 that we add to our result to ensure each limb is positive * * t3 represents the value we are BORROWING from constant_to_add.limb[3] - * t2, t1, t0 are the terms we will ADD to constant_to_add.limb[2], constant_to_add.limb[1], constant_to_add.limb[0] + * t2, t1, t0 are the terms we will ADD to constant_to_add.limb[2], constant_to_add.limb[1], + *constant_to_add.limb[0] * * i.e. The net value we add to `constant_to_add` is 0. We must ensure that: * t3 = t0 + (t1 << NUM_LIMB_BITS) + (t2 << NUM_LIMB_BITS * 2) @@ -587,8 +619,8 @@ bigfield bigfield::operator-(const bigfield& other) cons uint256_t t3(uint256_t(1) << (limb_2_borrow_shift - NUM_LIMB_BITS)); /** - * Compute the limbs of `constant_to_add`, including our offset terms t0, t1, t2, t3 that ensure each result limb is - *positive + * Compute the limbs of `constant_to_add`, including our offset terms t0, t1, t2, t3 that ensure each result + *limb is positive **/ uint256_t to_add_0 = uint256_t(constant_to_add.slice(0, NUM_LIMB_BITS)) + t0; uint256_t to_add_1 = uint256_t(constant_to_add.slice(NUM_LIMB_BITS, NUM_LIMB_BITS * 2)) + t1; @@ -624,7 +656,11 @@ bigfield bigfield::operator-(const bigfield& other) cons limbconst = limbconst || other.binary_basis_limbs[2].element.is_constant(); limbconst = limbconst || other.binary_basis_limbs[3].element.is_constant(); limbconst = limbconst || other.prime_basis_limb.is_constant(); - limbconst = limbconst || (prime_basis_limb.witness_index == other.prime_basis_limb.witness_index); + limbconst = + limbconst || + (prime_basis_limb.witness_index == + other.prime_basis_limb.witness_index); // We are checking if this is and identical element, so we + // need to compare the actual indices, not normalized ones if (!limbconst) { std::pair x0{ result.binary_basis_limbs[0].element.witness_index, binary_basis_limbs[0].element.multiplicative_constant }; @@ -713,10 +749,11 @@ bigfield bigfield::operator*(const bigfield& other) cons return remainder; } else { // when writing a*b = q*p + r we wish to enforce r<2^s for smallest s such that p<2^s - // hence the second constructor call is with can_overflow=false. This will allow using r in more additions mod - // 2^t without needing to apply the mod, where t=4*NUM_LIMB_BITS + // hence the second constructor call is with can_overflow=false. This will allow using r in more additions + // mod 2^t without needing to apply the mod, where t=4*NUM_LIMB_BITS - // Check if the product overflows CRT or the quotient can't be contained in a range proof and reduce accordingly + // Check if the product overflows CRT or the quotient can't be contained in a range proof and reduce + // accordingly auto [reduction_required, num_quotient_bits] = get_quotient_reduction_info({ get_maximum_value() }, { other.get_maximum_value() }, {}); if (reduction_required) { @@ -739,8 +776,8 @@ bigfield bigfield::operator*(const bigfield& other) cons } /** - * Division operator. Doesn't create constraints for b!=0, which can lead to vulnerabilities. If you need a safer - *variant use div_check_denominator_nonzero. + * Division operator. Create constraints for b!=0 by default. If you need a variant + *without the zero check, use div_without_denominator_check. * * To evaluate (a / b = c mod p), we instead evaluate (c * b = a mod p). **/ @@ -748,7 +785,7 @@ template bigfield bigfield::operator/(const bigfield& other) const { - return internal_div({ *this }, other, false); + return internal_div({ *this }, other, true); } /** * @brief Create constraints for summing these terms @@ -812,7 +849,11 @@ bigfield bigfield::internal_div(const std::vector bigfield::div_without_denominator_check(const s return internal_div(numerators, denominator, false); } +template +bigfield bigfield::div_without_denominator_check(const bigfield& denominator) +{ + return internal_div({ *this }, denominator, false); +} + /** * Div method with constraints for denominator!=0. * @@ -929,6 +976,7 @@ template bigfield bigfield bigfield bigfield::sqradd(const std::vector& to_add) const { + ASSERT(to_add.size() <= MAXIMUM_SUMMAND_COUNT); reduction_check(); Builder* ctx = context; @@ -993,36 +1041,62 @@ bigfield bigfield::sqradd(const std::vector& t /** * @brief Raise a bigfield to a power of an exponent. Note that the exponent must not exceed 32 bits and is - * implicitly range constrained. The exponent is turned into a field_t witness for the underlying pow method - * to work. + * implicitly range constrained. * * @returns this ** (exponent) * * @todo TODO(https://github.com/AztecProtocol/barretenberg/issues/1014) Improve the efficiency of this function. - * @todo TODO(https://github.com/AztecProtocol/barretenberg/issues/1015) Security of this (as part of the whole class) + * @todo TODO(https://github.com/AztecProtocol/barretenberg/issues/1015) Security of this (as part of the whole + * class) */ template bigfield bigfield::pow(const size_t exponent) const { - auto* ctx = get_context() ? get_context() : nullptr; + // Just return one immediately + + if (exponent == 0) { + return bigfield(uint256_t(1)); + } + + bool accumulator_initialized = false; + bigfield accumulator; + bigfield running_power = *this; + auto shifted_exponent = exponent; - return pow(witness_t(ctx, exponent)); + // Square and multiply + while (shifted_exponent != 0) { + if (shifted_exponent & 1) { + if (!accumulator_initialized) { + accumulator = running_power; + accumulator_initialized = true; + } else { + accumulator *= running_power; + } + } + if (shifted_exponent != 0) { + running_power = running_power.sqr(); + } + shifted_exponent >>= 1; + } + return accumulator; } /** - * @brief Raise a bigfield to a power of an exponent (field_t) that must be a witness. Note that the exponent must not - * exceed 32 bits and is implicitly range constrained. + * @brief Raise a bigfield to a power of an exponent (field_t) that must be a witness. Note that the exponent must + * not exceed 32 bits and is implicitly range constrained. * * @returns this ** (exponent) * * @todo TODO(https://github.com/AztecProtocol/barretenberg/issues/1014) Improve the efficiency of this function. - * @todo TODO(https://github.com/AztecProtocol/barretenberg/issues/1015) Security of this (as part of the whole class) + * @todo TODO(https://github.com/AztecProtocol/barretenberg/issues/1015) Security of this (as part of the whole + * class) */ template bigfield bigfield::pow(const field_t& exponent) const { auto* ctx = get_context() ? get_context() : exponent.get_context(); uint256_t exponent_value = exponent.get_value(); + if constexpr (IsSimulator) { if ((exponent_value >> 32) != static_cast(0)) { ctx->failure("field_t::pow exponent accumulator incorrect"); @@ -1033,34 +1107,37 @@ bigfield bigfield::pow(const field_t& exponent) return result; } - bool exponent_constant = exponent.is_constant(); + ASSERT(exponent_value.get_msb() < 32); + // Use the constant version that perfoms only the necessary multiplications if the exponent is constant + if (exponent.is_constant()) { + return this->pow(static_cast(exponent_value)); + } std::vector> exponent_bits(32); + // Collect individual bits as bool_t's for (size_t i = 0; i < exponent_bits.size(); ++i) { uint256_t value_bit = exponent_value & 1; bool_t bit; - bit = exponent_constant ? bool_t(ctx, value_bit.data[0]) : witness_t(ctx, value_bit.data[0]); + bit = bool_t(witness_t(ctx, value_bit.data[0])); exponent_bits[31 - i] = (bit); exponent_value >>= 1; } - if (!exponent_constant) { - field_t exponent_accumulator(ctx, 0); - for (const auto& bit : exponent_bits) { - exponent_accumulator += exponent_accumulator; - exponent_accumulator += bit; - } - exponent.assert_equal(exponent_accumulator, "field_t::pow exponent accumulator incorrect"); + field_t exponent_accumulator(ctx, 0); + + // Reconstruct the exponent from bits + for (const auto& bit : exponent_bits) { + exponent_accumulator += exponent_accumulator; + exponent_accumulator += field_t(bit); } + + // Ensure it's equal to the original + exponent.assert_equal(exponent_accumulator, "field_t::pow exponent accumulator incorrect"); bigfield accumulator(ctx, 1); - bigfield mul_coefficient = *this - 1; + bigfield one(1); + // Compute the power with a square-and-multiply algorithm for (size_t digit_idx = 0; digit_idx < 32; ++digit_idx) { accumulator *= accumulator; - const bigfield bit(field_t(exponent_bits[digit_idx]), - field_t(witness_t(ctx, 0)), - field_t(witness_t(ctx, 0)), - field_t(witness_t(ctx, 0)), - /*can_overflow=*/true); - accumulator *= (mul_coefficient * bit + 1); + accumulator *= one.conditional_select(*this, exponent_bits[digit_idx]); } accumulator.self_reduce(); accumulator.set_origin_tag(OriginTag(get_origin_tag(), exponent.tag)); @@ -1078,6 +1155,7 @@ bigfield bigfield::pow(const field_t& exponent) template bigfield bigfield::madd(const bigfield& to_mul, const std::vector& to_add) const { + ASSERT(to_add.size() <= MAXIMUM_SUMMAND_COUNT); Builder* ctx = context ? context : to_mul.context; reduction_check(); to_mul.reduction_check(); @@ -1147,6 +1225,10 @@ void bigfield::perform_reductions_for_mult_madd(std::vector& mul_right, const std::vector& to_add) { + ASSERT(mul_left.size() == mul_right.size()); + ASSERT(to_add.size() <= MAXIMUM_SUMMAND_COUNT); + ASSERT(mul_left.size() <= MAXIMUM_SUMMAND_COUNT); + const size_t number_of_products = mul_left.size(); // Get the maximum values of elements std::vector max_values_left; @@ -1221,19 +1303,20 @@ void bigfield::perform_reductions_for_mult_madd(std::vector& left_element, std::tuple& right_element) { return std::get<0>(left_element) > std::get<0>(right_element); }; - // Sort the vector, larger values first - std::sort(maximum_value_updates.begin(), maximum_value_updates.end(), compare_update_tuples); // Now we loop through, reducing 1 element each time. This is costly in code, but allows us to use fewer // gates while (reduction_required) { + // Compute the possible reduction updates + compute_updates(maximum_value_updates, mul_left, mul_right, number_of_products); + + // Sort the vector, larger values first + std::sort(maximum_value_updates.begin(), maximum_value_updates.end(), compare_update_tuples); // We choose the largest update auto [update_size, largest_update_product_index, multiplicand_index] = maximum_value_updates[0]; @@ -1247,19 +1330,14 @@ void bigfield::perform_reductions_for_mult_madd(std::vector( - get_quotient_reduction_info(max_values_left, max_values_right, to_add, { DEFAULT_MAXIMUM_REMAINDER })); - - compute_updates(maximum_value_updates, mul_left, mul_right, number_of_products); - for (size_t i = 0; i < number_of_products; i++) { max_values_left[i] = mul_left[i].get_maximum_value(); max_values_right[i] = mul_right[i].get_maximum_value(); } - - // Sort the vector - std::sort(maximum_value_updates.begin(), maximum_value_updates.end(), compare_update_tuples); + reduction_required = std::get<0>( + get_quotient_reduction_info(max_values_left, max_values_right, to_add, { DEFAULT_MAXIMUM_REMAINDER })); } + // Now we have reduced everything exactly to the point of no overflow. There is probably a way to use even // fewer reductions, but for now this will suffice. } @@ -1281,6 +1359,8 @@ bigfield bigfield::mult_madd(const std::vector bool fix_remainder_to_zero) { ASSERT(mul_left.size() == mul_right.size()); + ASSERT(mul_left.size() <= MAXIMUM_SUMMAND_COUNT); + ASSERT(to_add.size() <= MAXIMUM_SUMMAND_COUNT); std::vector mutable_mul_left(mul_left); std::vector mutable_mul_right(mul_right); @@ -1381,7 +1461,8 @@ bigfield bigfield::mult_madd(const std::vector } } - // Now that we know that there is at least 1 non-constant multiplication, we can start estimating reductions, etc + // Now that we know that there is at least 1 non-constant multiplication, we can start estimating reductions, + // etc // Compute the constant term we're adding const auto [_, constant_part_remainder_1024] = (sum_of_constant_products + add_right_constant_sum).divmod(modulus); @@ -1409,8 +1490,8 @@ bigfield bigfield::mult_madd(const std::vector // Check that we can actually reduce the products enough, this assert will probably never get triggered ASSERT((worst_case_product_sum + add_right_maximum) < get_maximum_crt_product()); - // We've collapsed all constants, checked if we can compute the sum of products in the worst case, time to check if - // we need to reduce something + // We've collapsed all constants, checked if we can compute the sum of products in the worst case, time to check + // if we need to reduce something perform_reductions_for_mult_madd(new_input_left, new_input_right, new_to_add); uint1024_t sum_of_products_final(0); for (size_t i = 0; i < final_number_of_products; i++) { @@ -1464,6 +1545,7 @@ bigfield bigfield::dual_madd(const bigfield& left_a, const bigfield& right_b, const std::vector& to_add) { + ASSERT(to_add.size() <= MAXIMUM_SUMMAND_COUNT); left_a.reduction_check(); right_a.reduction_check(); left_b.reduction_check(); @@ -1500,12 +1582,6 @@ bigfield bigfield::msub_div(const std::vector& const std::vector& to_sub, bool enable_divisor_nz_check) { - Builder* ctx = divisor.context; - - const size_t num_multiplications = mul_left.size(); - native product_native = 0; - bool products_constant = true; - // Check the basics ASSERT(mul_left.size() == mul_right.size()); ASSERT(divisor.get_value() != 0); @@ -1517,6 +1593,35 @@ bigfield bigfield::msub_div(const std::vector& for (auto& element : to_sub) { new_tag = OriginTag(new_tag, element.get_origin_tag()); } + // Gett he context + Builder* ctx = divisor.context; + if (ctx == NULL) { + for (auto& el : mul_left) { + if (el.context != NULL) { + ctx = el.context; + break; + } + } + } + if (ctx == NULL) { + for (auto& el : mul_right) { + if (el.context != NULL) { + ctx = el.context; + break; + } + } + } + if (ctx == NULL) { + for (auto& el : to_sub) { + if (el.context != NULL) { + ctx = el.context; + break; + } + } + } + const size_t num_multiplications = mul_left.size(); + native product_native = 0; + bool products_constant = true; // This check is optional, because it is heavy and often we don't need it at all if (enable_divisor_nz_check) { @@ -1550,8 +1655,10 @@ bigfield bigfield::msub_div(const std::vector& if (sub_constant && products_constant && divisor.is_constant()) { auto result = bigfield(ctx, uint256_t(result_value.lo.lo)); result.set_origin_tag(new_tag); + return result; } + ASSERT(ctx != NULL); // Create the result witness bigfield result = create_from_u512_as_witness(ctx, result_value.lo); @@ -1578,6 +1685,7 @@ bigfield bigfield::conditional_negate(const bool_t bigfield::conditional_negate(const bool_t>(predicate).madd(-(binary_basis_limbs[3].element * two) + to_add_3, binary_basis_limbs[3].element); - uint256_t max_limb_0 = binary_basis_limbs[0].maximum_value + to_add_0_u256 + t0; - uint256_t max_limb_1 = binary_basis_limbs[1].maximum_value + to_add_1_u256 + t1; - uint256_t max_limb_2 = binary_basis_limbs[2].maximum_value + to_add_2_u256 + t2; - uint256_t max_limb_3 = binary_basis_limbs[3].maximum_value + to_add_3_u256 - t3; + uint256_t maximum_negated_limb_0 = to_add_0_u256 + t0; + uint256_t maximum_negated_limb_1 = to_add_1_u256 + t1; + uint256_t maximum_negated_limb_2 = to_add_2_u256 + t2; + uint256_t maximum_negated_limb_3 = to_add_3_u256; + + uint256_t max_limb_0 = binary_basis_limbs[0].maximum_value > maximum_negated_limb_0 + ? binary_basis_limbs[0].maximum_value + : maximum_negated_limb_0; + uint256_t max_limb_1 = binary_basis_limbs[1].maximum_value > maximum_negated_limb_1 + ? binary_basis_limbs[1].maximum_value + : maximum_negated_limb_1; + uint256_t max_limb_2 = binary_basis_limbs[2].maximum_value > maximum_negated_limb_2 + ? binary_basis_limbs[2].maximum_value + : maximum_negated_limb_2; + uint256_t max_limb_3 = binary_basis_limbs[3].maximum_value > maximum_negated_limb_3 + ? binary_basis_limbs[3].maximum_value + : maximum_negated_limb_3; bigfield result(ctx); result.binary_basis_limbs[0] = Limb(limb_0, max_limb_0); @@ -1717,7 +1838,8 @@ bigfield bigfield::conditional_select(const bigfield& ot * This allows us to evaluate `operator==` using only 1 bigfield multiplication operation. * We can check the product equals 0 or 1 by directly evaluating the binary basis/prime basis limbs of Y. * i.e. if `r == 1` then `(a - b)*X` should have 0 for all limb values - * if `r == 0` then `(a - b)*X` should have 1 in the least significant binary basis limb and 0 elsewhere + * if `r == 0` then `(a - b)*X` should have 1 in the least significant binary basis limb and 0 + * elsewhere * @tparam Builder * @tparam T * @param other @@ -1732,15 +1854,15 @@ template bool_t bigfield::op if (!ctx) { // TODO(https://github.com/AztecProtocol/barretenberg/issues/660): null context _should_ mean that both are // constant, but we check with an assertion to be sure. - ASSERT(is_constant() == other.is_constant()); + ASSERT(is_constant() && other.is_constant()); return is_equal_raw; } bool_t is_equal = witness_t(ctx, is_equal_raw); bigfield diff = (*this) - other; - // TODO(https://github.com/AztecProtocol/barretenberg/issues/999): get native values efficiently (i.e. if u512 value - // fits in a u256, subtract off modulus until u256 fits into finite field) + // TODO(https://github.com/AztecProtocol/barretenberg/issues/999): get native values efficiently (i.e. if u512 + // value fits in a u256, subtract off modulus until u256 fits into finite field) native diff_native = native((diff.get_value() % modulus_u512).lo); native inverse_native = is_equal_raw ? 0 : diff_native.invert(); @@ -1772,10 +1894,8 @@ template bool_t bigfield::op * This prevents our field arithmetic from overflowing the native modulus boundary, whilst ensuring we can * still use the chinese remainder theorem to validate field multiplications with a reduced number of range checks * - * @param num_products The number of products a*b in the parent function that calls the reduction check. Needed to - *limit overflow **/ -template void bigfield::reduction_check(const size_t num_products) const +template void bigfield::reduction_check() const { if (is_constant()) { // this seems not a reduction check, but actually computing the reduction @@ -1807,12 +1927,32 @@ template void bigfield::reduction_che bool limb_overflow_test_1 = binary_basis_limbs[1].maximum_value > maximum_limb_value; bool limb_overflow_test_2 = binary_basis_limbs[2].maximum_value > maximum_limb_value; bool limb_overflow_test_3 = binary_basis_limbs[3].maximum_value > maximum_limb_value; - if (get_maximum_value() > get_maximum_unreduced_value(num_products) || limb_overflow_test_0 || - limb_overflow_test_1 || limb_overflow_test_2 || limb_overflow_test_3) { + if (get_maximum_value() > get_maximum_unreduced_value() || limb_overflow_test_0 || limb_overflow_test_1 || + limb_overflow_test_2 || limb_overflow_test_3) { self_reduce(); } } +/** + * SANITY CHECK on a value that is about to interact with another value + * + * @details ASSERTs that the value of all limbs is less than or equal to the prohibited maximum value. Checks that the + *maximum value of the whole element is also less than a prohibited maximum value + * + **/ +template void bigfield::sanity_check() const +{ + + uint256_t maximum_limb_value = get_prohibited_maximum_limb_value(); + bool limb_overflow_test_0 = binary_basis_limbs[0].maximum_value > maximum_limb_value; + bool limb_overflow_test_1 = binary_basis_limbs[1].maximum_value > maximum_limb_value; + bool limb_overflow_test_2 = binary_basis_limbs[2].maximum_value > maximum_limb_value; + bool limb_overflow_test_3 = binary_basis_limbs[3].maximum_value > maximum_limb_value; + ASSERT(!(get_maximum_value() > get_prohibited_maximum_value() || limb_overflow_test_0 || limb_overflow_test_1 || + limb_overflow_test_2 || limb_overflow_test_3)); +} + +// Underneath performs assert_less_than(modulus) // create a version with mod 2^t element part in [0,p-1] // After reducing to size 2^s, we check (p-1)-a is non-negative as integer. // We perform subtraction using carries on blocks of size 2^b. The operations inside the blocks are done mod r @@ -1821,70 +1961,11 @@ template void bigfield::reduction_che // non-negative at the end of subtraction, we know the subtraction result is positive as integers and a

void bigfield::assert_is_in_field() const { - // Warning: this assumes we have run circuit construction at least once in debug mode where large non reduced - // constants are allowed via ASSERT - if (is_constant()) { - return; - } - - self_reduce(); // this method in particular enforces limb vals are <2^b - needed for logic described above - uint256_t value = get_value().lo; - // TODO:make formal assert that modulus<=256 bits - constexpr uint256_t modulus_minus_one = modulus_u512.lo - 1; - - constexpr uint256_t modulus_minus_one_0 = modulus_minus_one.slice(0, NUM_LIMB_BITS); - constexpr uint256_t modulus_minus_one_1 = modulus_minus_one.slice(NUM_LIMB_BITS, NUM_LIMB_BITS * 2); - constexpr uint256_t modulus_minus_one_2 = modulus_minus_one.slice(NUM_LIMB_BITS * 2, NUM_LIMB_BITS * 3); - constexpr uint256_t modulus_minus_one_3 = modulus_minus_one.slice(NUM_LIMB_BITS * 3, NUM_LIMB_BITS * 4); - - bool borrow_0_value = value.slice(0, NUM_LIMB_BITS) > modulus_minus_one_0; - bool borrow_1_value = - (value.slice(NUM_LIMB_BITS, NUM_LIMB_BITS * 2) + uint256_t(borrow_0_value)) > (modulus_minus_one_1); - bool borrow_2_value = - (value.slice(NUM_LIMB_BITS * 2, NUM_LIMB_BITS * 3) + uint256_t(borrow_1_value)) > (modulus_minus_one_2); - - field_t modulus_0(context, modulus_minus_one_0); - field_t modulus_1(context, modulus_minus_one_1); - field_t modulus_2(context, modulus_minus_one_2); - field_t modulus_3(context, modulus_minus_one_3); - bool_t borrow_0(witness_t(context, borrow_0_value)); - bool_t borrow_1(witness_t(context, borrow_1_value)); - bool_t borrow_2(witness_t(context, borrow_2_value)); - // The way we use borrows here ensures that we are checking that modulus - binary_basis > 0. - // We check that the result in each limb is > 0. - // If the modulus part in this limb is smaller, we simply borrow the value from the higher limb. - // The prover can rearrange the borrows the way they like. The important thing is that the borrows are - // constrained. - field_t r0 = modulus_0 - binary_basis_limbs[0].element + static_cast>(borrow_0) * shift_1; - field_t r1 = modulus_1 - binary_basis_limbs[1].element + - static_cast>(borrow_1) * shift_1 - static_cast>(borrow_0); - field_t r2 = modulus_2 - binary_basis_limbs[2].element + - static_cast>(borrow_2) * shift_1 - static_cast>(borrow_1); - field_t r3 = modulus_3 - binary_basis_limbs[3].element - static_cast>(borrow_2); - r0 = r0.normalize(); - r1 = r1.normalize(); - r2 = r2.normalize(); - r3 = r3.normalize(); - if constexpr (HasPlookup) { - context->decompose_into_default_range(r0.witness_index, static_cast(NUM_LIMB_BITS)); - context->decompose_into_default_range(r1.witness_index, static_cast(NUM_LIMB_BITS)); - context->decompose_into_default_range(r2.witness_index, static_cast(NUM_LIMB_BITS)); - context->decompose_into_default_range(r3.witness_index, static_cast(NUM_LIMB_BITS)); - } else { - context->decompose_into_base4_accumulators( - r0.witness_index, static_cast(NUM_LIMB_BITS), "bigfield: assert_is_in_field range constraint 1."); - context->decompose_into_base4_accumulators( - r1.witness_index, static_cast(NUM_LIMB_BITS), "bigfield: assert_is_in_field range constraint 2."); - context->decompose_into_base4_accumulators( - r2.witness_index, static_cast(NUM_LIMB_BITS), "bigfield: assert_is_in_field range constraint 3."); - context->decompose_into_base4_accumulators( - r3.witness_index, static_cast(NUM_LIMB_BITS), "bigfield: assert_is_in_field range constraint 4."); - } + assert_less_than(modulus); } template void bigfield::assert_less_than(const uint256_t upper_limit) const { - // TODO(kesha): Merge this with assert_is_in_field // Warning: this assumes we have run circuit construction at least once in debug mode where large non reduced // constants are allowed via ASSERT if constexpr (IsSimulator) { @@ -1900,8 +1981,8 @@ template void bigfield::assert_less_t } ASSERT(upper_limit != 0); - // The circuit checks that limit - this >= 0, so if we are doing a less_than comparison, we need to subtract 1 from - // the limit + // The circuit checks that limit - this >= 0, so if we are doing a less_than comparison, we need to subtract 1 + // from the limit uint256_t strict_upper_limit = upper_limit - uint256_t(1); self_reduce(); // this method in particular enforces limb vals are <2^b - needed for logic described above uint256_t value = get_value().lo; @@ -1940,25 +2021,32 @@ template void bigfield::assert_less_t r1 = r1.normalize(); r2 = r2.normalize(); r3 = r3.normalize(); - if constexpr (Builder::CIRCUIT_TYPE == CircuitType::ULTRA) { - context->decompose_into_default_range(r0.witness_index, static_cast(NUM_LIMB_BITS)); - context->decompose_into_default_range(r1.witness_index, static_cast(NUM_LIMB_BITS)); - context->decompose_into_default_range(r2.witness_index, static_cast(NUM_LIMB_BITS)); - context->decompose_into_default_range(r3.witness_index, static_cast(NUM_LIMB_BITS)); + if constexpr (HasPlookup) { + context->decompose_into_default_range(r0.get_normalized_witness_index(), static_cast(NUM_LIMB_BITS)); + context->decompose_into_default_range(r1.get_normalized_witness_index(), static_cast(NUM_LIMB_BITS)); + context->decompose_into_default_range(r2.get_normalized_witness_index(), static_cast(NUM_LIMB_BITS)); + context->decompose_into_default_range(r3.get_normalized_witness_index(), static_cast(NUM_LIMB_BITS)); } else { - context->decompose_into_base4_accumulators( - r0.witness_index, static_cast(NUM_LIMB_BITS), "bigfield: assert_less_than range constraint 1."); - context->decompose_into_base4_accumulators( - r1.witness_index, static_cast(NUM_LIMB_BITS), "bigfield: assert_less_than range constraint 2."); - context->decompose_into_base4_accumulators( - r2.witness_index, static_cast(NUM_LIMB_BITS), "bigfield: assert_less_than range constraint 3."); - context->decompose_into_base4_accumulators( - r3.witness_index, static_cast(NUM_LIMB_BITS), "bigfield: assert_less_than range constraint 4."); + context->decompose_into_base4_accumulators(r0.get_normalized_witness_index(), + static_cast(NUM_LIMB_BITS), + "bigfield: assert_less_than range constraint 1."); + context->decompose_into_base4_accumulators(r1.get_normalized_witness_index(), + static_cast(NUM_LIMB_BITS), + "bigfield: assert_less_than range constraint 2."); + context->decompose_into_base4_accumulators(r2.get_normalized_witness_index(), + static_cast(NUM_LIMB_BITS), + "bigfield: assert_less_than range constraint 3."); + context->decompose_into_base4_accumulators(r3.get_normalized_witness_index(), + static_cast(NUM_LIMB_BITS), + "bigfield: assert_less_than range constraint 4."); } } // check elements are equal mod p by proving their integer difference is a multiple of p. // This relies on the minus operator for a-b increasing a by a multiple of p large enough so diff is non-negative +// When one of the elements is a constant and another is a witness we check equality of limbs, so if the witness +// bigfield element is in an unreduced form, it needs to be reduced first. We don't have automatice reduced form +// detection for now, so it is up to the circuit writer to detect this template void bigfield::assert_equal(const bigfield& other) const { Builder* ctx = this->context ? this->context : other.context; @@ -1970,6 +2058,7 @@ template void bigfield::assert_equal( if (is_constant() && other.is_constant()) { std::cerr << "bigfield: calling assert equal on 2 CONSTANT bigfield elements...is this intended?" << std::endl; + ASSERT(get_value() == other.get_value()); // We expect constants to be less than the target modulus return; } else if (other.is_constant()) { // TODO(https://github.com/AztecProtocol/barretenberg/issues/998): Something is fishy here @@ -1990,28 +2079,6 @@ template void bigfield::assert_equal( other.assert_equal(*this); return; } else { - if (is_constant() && other.is_constant()) { - std::cerr << "bigfield: calling assert equal on 2 CONSTANT bigfield elements...is this intended?" - << std::endl; - return; - } else if (other.is_constant()) { - // evaluate a strict equality - make sure *this is reduced first, or an honest prover - // might not be able to satisfy these constraints. - field_t t0 = (binary_basis_limbs[0].element - other.binary_basis_limbs[0].element); - field_t t1 = (binary_basis_limbs[1].element - other.binary_basis_limbs[1].element); - field_t t2 = (binary_basis_limbs[2].element - other.binary_basis_limbs[2].element); - field_t t3 = (binary_basis_limbs[3].element - other.binary_basis_limbs[3].element); - field_t t4 = (prime_basis_limb - other.prime_basis_limb); - t0.assert_is_zero(); - t1.assert_is_zero(); - t2.assert_is_zero(); - t3.assert_is_zero(); - t4.assert_is_zero(); - return; - } else if (is_constant()) { - other.assert_equal(*this); - return; - } bigfield diff = *this - other; const uint512_t diff_val = diff.get_value(); @@ -2047,7 +2114,7 @@ template void bigfield::assert_is_not const auto get_overload_count = [target_modulus = modulus_u512](const uint512_t& maximum_value) { uint512_t target = target_modulus; size_t overload_count = 0; - while (target < maximum_value) { + while (target <= maximum_value) { ++overload_count; target += target_modulus; } @@ -2101,13 +2168,15 @@ template void bigfield::self_reduce() if ((maximum_quotient_bits & 1ULL) == 1ULL) { ++maximum_quotient_bits; } - // TODO: implicit assumption here - NUM_LIMB_BITS large enough for all the quotient + + ASSERT(maximum_quotient_bits <= NUM_LIMB_BITS); uint32_t quotient_limb_index = context->add_variable(bb::fr(quotient_value.lo)); field_t quotient_limb = field_t::from_witness_index(context, quotient_limb_index); if constexpr (HasPlookup) { - context->decompose_into_default_range(quotient_limb.witness_index, static_cast(maximum_quotient_bits)); + context->decompose_into_default_range(quotient_limb.get_normalized_witness_index(), + static_cast(maximum_quotient_bits)); } else { - context->decompose_into_base4_accumulators(quotient_limb.witness_index, + context->decompose_into_base4_accumulators(quotient_limb.get_normalized_witness_index(), static_cast(maximum_quotient_bits), "bigfield: quotient_limb too large."); } @@ -2154,7 +2223,21 @@ void bigfield::unsafe_evaluate_multiply_add(const bigfield& input_le const std::vector& input_remainders) { + ASSERT(to_add.size() <= MAXIMUM_SUMMAND_COUNT); + ASSERT(input_remainders.size() <= MAXIMUM_SUMMAND_COUNT); + // Sanity checks + input_left.sanity_check(); + input_to_mul.sanity_check(); + input_quotient.sanity_check(); + for (auto& el : to_add) { + el.sanity_check(); + } + for (auto& el : input_remainders) { + el.sanity_check(); + } + std::vector remainders(input_remainders); + bigfield left = input_left; bigfield to_mul = input_to_mul; bigfield quotient = input_quotient; @@ -2183,7 +2266,19 @@ void bigfield::unsafe_evaluate_multiply_add(const bigfield& input_le uint512_t max_r0 = left.binary_basis_limbs[0].maximum_value * to_mul.binary_basis_limbs[0].maximum_value; max_r0 += (neg_modulus_limbs_u256[0] * quotient.binary_basis_limbs[0].maximum_value); - const uint512_t max_r1 = max_b0 + max_b1; + uint512_t max_r1 = max_b0 + max_b1; + + uint256_t borrow_lo_value = 0; + for (const auto& remainder : input_remainders) { + max_r0 += remainder.binary_basis_limbs[0].maximum_value; + max_r1 += remainder.binary_basis_limbs[1].maximum_value; + + borrow_lo_value += (remainder.binary_basis_limbs[0].maximum_value + + (remainder.binary_basis_limbs[1].maximum_value << NUM_LIMB_BITS)); + } + borrow_lo_value >>= 2 * NUM_LIMB_BITS; + field_t borrow_lo(ctx, bb::fr(borrow_lo_value)); + const uint512_t max_r2 = max_c0 + max_c1 + max_c2; const uint512_t max_r3 = max_d0 + max_d1 + max_d2 + max_d3; @@ -2196,7 +2291,8 @@ void bigfield::unsafe_evaluate_multiply_add(const bigfield& input_le (to_add[i].binary_basis_limbs[3].maximum_value << NUM_LIMB_BITS); } const uint512_t max_lo = max_r0 + (max_r1 << NUM_LIMB_BITS) + max_a0; - const uint512_t max_hi = max_r2 + (max_r3 << NUM_LIMB_BITS) + max_a1; + const uint512_t max_lo_carry = max_lo >> (2 * NUM_LIMB_BITS); + const uint512_t max_hi = max_r2 + (max_r3 << NUM_LIMB_BITS) + max_a1 + max_lo_carry; uint64_t max_lo_bits = (max_lo.get_msb() + 1); uint64_t max_hi_bits = max_hi.get_msb() + 1; @@ -2207,6 +2303,15 @@ void bigfield::unsafe_evaluate_multiply_add(const bigfield& input_le ++max_hi_bits; } + uint64_t carry_lo_msb = max_lo_bits - (2 * NUM_LIMB_BITS); + uint64_t carry_hi_msb = max_hi_bits - (2 * NUM_LIMB_BITS); + + if (max_lo_bits < (2 * NUM_LIMB_BITS)) { + carry_lo_msb = 0; + } + if (max_hi_bits < (2 * NUM_LIMB_BITS)) { + carry_hi_msb = 0; + } if constexpr (HasPlookup) { // The plookup custom bigfield gate requires inputs are witnesses. // If we're using constant values, instantiate them as circuit variables @@ -2278,38 +2383,34 @@ void bigfield::unsafe_evaluate_multiply_add(const bigfield& input_le bb::non_native_field_witnesses witnesses{ { - left.binary_basis_limbs[0].element.normalize().witness_index, - left.binary_basis_limbs[1].element.normalize().witness_index, - left.binary_basis_limbs[2].element.normalize().witness_index, - left.binary_basis_limbs[3].element.normalize().witness_index, - left.prime_basis_limb.witness_index, + left.binary_basis_limbs[0].element.get_normalized_witness_index(), + left.binary_basis_limbs[1].element.get_normalized_witness_index(), + left.binary_basis_limbs[2].element.get_normalized_witness_index(), + left.binary_basis_limbs[3].element.get_normalized_witness_index(), }, { - to_mul.binary_basis_limbs[0].element.normalize().witness_index, - to_mul.binary_basis_limbs[1].element.normalize().witness_index, - to_mul.binary_basis_limbs[2].element.normalize().witness_index, - to_mul.binary_basis_limbs[3].element.normalize().witness_index, - to_mul.prime_basis_limb.witness_index, + to_mul.binary_basis_limbs[0].element.get_normalized_witness_index(), + to_mul.binary_basis_limbs[1].element.get_normalized_witness_index(), + to_mul.binary_basis_limbs[2].element.get_normalized_witness_index(), + to_mul.binary_basis_limbs[3].element.get_normalized_witness_index(), }, { - quotient.binary_basis_limbs[0].element.normalize().witness_index, - quotient.binary_basis_limbs[1].element.normalize().witness_index, - quotient.binary_basis_limbs[2].element.normalize().witness_index, - quotient.binary_basis_limbs[3].element.normalize().witness_index, - quotient.prime_basis_limb.witness_index, + quotient.binary_basis_limbs[0].element.get_normalized_witness_index(), + quotient.binary_basis_limbs[1].element.get_normalized_witness_index(), + quotient.binary_basis_limbs[2].element.get_normalized_witness_index(), + quotient.binary_basis_limbs[3].element.get_normalized_witness_index(), }, { - remainder_limbs[0].normalize().witness_index, - remainder_limbs[1].normalize().witness_index, - remainder_limbs[2].normalize().witness_index, - remainder_limbs[3].normalize().witness_index, - remainder_prime_limb.witness_index, + remainder_limbs[0].get_normalized_witness_index(), + remainder_limbs[1].get_normalized_witness_index(), + remainder_limbs[2].get_normalized_witness_index(), + remainder_limbs[3].get_normalized_witness_index(), }, { neg_modulus_limbs[0], neg_modulus_limbs[1], neg_modulus_limbs[2], neg_modulus_limbs[3] }, modulus, }; // N.B. this method also evaluates the prime field component of the non-native field mul - const auto [lo_idx, hi_idx] = ctx->evaluate_non_native_field_multiplication(witnesses, false); + const auto [lo_idx, hi_idx] = ctx->evaluate_non_native_field_multiplication(witnesses); bb::fr neg_prime = -bb::fr(uint256_t(target_basis.modulus)); field_t::evaluate_polynomial_identity(left.prime_basis_limb, @@ -2317,19 +2418,19 @@ void bigfield::unsafe_evaluate_multiply_add(const bigfield& input_le quotient.prime_basis_limb * neg_prime, -remainder_prime_limb); - field_t lo = field_t::from_witness_index(ctx, lo_idx); + field_t lo = field_t::from_witness_index(ctx, lo_idx) + borrow_lo; field_t hi = field_t::from_witness_index(ctx, hi_idx); - const uint64_t carry_lo_msb = max_lo_bits - (2 * NUM_LIMB_BITS); - const uint64_t carry_hi_msb = max_hi_bits - (2 * NUM_LIMB_BITS); // if both the hi and lo output limbs have less than 70 bits, we can use our custom // limb accumulation gate (accumulates 2 field elements, each composed of 5 14-bit limbs, in 3 gates) if (carry_lo_msb <= 70 && carry_hi_msb <= 70) { - ctx->range_constrain_two_limbs( - hi.witness_index, lo.witness_index, size_t(carry_lo_msb), size_t(carry_hi_msb)); + ctx->range_constrain_two_limbs(hi.get_normalized_witness_index(), + lo.get_normalized_witness_index(), + size_t(carry_hi_msb), + size_t(carry_lo_msb)); } else { - ctx->decompose_into_default_range(hi.normalize().witness_index, carry_hi_msb); - ctx->decompose_into_default_range(lo.normalize().witness_index, carry_lo_msb); + ctx->decompose_into_default_range(hi.get_normalized_witness_index(), carry_hi_msb); + ctx->decompose_into_default_range(lo.get_normalized_witness_index(), carry_lo_msb); } } else { const field_t b0 = left.binary_basis_limbs[1].element.madd( @@ -2351,9 +2452,9 @@ void bigfield::unsafe_evaluate_multiply_add(const bigfield& input_le const field_t d3 = left.binary_basis_limbs[0].element.madd( to_mul.binary_basis_limbs[3].element, quotient.binary_basis_limbs[0].element * neg_modulus_limbs[3]); - // We wish to show that left*right - quotient*remainder = 0 mod 2^t, we do this by collecting the limb products - // into two separate variables - carry_lo and carry_hi, which are still small enough not to wrap mod r - // Their first t/2 bits will equal, respectively, the first and second t/2 bits of the expresssion + // We wish to show that left*right - quotient*remainder = 0 mod 2^t, we do this by collecting the limb + // products into two separate variables - carry_lo and carry_hi, which are still small enough not to wrap + // mod r Their first t/2 bits will equal, respectively, the first and second t/2 bits of the expresssion // Thus it will suffice to check that each of them begins with t/2 zeroes. We do this by in fact assigning // to these variables those expressions divided by 2^{t/2}. Since we have bounds on their ranage that are // smaller than r, We can range check the divisions by the original range bounds divided by 2^{t/2} @@ -2379,6 +2480,7 @@ void bigfield::unsafe_evaluate_multiply_add(const bigfield& input_le } field_t t1 = carry_lo.add_two(-remainders[0].binary_basis_limbs[2].element, -(remainders[0].binary_basis_limbs[3].element * shift_1)); + carry_lo += borrow_lo; field_t carry_hi_0 = r2 * shift_right_2; field_t carry_hi_1 = r3 * (shift_1 * shift_right_2); field_t carry_hi_2 = t1 * shift_right_2; @@ -2416,15 +2518,12 @@ void bigfield::unsafe_evaluate_multiply_add(const bigfield& input_le field_t::evaluate_polynomial_identity( left.prime_basis_limb, to_mul.prime_basis_limb, quotient.prime_basis_limb * neg_prime, linear_terms); - const uint64_t carry_lo_msb = max_lo_bits - (2 * NUM_LIMB_BITS); - const uint64_t carry_hi_msb = max_hi_bits - (2 * NUM_LIMB_BITS); - const bb::fr carry_lo_shift(uint256_t(uint256_t(1) << carry_lo_msb)); if ((carry_hi_msb + carry_lo_msb) < field_t::modulus.get_msb()) { field_t carry_combined = carry_lo + (carry_hi * carry_lo_shift); carry_combined = carry_combined.normalize(); const auto accumulators = ctx->decompose_into_base4_accumulators( - carry_combined.witness_index, + carry_combined.get_normalized_witness_index(), static_cast(carry_lo_msb + carry_hi_msb), "bigfield: carry_combined too large in unsafe_evaluate_multiply_add."); field_t accumulator_midpoint = @@ -2433,10 +2532,10 @@ void bigfield::unsafe_evaluate_multiply_add(const bigfield& input_le } else { carry_lo = carry_lo.normalize(); carry_hi = carry_hi.normalize(); - ctx->decompose_into_base4_accumulators(carry_lo.witness_index, + ctx->decompose_into_base4_accumulators(carry_lo.get_normalized_witness_index(), static_cast(carry_lo_msb), "bigfield: carry_lo too large in unsafe_evaluate_multiply_add."); - ctx->decompose_into_base4_accumulators(carry_hi.witness_index, + ctx->decompose_into_base4_accumulators(carry_hi.get_normalized_witness_index(), static_cast(carry_hi_msb), "bigfield: carry_hi too large in unsafe_evaluate_multiply_add."); } @@ -2471,7 +2570,26 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vect const bigfield& input_quotient, const std::vector& input_remainders) { + ASSERT(input_left.size() == input_right.size()); + ASSERT(input_left.size() <= MAXIMUM_SUMMAND_COUNT); + ASSERT(to_add.size() <= MAXIMUM_SUMMAND_COUNT); + ASSERT(input_remainders.size() <= MAXIMUM_SUMMAND_COUNT); + ASSERT(input_left.size() == input_right.size() && input_left.size() < 1024); + // Sanity checks + for (auto& el : input_left) { + el.sanity_check(); + } + for (auto& el : input_right) { + el.sanity_check(); + } + for (auto& el : to_add) { + el.sanity_check(); + } + input_quotient.sanity_check(); + for (auto& el : input_remainders) { + el.sanity_check(); + } std::vector remainders(input_remainders); std::vector left(input_left); std::vector right(input_right); @@ -2527,7 +2645,19 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vect // max_r3 = terms from 2^3t - 2^5t uint512_t max_r0 = (neg_modulus_limbs_u256[0] * quotient.binary_basis_limbs[0].maximum_value); max_r0 += (neg_modulus_limbs_u256[0] * quotient.binary_basis_limbs[0].maximum_value); - const uint512_t max_r1 = max_b0 + max_b1; + uint512_t max_r1 = max_b0 + max_b1; + + uint256_t borrow_lo_value(0); + for (const auto& remainder : input_remainders) { + max_r0 += remainder.binary_basis_limbs[0].maximum_value; + max_r1 += remainder.binary_basis_limbs[1].maximum_value; + + borrow_lo_value += remainder.binary_basis_limbs[0].maximum_value + + (remainder.binary_basis_limbs[1].maximum_value << NUM_LIMB_BITS); + } + borrow_lo_value >>= 2 * NUM_LIMB_BITS; + field_t borrow_lo(ctx, bb::fr(borrow_lo_value)); + const uint512_t max_r2 = max_c0 + max_c1 + max_c2; const uint512_t max_r3 = max_d0 + max_d1 + max_d2 + max_d3; @@ -2554,6 +2684,8 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vect max_hi += product_hi; } + const uint512_t max_lo_carry = max_lo >> (2 * NUM_LIMB_BITS); + max_hi += max_lo_carry; // Compute the maximum number of bits in `max_lo` and `max_hi` - this defines the range constraint values we // will need to apply to validate our product uint64_t max_lo_bits = (max_lo.get_msb() + 1); @@ -2618,32 +2750,28 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vect if (i > 0) { bb::non_native_field_witnesses mul_witnesses = { { - left[i].binary_basis_limbs[0].element.normalize().witness_index, - left[i].binary_basis_limbs[1].element.normalize().witness_index, - left[i].binary_basis_limbs[2].element.normalize().witness_index, - left[i].binary_basis_limbs[3].element.normalize().witness_index, - left[i].prime_basis_limb.witness_index, + left[i].binary_basis_limbs[0].element.get_normalized_witness_index(), + left[i].binary_basis_limbs[1].element.get_normalized_witness_index(), + left[i].binary_basis_limbs[2].element.get_normalized_witness_index(), + left[i].binary_basis_limbs[3].element.get_normalized_witness_index(), }, { - right[i].binary_basis_limbs[0].element.normalize().witness_index, - right[i].binary_basis_limbs[1].element.normalize().witness_index, - right[i].binary_basis_limbs[2].element.normalize().witness_index, - right[i].binary_basis_limbs[3].element.normalize().witness_index, - right[i].prime_basis_limb.witness_index, + right[i].binary_basis_limbs[0].element.get_normalized_witness_index(), + right[i].binary_basis_limbs[1].element.get_normalized_witness_index(), + right[i].binary_basis_limbs[2].element.get_normalized_witness_index(), + right[i].binary_basis_limbs[3].element.get_normalized_witness_index(), }, { ctx->zero_idx, ctx->zero_idx, ctx->zero_idx, ctx->zero_idx, - ctx->zero_idx, }, { ctx->zero_idx, ctx->zero_idx, ctx->zero_idx, ctx->zero_idx, - ctx->zero_idx, }, { 0, 0, 0, 0 }, modulus, @@ -2714,38 +2842,34 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vect bb::non_native_field_witnesses witnesses{ { - left[0].binary_basis_limbs[0].element.normalize().witness_index, - left[0].binary_basis_limbs[1].element.normalize().witness_index, - left[0].binary_basis_limbs[2].element.normalize().witness_index, - left[0].binary_basis_limbs[3].element.normalize().witness_index, - left[0].prime_basis_limb.normalize().witness_index, + left[0].binary_basis_limbs[0].element.get_normalized_witness_index(), + left[0].binary_basis_limbs[1].element.get_normalized_witness_index(), + left[0].binary_basis_limbs[2].element.get_normalized_witness_index(), + left[0].binary_basis_limbs[3].element.get_normalized_witness_index(), }, { - right[0].binary_basis_limbs[0].element.normalize().witness_index, - right[0].binary_basis_limbs[1].element.normalize().witness_index, - right[0].binary_basis_limbs[2].element.normalize().witness_index, - right[0].binary_basis_limbs[3].element.normalize().witness_index, - right[0].prime_basis_limb.normalize().witness_index, + right[0].binary_basis_limbs[0].element.get_normalized_witness_index(), + right[0].binary_basis_limbs[1].element.get_normalized_witness_index(), + right[0].binary_basis_limbs[2].element.get_normalized_witness_index(), + right[0].binary_basis_limbs[3].element.get_normalized_witness_index(), }, { - quotient.binary_basis_limbs[0].element.normalize().witness_index, - quotient.binary_basis_limbs[1].element.normalize().witness_index, - quotient.binary_basis_limbs[2].element.normalize().witness_index, - quotient.binary_basis_limbs[3].element.normalize().witness_index, - quotient.prime_basis_limb.normalize().witness_index, + quotient.binary_basis_limbs[0].element.get_normalized_witness_index(), + quotient.binary_basis_limbs[1].element.get_normalized_witness_index(), + quotient.binary_basis_limbs[2].element.get_normalized_witness_index(), + quotient.binary_basis_limbs[3].element.get_normalized_witness_index(), }, { - remainder_limbs[0].normalize().witness_index, - remainder_limbs[1].normalize().witness_index, - remainder_limbs[2].normalize().witness_index, - remainder_limbs[3].normalize().witness_index, - remainder_prime_limb.normalize().witness_index, + remainder_limbs[0].get_normalized_witness_index(), + remainder_limbs[1].get_normalized_witness_index(), + remainder_limbs[2].get_normalized_witness_index(), + remainder_limbs[3].get_normalized_witness_index(), }, { neg_modulus_limbs[0], neg_modulus_limbs[1], neg_modulus_limbs[2], neg_modulus_limbs[3] }, modulus, }; - const auto [lo_1_idx, hi_1_idx] = ctx->evaluate_non_native_field_multiplication(witnesses, false); + const auto [lo_1_idx, hi_1_idx] = ctx->evaluate_non_native_field_multiplication(witnesses); bb::fr neg_prime = -bb::fr(uint256_t(target_basis.modulus)); @@ -2754,20 +2878,29 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vect quotient.prime_basis_limb * neg_prime, -remainder_prime_limb); - field_t lo = field_t::from_witness_index(ctx, lo_1_idx); + field_t lo = field_t::from_witness_index(ctx, lo_1_idx) + borrow_lo; field_t hi = field_t::from_witness_index(ctx, hi_1_idx); - const uint64_t carry_lo_msb = max_lo_bits - (2 * NUM_LIMB_BITS); - const uint64_t carry_hi_msb = max_hi_bits - (2 * NUM_LIMB_BITS); + uint64_t carry_lo_msb = max_lo_bits - (2 * NUM_LIMB_BITS); + uint64_t carry_hi_msb = max_hi_bits - (2 * NUM_LIMB_BITS); + + if (max_lo_bits < (2 * NUM_LIMB_BITS)) { + carry_lo_msb = 0; + } + if (max_hi_bits < (2 * NUM_LIMB_BITS)) { + carry_hi_msb = 0; + } // if both the hi and lo output limbs have less than 70 bits, we can use our custom // limb accumulation gate (accumulates 2 field elements, each composed of 5 14-bit limbs, in 3 gates) if (carry_lo_msb <= 70 && carry_hi_msb <= 70) { - ctx->range_constrain_two_limbs( - hi.witness_index, lo.witness_index, (size_t)carry_lo_msb, (size_t)carry_hi_msb); + ctx->range_constrain_two_limbs(hi.get_normalized_witness_index(), + lo.get_normalized_witness_index(), + (size_t)carry_hi_msb, + (size_t)carry_lo_msb); } else { - ctx->decompose_into_default_range(hi.normalize().witness_index, carry_hi_msb); - ctx->decompose_into_default_range(lo.normalize().witness_index, carry_lo_msb); + ctx->decompose_into_default_range(hi.get_normalized_witness_index(), carry_hi_msb); + ctx->decompose_into_default_range(lo.get_normalized_witness_index(), carry_lo_msb); } /* NOTE TO AUDITOR: An extraneous block if constexpr (HasPlookup) { @@ -2913,6 +3046,7 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vect field_t carry_lo = carry_lo_0.add_two(carry_lo_1, carry_lo_2); field_t t1 = carry_lo.add_two(-remainder_limbs[2], -(remainder_limbs[3] * shift_1)); + carry_lo += borrow_lo; field_t carry_hi_0 = r2 * shift_right_2; field_t carry_hi_1 = r3 * (shift_1 * shift_right_2); field_t carry_hi_2 = t1 * shift_right_2; @@ -2936,15 +3070,17 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vect if constexpr (HasPlookup) { carry_lo = carry_lo.normalize(); carry_hi = carry_hi.normalize(); - ctx->decompose_into_default_range(carry_lo.witness_index, static_cast(carry_lo_msb)); - ctx->decompose_into_default_range(carry_hi.witness_index, static_cast(carry_hi_msb)); + ctx->decompose_into_default_range(carry_lo.get_normalized_witness_index(), + static_cast(carry_lo_msb)); + ctx->decompose_into_default_range(carry_hi.get_normalized_witness_index(), + static_cast(carry_hi_msb)); } else { if ((carry_hi_msb + carry_lo_msb) < field_t::modulus.get_msb()) { field_t carry_combined = carry_lo + (carry_hi * carry_lo_shift); carry_combined = carry_combined.normalize(); const auto accumulators = ctx->decompose_into_base4_accumulators( - carry_combined.witness_index, + carry_combined.get_normalized_witness_index(), static_cast(carry_lo_msb + carry_hi_msb), "bigfield: carry_combined too large in unsafe_evaluate_multiple_multiply_add."); field_t accumulator_midpoint = field_t::from_witness_index( @@ -2954,11 +3090,11 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vect carry_lo = carry_lo.normalize(); carry_hi = carry_hi.normalize(); ctx->decompose_into_base4_accumulators( - carry_lo.witness_index, + carry_lo.get_normalized_witness_index(), static_cast(carry_lo_msb), "bigfield: carry_lo too large in unsafe_evaluate_multiple_multiply_add."); ctx->decompose_into_base4_accumulators( - carry_hi.witness_index, + carry_hi.get_normalized_witness_index(), static_cast(carry_hi_msb), "bigfield: carry_hi too large in unsafe_evaluate_multiple_multiply_add."); } @@ -2972,10 +3108,21 @@ void bigfield::unsafe_evaluate_square_add(const bigfield& left, const bigfield& quotient, const bigfield& remainder) { + ASSERT(to_add.size() <= MAXIMUM_SUMMAND_COUNT); + if (HasPlookup) { unsafe_evaluate_multiply_add(left, left, to_add, quotient, { remainder }); return; } + + // Sanity checks + left.sanity_check(); + remainder.sanity_check(); + quotient.sanity_check(); + for (auto& el : to_add) { + el.sanity_check(); + } + Builder* ctx = left.context == nullptr ? quotient.context : left.context; uint512_t max_b0 = (left.binary_basis_limbs[1].maximum_value * left.binary_basis_limbs[0].maximum_value); @@ -3096,15 +3243,15 @@ void bigfield::unsafe_evaluate_square_add(const bigfield& left, if constexpr (HasPlookup) { carry_lo = carry_lo.normalize(); carry_hi = carry_hi.normalize(); - ctx->decompose_into_default_range(carry_lo.witness_index, static_cast(carry_lo_msb)); - ctx->decompose_into_default_range(carry_hi.witness_index, static_cast(carry_hi_msb)); + ctx->decompose_into_default_range(carry_lo.get_normalized_witness_index(), static_cast(carry_lo_msb)); + ctx->decompose_into_default_range(carry_hi.get_normalized_witness_index(), static_cast(carry_hi_msb)); } else { if ((carry_hi_msb + carry_lo_msb) < field_t::modulus.get_msb()) { field_t carry_combined = carry_lo + (carry_hi * carry_lo_shift); carry_combined = carry_combined.normalize(); const auto accumulators = ctx->decompose_into_base4_accumulators( - carry_combined.witness_index, + carry_combined.get_normalized_witness_index(), static_cast(carry_lo_msb + carry_hi_msb), "bigfield: carry_combined too large in unsafe_evaluate_square_add."); field_t accumulator_midpoint = @@ -3113,10 +3260,10 @@ void bigfield::unsafe_evaluate_square_add(const bigfield& left, } else { carry_lo = carry_lo.normalize(); carry_hi = carry_hi.normalize(); - ctx->decompose_into_base4_accumulators(carry_lo.witness_index, + ctx->decompose_into_base4_accumulators(carry_lo.get_normalized_witness_index(), static_cast(carry_lo_msb), "bigfield: carry_lo too large in unsafe_evaluate_square_add."); - ctx->decompose_into_base4_accumulators(carry_hi.witness_index, + ctx->decompose_into_base4_accumulators(carry_hi.get_normalized_witness_index(), static_cast(carry_hi_msb), "bigfield: carry_hi too large in unsafe_evaluate_square_add"); } @@ -3127,6 +3274,8 @@ template std::pair bigfield::compute_quotient_remainder_values( const bigfield& a, const bigfield& b, const std::vector& to_add) { + ASSERT(to_add.size() <= MAXIMUM_SUMMAND_COUNT); + uint512_t add_values(0); for (const auto& add_element : to_add) { add_element.reduction_check(); @@ -3149,6 +3298,8 @@ uint512_t bigfield::compute_maximum_quotient_value(const std::vector const std::vector& to_add) { ASSERT(as.size() == bs.size()); + ASSERT(to_add.size() <= MAXIMUM_SUMMAND_COUNT); + uint512_t add_values(0); for (const auto& add_element : to_add) { add_values += add_element; @@ -3171,6 +3322,11 @@ std::pair bigfield::get_quotient_reduction_info(const const std::vector& remainders_max) { ASSERT(as_max.size() == bs_max.size()); + + ASSERT(to_add.size() <= MAXIMUM_SUMMAND_COUNT); + ASSERT(as_max.size() <= MAXIMUM_SUMMAND_COUNT); + ASSERT(remainders_max.size() <= MAXIMUM_SUMMAND_COUNT); + // Check if the product sum can overflow CRT modulus if (mul_product_overflows_crt_modulus(as_max, bs_max, to_add)) { return std::pair(true, 0); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/goblin_field.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/goblin_field.hpp index 8e18a679ce4..c26305e7d4d 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/goblin_field.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/goblin_field.hpp @@ -53,9 +53,13 @@ template class goblin_field { // N.B. this method is because AggregationState expects group element coordinates to be split into 4 slices // (we could update to only use 2 for Mega but that feels complex) - goblin_field(field_ct lolo, field_ct lohi, field_ct hilo, field_ct hihi, [[maybe_unused]] bool can_overflow = false) - : limbs{ lolo + lohi * (uint256_t(1) << NUM_LIMB_BITS), hilo + hihi * (uint256_t(1) << NUM_LIMB_BITS) } - {} + static goblin_field construct_from_limbs( + field_ct lolo, field_ct lohi, field_ct hilo, field_ct hihi, [[maybe_unused]] bool can_overflow = false) + { + goblin_field result; + result.limbs = { lolo + lohi * (uint256_t(1) << NUM_LIMB_BITS), hilo + hihi * (uint256_t(1) << NUM_LIMB_BITS) }; + return result; + } void assert_equal(const goblin_field& other) const { diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_impl.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_impl.hpp index aa667bf2595..03872a3a945 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_impl.hpp @@ -93,7 +93,7 @@ element element::operator+(const element& other) con // If either inputs are points at infinity, we set lambda_denominator to be 1. This ensures we never trigger a // divide by zero error. // Note: if either inputs are points at infinity we will not use the result of this computation. - Fq safe_edgecase_denominator = Fq(field_t(1), field_t(0), field_t(0), field_t(0)); + Fq safe_edgecase_denominator = Fq(1); lambda_denominator = Fq::conditional_assign( lhs_infinity || rhs_infinity || infinity_predicate, safe_edgecase_denominator, lambda_denominator); const Fq lambda = Fq::div_without_denominator_check({ lambda_numerator }, lambda_denominator); @@ -163,7 +163,7 @@ element element::operator-(const element& other) con // If either inputs are points at infinity, we set lambda_denominator to be 1. This ensures we never trigger a // divide by zero error. // (if either inputs are points at infinity we will not use the result of this computation) - Fq safe_edgecase_denominator = Fq(field_t(1), field_t(0), field_t(0), field_t(0)); + Fq safe_edgecase_denominator = Fq(1); lambda_denominator = Fq::conditional_assign( lhs_infinity || rhs_infinity || infinity_predicate, safe_edgecase_denominator, lambda_denominator); const Fq lambda = Fq::div_without_denominator_check({ lambda_numerator }, lambda_denominator); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_tables.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_tables.hpp index 821779acbf6..40cfcf24abc 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_tables.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup_tables.hpp @@ -74,8 +74,9 @@ element element::read_group_element_rom_tables( const auto yhi = tables[3][index]; const auto xyprime = tables[4][index]; - Fq x_fq(xlo[0], xlo[1], xhi[0], xhi[1], xyprime[0]); - Fq y_fq(ylo[0], ylo[1], yhi[0], yhi[1], xyprime[1]); + // We assign maximum_value of each limb here, so we can use the unsafe API from element construction + Fq x_fq = Fq::unsafe_construct_from_limbs(xlo[0], xlo[1], xhi[0], xhi[1], xyprime[0]); + Fq y_fq = Fq::unsafe_construct_from_limbs(ylo[0], ylo[1], yhi[0], yhi[1], xyprime[1]); x_fq.binary_basis_limbs[0].maximum_value = limb_max[0]; x_fq.binary_basis_limbs[1].maximum_value = limb_max[1]; x_fq.binary_basis_limbs[2].maximum_value = limb_max[2]; @@ -157,8 +158,10 @@ element element::eight_bit_fixed_base_table::oper const auto yhi = plookup_read::read_pair_from_table(tags[3], index); const auto xyprime = plookup_read::read_pair_from_table(tags[4], index); - Fq x = Fq(xlo.first, xlo.second, xhi.first, xhi.second, xyprime.first); - Fq y = Fq(ylo.first, ylo.second, yhi.first, yhi.second, xyprime.second); + // All the elements are precomputed constants so they are completely reduced, so the default maximum limb values are + // appropriate + Fq x = Fq::unsafe_construct_from_limbs(xlo.first, xlo.second, xhi.first, xhi.second, xyprime.first); + Fq y = Fq::unsafe_construct_from_limbs(ylo.first, ylo.second, yhi.first, yhi.second, xyprime.second); if (use_endomorphism) { y = -y; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.hpp index 686b9a6deb1..76d4c333700 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.hpp @@ -63,6 +63,8 @@ template class bool_t { bool_t normalize() const; + uint32_t get_normalized_witness_index() const { return normalize().witness_index; } + Builder* get_context() const { return context; } void set_origin_tag(const OriginTag& new_tag) const { tag = new_tag; } diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp index 6639158e411..6fa48ee7957 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp @@ -249,7 +249,7 @@ template class DataBusDepot { l1.create_range_constraint(Fq::NUM_LIMB_BITS, "l1"); l2.create_range_constraint(Fq::NUM_LIMB_BITS, "l2"); l3.create_range_constraint(Fq::NUM_LAST_LIMB_BITS, "l3"); - return Fq(l0, l1, l2, l3, /*can_overflow=*/false); + return Fq::construct_from_limbs(l0, l1, l2, l3, /*can_overflow=*/false); } void assert_equality_of_commitments(const Commitment& P0, const Commitment& P1) diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.cpp index 1081be69d34..51b4b726be5 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.cpp @@ -77,7 +77,15 @@ template field_t::operator bool_t() const bool mul_constant_check = (multiplicative_constant == bb::fr::one()); bool inverted_check = (additive_constant == bb::fr::one()) && (multiplicative_constant == bb::fr::neg_one()); if ((!add_constant_check || !mul_constant_check) && !inverted_check) { - normalize(); + auto normalized_element = normalize(); + bb::fr witness = context->get_variable(normalized_element.get_witness_index()); + ASSERT((witness == bb::fr::zero()) || (witness == bb::fr::one())); + bool_t result(context); + result.witness_bool = (witness == bb::fr::one()); + result.witness_inverted = false; + result.witness_index = normalized_element.get_witness_index(); + context->create_bool_gate(normalized_element.get_witness_index()); + return result; } bb::fr witness = context->get_variable(witness_index); @@ -509,6 +517,18 @@ template field_t field_t::add_two(const fie return result; } +/** + * @brief Return an new element, where the in-circuit witness contains the actual represented value (multiplicative + * constant is 1 and additive_constant is 0) + * + * @details If the element is a constant or it is already normalized, just return the element itself + * + *@todo We need to add a mechanism into the circuit builders for caching normalized variants for fields and bigfields. + *It should make the circuits smaller. https://github.com/AztecProtocol/barretenberg/issues/1052 + * + * @tparam Builder + * @return field_t + */ template field_t field_t::normalize() const { if (witness_index == IS_CONSTANT || @@ -923,6 +943,7 @@ void field_t::evaluate_linear_identity(const field_t& a, const field_t& if (a.witness_index == IS_CONSTANT && b.witness_index == IS_CONSTANT && c.witness_index == IS_CONSTANT && d.witness_index == IS_CONSTANT) { + ASSERT(a.get_value() + b.get_value() + c.get_value() + d.get_value() == 0); return; } @@ -958,6 +979,7 @@ void field_t::evaluate_polynomial_identity(const field_t& a, if (a.witness_index == IS_CONSTANT && b.witness_index == IS_CONSTANT && c.witness_index == IS_CONSTANT && d.witness_index == IS_CONSTANT) { + ASSERT((a.get_value() * b.get_value() + c.get_value() + d.get_value()).is_zero()); return; } @@ -1187,7 +1209,6 @@ std::vector> field_t::decompose_into_bits( // y_lo = (2**128 + p_lo) - sum_lo field_t y_lo = (-sum) + (p_lo + shift); y_lo += shifted_high_limb; - y_lo.normalize(); if constexpr (IsSimulator) { fr sum_lo = shift + p_lo - y_lo.get_value(); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.hpp index 479c274ec57..77a1d4d8759 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.hpp @@ -256,7 +256,7 @@ template class field_t { * Constants do not need to be normalized, as there is no underlying 'witness'; a constant's value is * wholly tracked by `this.additive_constant`, so we definitely don't want to set that to 0! **/ - field_t normalize() const; + [[nodiscard]] field_t normalize() const; bb::fr get_value() const; @@ -313,8 +313,26 @@ template class field_t { context->fix_witness(witness_index, get_value()); } + /** + * @brief Get the witness index of the current field element. + * + * @warning Are you sure you don't want to use + * get_normalized_witness_index? + * + * @return uint32_t + */ uint32_t get_witness_index() const { return witness_index; } + /** + * @brief Get the index of a normalized version of this element + * + * @details Most of the time when using field elements in other parts of stdlib we want to use this API instead of + * get_witness index. The reason is it will prevent some soundess vulnerabilities + * + * @return uint32_t + */ + uint32_t get_normalized_witness_index() const { return normalize().witness_index; } + std::vector> decompose_into_bits( size_t num_bits = 256, std::function(Builder* ctx, uint64_t, uint256_t)> get_bit = diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp index 8da6527284c..546e79012ec 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp @@ -107,6 +107,17 @@ template class stdlib_field : public testing::Test { run_test(-1, fr::modulus.get_msb() + 1, true); } + /** + * @brief Test that bool is converted correctly + * + */ + static void test_bool_conversion_regression() + { + Builder builder = Builder(); + field_ct one = field_ct(witness_ct(&builder, 1)); + bool_ct b_false = bool_ct(one * field_ct(0)); + EXPECT_FALSE(b_false.get_value()); + } /** * @brief Demonstrate current behavior of assert_equal. */ @@ -1099,6 +1110,11 @@ TYPED_TEST(stdlib_field, test_assert_equal) TestFixture::test_assert_equal(); } +TYPED_TEST(stdlib_field, test_bool_conversion_regression) +{ + TestFixture::test_bool_conversion_regression(); +} + TYPED_TEST(stdlib_field, test_div) { TestFixture::test_div(); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/plookup/plookup.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/plookup/plookup.test.cpp index 9c36c59a809..a76a95a9a1d 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/plookup/plookup.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/plookup/plookup.test.cpp @@ -555,8 +555,8 @@ TEST(stdlib_plookup, secp256k1_generator) const auto xhi = plookup_read::read_pair_from_table(MultiTableId::SECP256K1_XHI, index); const auto ylo = plookup_read::read_pair_from_table(MultiTableId::SECP256K1_YLO, index); const auto yhi = plookup_read::read_pair_from_table(MultiTableId::SECP256K1_YHI, index); - curve::fq_ct x = curve::fq_ct(xlo.first, xlo.second, xhi.first, xhi.second); - curve::fq_ct y = curve::fq_ct(ylo.first, ylo.second, yhi.first, yhi.second); + curve::fq_ct x = curve::fq_ct::unsafe_construct_from_limbs(xlo.first, xlo.second, xhi.first, xhi.second); + curve::fq_ct y = curve::fq_ct::unsafe_construct_from_limbs(ylo.first, ylo.second, yhi.first, yhi.second); const auto res = curve::g1_ct(x, y).get_value(); curve::fr scalar(i); @@ -573,8 +573,8 @@ TEST(stdlib_plookup, secp256k1_generator) const auto ylo = plookup_read::read_pair_from_table(MultiTableId::SECP256K1_YLO, circuit_naf_values[0]); const auto yhi = plookup_read::read_pair_from_table(MultiTableId::SECP256K1_YHI, circuit_naf_values[0]); - curve::fq_ct x = curve::fq_ct(xlo.first, xlo.second, xhi.first, xhi.second); - curve::fq_ct y = curve::fq_ct(ylo.first, ylo.second, yhi.first, yhi.second); + curve::fq_ct x = curve::fq_ct::unsafe_construct_from_limbs(xlo.first, xlo.second, xhi.first, xhi.second); + curve::fq_ct y = curve::fq_ct::unsafe_construct_from_limbs(ylo.first, ylo.second, yhi.first, yhi.second); accumulator = curve::g1_ct(x, y); } for (size_t i = 1; i < circuit_naf_values.size(); ++i) { @@ -590,8 +590,8 @@ TEST(stdlib_plookup, secp256k1_generator) const auto xhi = plookup_read::read_pair_from_table(MultiTableId::SECP256K1_XHI, circuit_naf_values[i]); const auto ylo = plookup_read::read_pair_from_table(MultiTableId::SECP256K1_YLO, circuit_naf_values[i]); const auto yhi = plookup_read::read_pair_from_table(MultiTableId::SECP256K1_YHI, circuit_naf_values[i]); - curve::fq_ct x = curve::fq_ct(xlo.first, xlo.second, xhi.first, xhi.second); - curve::fq_ct y = curve::fq_ct(ylo.first, ylo.second, yhi.first, yhi.second); + curve::fq_ct x = curve::fq_ct::unsafe_construct_from_limbs(xlo.first, xlo.second, xhi.first, xhi.second); + curve::fq_ct y = curve::fq_ct::unsafe_construct_from_limbs(ylo.first, ylo.second, yhi.first, yhi.second); accumulator = accumulator.montgomery_ladder(curve::g1_ct(x, y)); } diff --git a/barretenberg/cpp/src/barretenberg/stdlib/translator_vm_verifier/translator_recursive_verifier.cpp b/barretenberg/cpp/src/barretenberg/stdlib/translator_vm_verifier/translator_recursive_verifier.cpp index a94fe57d157..a4a4befd848 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/translator_vm_verifier/translator_recursive_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/translator_vm_verifier/translator_recursive_verifier.cpp @@ -137,7 +137,8 @@ bool TranslatorRecursiveVerifier_::verify_translation( typename Flavor::FF>& translation_evaluations) { const auto reconstruct_from_array = [&](const auto& arr) { - const BF reconstructed = BF(arr[0], arr[1], arr[2], arr[3]); + const BF reconstructed = BF::construct_from_limbs(arr[0], arr[1], arr[2], arr[3]); + return reconstructed; }; diff --git a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_circuit_builder.cpp b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_circuit_builder.cpp index 180908807e5..9409d1c119c 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_circuit_builder.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_circuit_builder.cpp @@ -1669,7 +1669,7 @@ std::array UltraCircuitBuilder_::decompose_non_nat **/ template std::array UltraCircuitBuilder_::evaluate_non_native_field_multiplication( - const non_native_field_witnesses& input, const bool range_constrain_quotient_and_remainder) + const non_native_field_witnesses& input) { std::array a{ @@ -1697,8 +1697,6 @@ std::array UltraCircuitBuilder_::evaluate_non_nati this->get_variable(input.r[3]), }; constexpr FF LIMB_SHIFT = uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS; - constexpr FF LIMB_SHIFT_2 = uint256_t(1) << (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS); - constexpr FF LIMB_SHIFT_3 = uint256_t(1) << (3 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS); constexpr FF LIMB_RSHIFT = FF(1) / FF(uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS); constexpr FF LIMB_RSHIFT_2 = FF(1) / FF(uint256_t(1) << (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS)); @@ -1722,35 +1720,6 @@ std::array UltraCircuitBuilder_::evaluate_non_nati const uint32_t hi_2_idx = this->add_variable(hi_2); const uint32_t hi_3_idx = this->add_variable(hi_3); - // Sometimes we have already applied range constraints on the quotient and remainder - if (range_constrain_quotient_and_remainder) { - // /** - // * r_prime - r_0 - r_1 * 2^b - r_2 * 2^2b - r_3 * 2^3b = 0 - // * - // * w_4_omega - w_4 + w_1(2^b) + w_2(2^2b) + w_3(2^3b) = 0 - // * - // **/ - create_big_add_gate( - { input.r[1], input.r[2], input.r[3], input.r[4], LIMB_SHIFT, LIMB_SHIFT_2, LIMB_SHIFT_3, -1, 0 }, true); - // TODO(https://github.com/AztecProtocol/barretenberg/issues/879): dummy necessary for preceeding big add gate - create_dummy_gate(blocks.arithmetic, this->zero_idx, this->zero_idx, this->zero_idx, input.r[0]); - range_constrain_two_limbs(input.r[0], input.r[1]); - range_constrain_two_limbs(input.r[2], input.r[3]); - - // /** - // * q_prime - q_0 - q_1 * 2^b - q_2 * 2^2b - q_3 * 2^3b = 0 - // * - // * w_4_omega - w_4 + w_1(2^b) + w_2(2^2b) + w_3(2^3b) = 0 - // * - // **/ - create_big_add_gate( - { input.q[1], input.q[2], input.q[3], input.q[4], LIMB_SHIFT, LIMB_SHIFT_2, LIMB_SHIFT_3, -1, 0 }, true); - // TODO(https://github.com/AztecProtocol/barretenberg/issues/879): dummy necessary for preceeding big add gate - create_dummy_gate(blocks.arithmetic, this->zero_idx, this->zero_idx, this->zero_idx, input.q[0]); - range_constrain_two_limbs(input.q[0], input.q[1]); - range_constrain_two_limbs(input.q[2], input.q[3]); - } - // TODO(https://github.com/AztecProtocol/barretenberg/issues/879): Originally this was a single arithmetic gate. // With trace sorting, we must add a dummy gate since the add gate would otherwise try to read into an aux gate that // has been sorted out of sequence. @@ -1834,12 +1803,12 @@ void UltraCircuitBuilder_::process_non_native_field_multiplicat { for (size_t i = 0; i < cached_partial_non_native_field_multiplications.size(); ++i) { auto& c = cached_partial_non_native_field_multiplications[i]; - for (size_t j = 0; j < 5; ++j) { + for (size_t j = 0; j < c.a.size(); ++j) { c.a[j] = this->real_variable_index[c.a[j]]; c.b[j] = this->real_variable_index[c.b[j]]; } } - cached_partial_non_native_field_multiplication::deduplicate(cached_partial_non_native_field_multiplications); + cached_partial_non_native_field_multiplication::deduplicate(cached_partial_non_native_field_multiplications, this); // iterate over the cached items and create constraints for (const auto& input : cached_partial_non_native_field_multiplications) { diff --git a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_circuit_builder.hpp b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_circuit_builder.hpp index cdf460d035f..821339329fc 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_circuit_builder.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_circuit_builder.hpp @@ -21,12 +21,11 @@ namespace bb { template struct non_native_field_witnesses { // first 4 array elements = limbs - // 5th element = prime basis limb - std::array a; - std::array b; - std::array q; - std::array r; - std::array neg_modulus; + std::array a; + std::array b; + std::array q; + std::array r; + std::array neg_modulus; FF modulus; }; @@ -196,31 +195,48 @@ class UltraCircuitBuilder_ : public CircuitBuilderBase a; - std::array b; - FF lo_0; - FF hi_0; - FF hi_1; + std::array a; + std::array b; + uint32_t lo_0; + uint32_t hi_0; + uint32_t hi_1; bool operator==(const cached_partial_non_native_field_multiplication& other) const { bool valid = true; - for (size_t i = 0; i < 5; ++i) { + for (size_t i = 0; i < 4; ++i) { valid = valid && (a[i] == other.a[i]); valid = valid && (b[i] == other.b[i]); } return valid; } - static void deduplicate(std::vector& vec) + /** + * @brief Dedupilcate cache entries which represent multiplication of the same witnesses + * + * @details While a and b witness vectors are the same, lo_0, hi_0 and hi_1 can vary, so we have to connect them + * or there is a vulnerability + * + * @param vec + * @param circuit_builder + */ + static void deduplicate(std::vector& vec, + UltraCircuitBuilder_* circuit_builder) { std::unordered_set> seen; std::vector uniqueVec; for (const auto& item : vec) { - if (seen.insert(item).second) { + auto [existing_element, not_in_set] = seen.insert(item); + // Memorize if not in set yet + if (not_in_set) { uniqueVec.push_back(item); + } else { + // If we already have a representative, we need to connect the outputs together + circuit_builder->assert_equal(item.lo_0, (*existing_element).lo_0); + circuit_builder->assert_equal(item.hi_0, (*existing_element).hi_0); + circuit_builder->assert_equal(item.hi_1, (*existing_element).hi_1); } } @@ -801,8 +817,7 @@ class UltraCircuitBuilder_ : public CircuitBuilderBase decompose_non_native_field_double_width_limb( const uint32_t limb_idx, const size_t num_limb_bits = (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS)); - std::array evaluate_non_native_field_multiplication( - const non_native_field_witnesses& input, const bool range_constrain_quotient_and_remainder = true); + std::array evaluate_non_native_field_multiplication(const non_native_field_witnesses& input); std::array queue_partial_non_native_field_multiplication(const non_native_field_witnesses& input); typedef std::pair scaled_witness; typedef std::tuple add_simple; diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_honk.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_honk.test.cpp index 62dfe74c9c1..79f6406cbe6 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_honk.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_honk.test.cpp @@ -756,22 +756,20 @@ TYPED_TEST(UltraHonkTests, non_native_field_multiplication) const auto split_into_limbs = [&](const uint512_t& input) { constexpr size_t NUM_BITS = 68; - std::array limbs; + std::array limbs; limbs[0] = input.slice(0, NUM_BITS).lo; limbs[1] = input.slice(NUM_BITS * 1, NUM_BITS * 2).lo; limbs[2] = input.slice(NUM_BITS * 2, NUM_BITS * 3).lo; limbs[3] = input.slice(NUM_BITS * 3, NUM_BITS * 4).lo; - limbs[4] = fr(input.lo); return limbs; }; - const auto get_limb_witness_indices = [&](const std::array& limbs) { - std::array limb_indices; + const auto get_limb_witness_indices = [&](const std::array& limbs) { + std::array limb_indices; limb_indices[0] = circuit_builder.add_variable(limbs[0]); limb_indices[1] = circuit_builder.add_variable(limbs[1]); limb_indices[2] = circuit_builder.add_variable(limbs[2]); limb_indices[3] = circuit_builder.add_variable(limbs[3]); - limb_indices[4] = circuit_builder.add_variable(limbs[4]); return limb_indices; }; const uint512_t BINARY_BASIS_MODULUS = uint512_t(1) << (68 * 4);