From ca5a3c7a7fc147d228d31e03c3da8ba912de5cfa Mon Sep 17 00:00:00 2001 From: David Banks <47112877+dbanks12@users.noreply.github.com> Date: Mon, 12 Dec 2022 11:31:21 -0500 Subject: [PATCH 1/7] Fix to bootstrap.sh after move into submodule (#1) * Updated bootstrap now that bberg may be a submodule, and now that cpp subdir exists * fix gitignore, wasnt properly ignoring cpp/build* * fix the barretenberg cpp formatting pre-commit hook now that barretenberg iss its own repo and may be a submodule --- cpp/.gitignore | 2 +- cpp/bootstrap.sh | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cpp/.gitignore b/cpp/.gitignore index e2d5bec647..be056d1bc0 100644 --- a/cpp/.gitignore +++ b/cpp/.gitignore @@ -1,4 +1,4 @@ -./build* +build*/ src/wasi-sdk-* src/aztec/plonk/proof_system/proving_key/fixtures src/aztec/rollup/proofs/*/fixtures diff --git a/cpp/bootstrap.sh b/cpp/bootstrap.sh index 2d28fe3e56..3f61cf59b3 100755 --- a/cpp/bootstrap.sh +++ b/cpp/bootstrap.sh @@ -6,8 +6,13 @@ rm -rf ./build rm -rf ./build-wasm # Install formatting git hook. -echo "cd ./barretenberg && ./format.sh staged" > ../.git/hooks/pre-commit -chmod +x ../.git/hooks/pre-commit +HOOKS_DIR=$(git rev-parse --git-path hooks) +# The pre-commit script will live in a barretenberg-specific hooks directory +# That may be just in the top level of this repository, +# or may be in a .git/modules/barretenberg subdirectory when this is actually a submodule +# Either way, running `git rev-parse --show-toplevel` from the hooks directory gives the path to barretenberg +echo "cd \$(git rev-parse --show-toplevel)/cpp && ./format.sh staged" > $HOOKS_DIR/pre-commit +chmod +x $HOOKS_DIR/pre-commit # Determine system. if [[ "$OSTYPE" == "darwin"* ]]; then From 68aff9c99c683956f417d069360ca36deb5e351c Mon Sep 17 00:00:00 2001 From: Charlie Lye Date: Mon, 12 Dec 2022 21:04:06 +0000 Subject: [PATCH 2/7] Contrived change. --- cpp/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 5805f2db92..c346f05b71 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -68,4 +68,4 @@ include(cmake/gtest.cmake) include(cmake/benchmark.cmake) include(cmake/module.cmake) -add_subdirectory(src/aztec) \ No newline at end of file +add_subdirectory(src/aztec) From 3eee2fdd5133490ae57fd2db94c645891e3e8fcf Mon Sep 17 00:00:00 2001 From: Charlie Lye Date: Mon, 12 Dec 2022 21:08:31 +0000 Subject: [PATCH 3/7] Revert "Contrived change." This reverts commit 68aff9c99c683956f417d069360ca36deb5e351c. --- cpp/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index c346f05b71..5805f2db92 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -68,4 +68,4 @@ include(cmake/gtest.cmake) include(cmake/benchmark.cmake) include(cmake/module.cmake) -add_subdirectory(src/aztec) +add_subdirectory(src/aztec) \ No newline at end of file From 80c38b49f8fe439ead1d951f48f1445fcdb82d2e Mon Sep 17 00:00:00 2001 From: Charlie Lye Date: Mon, 12 Dec 2022 21:22:24 +0000 Subject: [PATCH 4/7] Revert "Revert "Contrived change."" This reverts commit 3eee2fdd5133490ae57fd2db94c645891e3e8fcf. --- cpp/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 5805f2db92..c346f05b71 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -68,4 +68,4 @@ include(cmake/gtest.cmake) include(cmake/benchmark.cmake) include(cmake/module.cmake) -add_subdirectory(src/aztec) \ No newline at end of file +add_subdirectory(src/aztec) From 7644f254032eef38abe84a388e76a1ca06a25a55 Mon Sep 17 00:00:00 2001 From: Charlie Lye Date: Tue, 13 Dec 2022 14:41:59 +0000 Subject: [PATCH 5/7] Introduce cpp to paths. --- cpp/dockerfiles/Dockerfile.wasm-linux-clang | 2 +- cpp/dockerfiles/Dockerfile.x86_64-linux-clang | 8 ++++---- cpp/dockerfiles/Dockerfile.x86_64-linux-clang-assert | 2 +- cpp/dockerfiles/Dockerfile.x86_64-linux-gcc | 4 ++-- cpp/scripts/run_tests | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cpp/dockerfiles/Dockerfile.wasm-linux-clang b/cpp/dockerfiles/Dockerfile.wasm-linux-clang index 17dc4b8795..7f1bd47690 100644 --- a/cpp/dockerfiles/Dockerfile.wasm-linux-clang +++ b/cpp/dockerfiles/Dockerfile.wasm-linux-clang @@ -8,4 +8,4 @@ COPY . . RUN mkdir build && cd build && cmake -DTOOLCHAIN=wasm-linux-clang .. && make -j$(nproc) barretenberg.wasm FROM alpine:3.13 -COPY --from=builder /usr/src/barretenberg/build/bin/barretenberg.wasm /usr/src/barretenberg/build/bin/barretenberg.wasm +COPY --from=builder /usr/src/barretenberg/cpp/build/bin/barretenberg.wasm /usr/src/barretenberg/cpp/build/bin/barretenberg.wasm diff --git a/cpp/dockerfiles/Dockerfile.x86_64-linux-clang b/cpp/dockerfiles/Dockerfile.x86_64-linux-clang index 9554f11d35..475562d25e 100644 --- a/cpp/dockerfiles/Dockerfile.x86_64-linux-clang +++ b/cpp/dockerfiles/Dockerfile.x86_64-linux-clang @@ -23,7 +23,7 @@ RUN mkdir build && cd build && cmake -DOpenMP_omp_LIBRARY=/usr/local/lib/libomp. FROM alpine:3.13 RUN apk update && apk add llvm10-libs COPY --from=builder /usr/src/barretenberg/srs_db /usr/src/barretenberg/srs_db -COPY --from=builder /usr/src/barretenberg/build/bin/db_cli /usr/src/barretenberg/build/bin/db_cli -COPY --from=builder /usr/src/barretenberg/build/bin/rollup_cli /usr/src/barretenberg/build/bin/rollup_cli -COPY --from=builder /usr/src/barretenberg/build/bin/tx_factory /usr/src/barretenberg/build/bin/tx_factory -COPY --from=builder /usr/src/barretenberg/build/bin/keygen /usr/src/barretenberg/build/bin/keygen \ No newline at end of file +COPY --from=builder /usr/src/barretenberg/cpp/build/bin/db_cli /usr/src/barretenberg/cpp/build/bin/db_cli +COPY --from=builder /usr/src/barretenberg/cpp/build/bin/rollup_cli /usr/src/barretenberg/cpp/build/bin/rollup_cli +COPY --from=builder /usr/src/barretenberg/cpp/build/bin/tx_factory /usr/src/barretenberg/cpp/build/bin/tx_factory +COPY --from=builder /usr/src/barretenberg/cpp/build/bin/keygen /usr/src/barretenberg/cpp/build/bin/keygen \ No newline at end of file diff --git a/cpp/dockerfiles/Dockerfile.x86_64-linux-clang-assert b/cpp/dockerfiles/Dockerfile.x86_64-linux-clang-assert index cdd9c8c48f..66221d8107 100644 --- a/cpp/dockerfiles/Dockerfile.x86_64-linux-clang-assert +++ b/cpp/dockerfiles/Dockerfile.x86_64-linux-clang-assert @@ -23,4 +23,4 @@ RUN mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=RelWithAssert -DOpenMP_o FROM alpine:3.13 RUN apk update && apk add llvm10-libs curl COPY --from=builder /usr/src/barretenberg/srs_db /usr/src/barretenberg/srs_db -COPY --from=builder /usr/src/barretenberg/build/bin/*_tests /usr/src/barretenberg/build/bin/ \ No newline at end of file +COPY --from=builder /usr/src/barretenberg/cpp/build/bin/*_tests /usr/src/barretenberg/cpp/build/bin/ \ No newline at end of file diff --git a/cpp/dockerfiles/Dockerfile.x86_64-linux-gcc b/cpp/dockerfiles/Dockerfile.x86_64-linux-gcc index 30687a98f5..bba21d87a5 100644 --- a/cpp/dockerfiles/Dockerfile.x86_64-linux-gcc +++ b/cpp/dockerfiles/Dockerfile.x86_64-linux-gcc @@ -13,5 +13,5 @@ RUN mkdir build && cd build && cmake -DTOOLCHAIN=x86_64-linux-gcc -DCI=ON .. && FROM alpine:3.13 RUN apk update && apk add libstdc++ libgomp -COPY --from=builder /usr/src/barretenberg/build/bin/db_cli /usr/src/barretenberg/build/bin/db_cli -COPY --from=builder /usr/src/barretenberg/build/bin/rollup_cli /usr/src/barretenberg/build/bin/rollup_cli \ No newline at end of file +COPY --from=builder /usr/src/barretenberg/cpp/build/bin/db_cli /usr/src/barretenberg/cpp/build/bin/db_cli +COPY --from=builder /usr/src/barretenberg/cpp/build/bin/rollup_cli /usr/src/barretenberg/cpp/build/bin/rollup_cli \ No newline at end of file diff --git a/cpp/scripts/run_tests b/cpp/scripts/run_tests index 392134d1cc..07a0eec618 100755 --- a/cpp/scripts/run_tests +++ b/cpp/scripts/run_tests @@ -18,5 +18,5 @@ docker run --rm -t $IMAGE_URI /bin/sh -c "\ set -e; \ cd /usr/src/barretenberg/srs_db; \ ./download_ignition.sh 1; \ - cd /usr/src/barretenberg/build; \ + cd /usr/src/barretenberg/cpp/build; \ for BIN in $TESTS; do ./bin/\$BIN $@; done" \ No newline at end of file From 79475556a5b79d9e5f051438b61606e8eff67fbc Mon Sep 17 00:00:00 2001 From: Charlie Lye Date: Tue, 13 Dec 2022 14:56:35 +0000 Subject: [PATCH 6/7] More cpp path. --- cpp/dockerfiles/Dockerfile.arm64-linux-gcc | 2 +- cpp/dockerfiles/Dockerfile.wasm-linux-clang | 4 ++-- cpp/dockerfiles/Dockerfile.x86_64-linux-clang | 4 ++-- cpp/dockerfiles/Dockerfile.x86_64-linux-clang-assert | 4 ++-- cpp/dockerfiles/Dockerfile.x86_64-linux-gcc | 2 +- cpp/scripts/run_tests | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cpp/dockerfiles/Dockerfile.arm64-linux-gcc b/cpp/dockerfiles/Dockerfile.arm64-linux-gcc index c4b5623a73..7a9e7bf7df 100644 --- a/cpp/dockerfiles/Dockerfile.arm64-linux-gcc +++ b/cpp/dockerfiles/Dockerfile.arm64-linux-gcc @@ -1,5 +1,5 @@ FROM aztecprotocol/crosstool-ng-arm64:latest -WORKDIR /usr/src/barretenberg +WORKDIR /usr/src/barretenberg/cpp COPY . . RUN mkdir build && cd build && cmake -DTOOLCHAIN=arm64-linux-gcc .. && make -j$(nproc) RUN cd build && qemu-aarch64 ./test/barretenberg_tests diff --git a/cpp/dockerfiles/Dockerfile.wasm-linux-clang b/cpp/dockerfiles/Dockerfile.wasm-linux-clang index 7f1bd47690..7a2d16c126 100644 --- a/cpp/dockerfiles/Dockerfile.wasm-linux-clang +++ b/cpp/dockerfiles/Dockerfile.wasm-linux-clang @@ -1,9 +1,9 @@ FROM ubuntu:focal AS builder RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential wget git libssl-dev cmake curl binaryen RUN curl https://wasmtime.dev/install.sh -sSf | bash /dev/stdin --version v0.25.0 -WORKDIR /usr/src/barretenberg/src +WORKDIR /usr/src/barretenberg/cpp/src RUN curl -s -L https://github.com/CraneStation/wasi-sdk/releases/download/wasi-sdk-12/wasi-sdk-12.0-linux.tar.gz | tar zxfv - -WORKDIR /usr/src/barretenberg +WORKDIR /usr/src/barretenberg/cpp COPY . . RUN mkdir build && cd build && cmake -DTOOLCHAIN=wasm-linux-clang .. && make -j$(nproc) barretenberg.wasm diff --git a/cpp/dockerfiles/Dockerfile.x86_64-linux-clang b/cpp/dockerfiles/Dockerfile.x86_64-linux-clang index 475562d25e..87ac028098 100644 --- a/cpp/dockerfiles/Dockerfile.x86_64-linux-clang +++ b/cpp/dockerfiles/Dockerfile.x86_64-linux-clang @@ -15,14 +15,14 @@ RUN git clone -b release/10.x --depth 1 https://github.com/llvm/llvm-project.git && make -j$(nproc) \ && make install \ && cd ../.. && rm -rf llvm-project -WORKDIR /usr/src/barretenberg +WORKDIR /usr/src/barretenberg/cpp COPY . . # Only build binaries that are needed upstream. RUN mkdir build && cd build && cmake -DOpenMP_omp_LIBRARY=/usr/local/lib/libomp.a .. && make -j$(nproc) db_cli rollup_cli tx_factory keygen FROM alpine:3.13 RUN apk update && apk add llvm10-libs -COPY --from=builder /usr/src/barretenberg/srs_db /usr/src/barretenberg/srs_db +COPY --from=builder /usr/src/barretenberg/cpp/srs_db /usr/src/barretenberg/cpp/srs_db COPY --from=builder /usr/src/barretenberg/cpp/build/bin/db_cli /usr/src/barretenberg/cpp/build/bin/db_cli COPY --from=builder /usr/src/barretenberg/cpp/build/bin/rollup_cli /usr/src/barretenberg/cpp/build/bin/rollup_cli COPY --from=builder /usr/src/barretenberg/cpp/build/bin/tx_factory /usr/src/barretenberg/cpp/build/bin/tx_factory diff --git a/cpp/dockerfiles/Dockerfile.x86_64-linux-clang-assert b/cpp/dockerfiles/Dockerfile.x86_64-linux-clang-assert index 66221d8107..fb9ed4d28b 100644 --- a/cpp/dockerfiles/Dockerfile.x86_64-linux-clang-assert +++ b/cpp/dockerfiles/Dockerfile.x86_64-linux-clang-assert @@ -15,12 +15,12 @@ RUN git clone -b release/10.x --depth 1 https://github.com/llvm/llvm-project.git && make -j$(nproc) \ && make install \ && cd ../.. && rm -rf llvm-project -WORKDIR /usr/src/barretenberg +WORKDIR /usr/src/barretenberg/cpp COPY . . # Build everything to ensure everything builds. All tests will be run from the result of this build. RUN mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=RelWithAssert -DOpenMP_omp_LIBRARY=/usr/local/lib/libomp.a -DCI=ON .. && make -j$(nproc) FROM alpine:3.13 RUN apk update && apk add llvm10-libs curl -COPY --from=builder /usr/src/barretenberg/srs_db /usr/src/barretenberg/srs_db +COPY --from=builder /usr/src/barretenberg/cpp/srs_db /usr/src/barretenberg/cpp/srs_db COPY --from=builder /usr/src/barretenberg/cpp/build/bin/*_tests /usr/src/barretenberg/cpp/build/bin/ \ No newline at end of file diff --git a/cpp/dockerfiles/Dockerfile.x86_64-linux-gcc b/cpp/dockerfiles/Dockerfile.x86_64-linux-gcc index bba21d87a5..2eeaf4fd9f 100644 --- a/cpp/dockerfiles/Dockerfile.x86_64-linux-gcc +++ b/cpp/dockerfiles/Dockerfile.x86_64-linux-gcc @@ -6,7 +6,7 @@ RUN apk update \ cmake \ git \ curl -WORKDIR /usr/src/barretenberg +WORKDIR /usr/src/barretenberg/cpp COPY . . # Build the entire project (not just rollup_cli and db_cli), as we want to check everything builds under gcc. RUN mkdir build && cd build && cmake -DTOOLCHAIN=x86_64-linux-gcc -DCI=ON .. && make -j$(nproc) diff --git a/cpp/scripts/run_tests b/cpp/scripts/run_tests index 07a0eec618..e6c8c8d28d 100755 --- a/cpp/scripts/run_tests +++ b/cpp/scripts/run_tests @@ -16,7 +16,7 @@ fi docker run --rm -t $IMAGE_URI /bin/sh -c "\ set -e; \ - cd /usr/src/barretenberg/srs_db; \ + cd /usr/src/barretenberg/cpp/srs_db; \ ./download_ignition.sh 1; \ cd /usr/src/barretenberg/cpp/build; \ for BIN in $TESTS; do ./bin/\$BIN $@; done" \ No newline at end of file From 1fd47498742dba02cd7f8b6e201995210f6845a4 Mon Sep 17 00:00:00 2001 From: Charlie Lye Date: Fri, 16 Dec 2022 11:22:13 +0000 Subject: [PATCH 7/7] Cl/mum merge (#4) * Initial merge. Still has conflicts. * Tidied up bigfield and biggroup * Builds. * Order constants. * Fix. * Make more tests pass. * Update circuit change detection stuff to be local to tests. * Include header. * Cleanup and settings. * Path updates. * Path updates. Co-authored-by: Rumata888 --- .circleci/config.yml | 36 +- .vscode/c_cpp_properties.json | 17 + .vscode/extensions.json | 9 + .vscode/settings.json | 8 + cpp/CMakeLists.txt | 8 +- cpp/bootstrap.sh | 10 +- cpp/cmake/module.cmake | 5 + cpp/cmake/threading.cmake | 13 + .../Dockerfile.x86_64-linux-clang-assert | 1 + cpp/scripts/bb-tests | 3 +- cpp/scripts/run_tests | 9 +- cpp/src/aztec/CMakeLists.txt | 3 + .../benchmark/pippenger_bench/CMakeLists.txt | 2 +- .../aztec/benchmark/pippenger_bench/main.cpp | 2 +- cpp/src/aztec/crypto/CMakeLists.txt | 2 + cpp/src/aztec/crypto/blake3s/CMakeLists.txt | 1 + cpp/src/aztec/crypto/blake3s/blake3-impl.hpp | 80 + cpp/src/aztec/crypto/blake3s/blake3s.cpp | 281 ++ cpp/src/aztec/crypto/blake3s/blake3s.hpp | 96 + cpp/src/aztec/crypto/blake3s/blake3s.test.cpp | 398 ++ cpp/src/aztec/crypto/blake3s/c_bind.cpp | 15 + .../aztec/crypto/blake3s_full/CMakeLists.txt | 1 + .../aztec/crypto/blake3s_full/blake3-impl.hpp | 294 ++ cpp/src/aztec/crypto/blake3s_full/blake3s.cpp | 884 +++++ cpp/src/aztec/crypto/blake3s_full/blake3s.hpp | 140 + .../crypto/blake3s_full/blake3s.test.cpp | 896 +++++ .../pedersen/convert_buffer_to_field.hpp | 37 + cpp/src/aztec/crypto/pedersen/pedersen.cpp | 27 +- .../aztec/crypto/pedersen/pedersen_lookup.cpp | 211 ++ .../aztec/crypto/pedersen/pedersen_lookup.hpp | 49 + .../crypto/pedersen/pedersen_lookup.test.cpp | 143 + .../aztec/crypto/pedersen/sidon_pedersen.cpp | 171 - .../aztec/crypto/pedersen/sidon_pedersen.hpp | 38 - .../crypto/pedersen/sidon_pedersen.test.cpp | 133 - cpp/src/aztec/ecc/curves/bn254/fq.test.cpp | 8 +- cpp/src/aztec/ecc/curves/bn254/fr.hpp | 4 + cpp/src/aztec/ecc/curves/bn254/fr.test.cpp | 9 +- cpp/src/aztec/ecc/curves/bn254/g2.test.cpp | 29 +- .../bn254/scalar_multiplication/pippenger.cpp | 15 +- .../bn254/scalar_multiplication/pippenger.hpp | 4 +- .../scalar_multiplication.cpp | 6 +- .../scalar_multiplication.test.cpp | 10 +- .../ecc/curves/grumpkin/grumpkin.test.cpp | 2 +- .../aztec/ecc/curves/secp256k1/secp256k1.cpp | 8 +- .../aztec/ecc/curves/secp256k1/secp256k1.hpp | 35 +- .../ecc/curves/secp256k1/secp256k1.test.cpp | 79 +- .../curves/secp256k1/secp256k1_endo_notes.hpp | 164 + .../aztec/ecc/curves/secp256r1/secp256r1.cpp | 9 +- .../aztec/ecc/curves/secp256r1/secp256r1.hpp | 3 - cpp/src/aztec/ecc/curves/types.hpp | 5 + cpp/src/aztec/ecc/fields/field.hpp | 93 +- cpp/src/aztec/ecc/fields/field12.hpp | 3 +- cpp/src/aztec/ecc/fields/field2.hpp | 5 +- cpp/src/aztec/ecc/fields/field_impl.hpp | 2 + .../aztec/ecc/fields/field_impl_generic.hpp | 4 +- cpp/src/aztec/ecc/groups/affine_element.hpp | 97 +- .../aztec/ecc/groups/affine_element.test.cpp | 98 +- .../aztec/ecc/groups/affine_element_impl.hpp | 47 +- cpp/src/aztec/ecc/groups/element.hpp | 4 +- cpp/src/aztec/ecc/groups/element_impl.hpp | 13 +- cpp/src/aztec/ecc/groups/group.hpp | 7 +- cpp/src/aztec/ecc/groups/wnaf.hpp | 4 +- cpp/src/aztec/ecc/groups/wnaf.test.cpp | 3 +- .../aztec/lagrange_base_gen/CMakeLists.txt | 10 + .../lagrange_base_gen/lagrange_base_gen.sh | 28 + .../lagrange_base_processor.cpp | 53 + cpp/src/aztec/numeric/bitop/sparse_form.hpp | 6 +- cpp/src/aztec/numeric/uint256/uint256.hpp | 10 + cpp/src/aztec/plonk/CMakeLists.txt | 2 +- .../aztec/plonk/composer/composer_base.cpp | 208 +- .../aztec/plonk/composer/composer_base.hpp | 368 +- .../plookup/compute_verification_key.cpp | 98 - .../plookup/compute_verification_key.hpp | 15 - .../aztec/plonk/composer/plookup_composer.cpp | 1659 -------- .../aztec/plonk/composer/plookup_composer.hpp | 345 -- .../plonk/composer/plookup_tables/aes128.hpp | 29 +- .../plonk/composer/plookup_tables/blake2s.hpp | 219 ++ .../non_native_group_generator.cpp | 498 +++ .../non_native_group_generator.hpp | 63 + .../composer/plookup_tables/pedersen.hpp | 201 +- .../plookup_tables/plookup_tables.cpp | 184 +- .../plookup_tables/plookup_tables.hpp | 175 +- .../plonk/composer/plookup_tables/sha256.hpp | 73 +- .../plookup_tables/sidon_pedersen.hpp | 258 -- .../plonk/composer/plookup_tables/sparse.hpp | 12 +- .../plonk/composer/plookup_tables/types.hpp | 177 +- .../plonk/composer/plookup_tables/uint.hpp | 20 +- .../standard/compute_verification_key.cpp | 62 - .../standard/compute_verification_key.hpp | 15 - .../plonk/composer/standard_composer.cpp | 16 +- .../plonk/composer/standard_composer.hpp | 61 +- .../turbo/compute_verification_key.cpp | 66 - .../turbo/compute_verification_key.hpp | 15 - .../aztec/plonk/composer/turbo_composer.cpp | 147 +- .../aztec/plonk/composer/turbo_composer.hpp | 62 +- .../plonk/composer/turbo_composer.test.cpp | 16 +- .../aztec/plonk/composer/ultra_composer.cpp | 2465 ++++++++++++ .../aztec/plonk/composer/ultra_composer.hpp | 585 +++ ...poser.test.cpp => ultra_composer.test.cpp} | 406 +- .../commitment_scheme.test.cpp | 2 +- .../kate_commitment_scheme.cpp | 24 +- .../kate_commitment_scheme.hpp | 5 +- .../aztec/plonk/proof_system/constants.hpp | 16 + .../plonk/proof_system/prover/c_bind.cpp | 39 +- .../proof_system/prover/c_bind_unrolled.cpp | 44 +- .../plonk/proof_system/prover/prover.cpp | 145 +- .../plonk/proof_system/prover/prover.hpp | 16 +- .../plonk/proof_system/prover/prover.test.cpp | 2 +- .../proof_system/proving_key/proving_key.cpp | 3 +- .../proof_system/proving_key/proving_key.hpp | 7 +- .../proof_system/proving_key/serialize.hpp | 6 +- .../public_inputs/public_inputs_impl.hpp | 100 +- .../types/polynomial_manifest.hpp | 171 +- .../proof_system/types/program_settings.hpp | 115 +- .../proof_system/types/prover_settings.hpp | 23 +- .../proof_system/utils/kate_verification.hpp | 14 +- .../plonk/proof_system/utils/permutation.hpp | 39 +- .../verification_key/verification_key.cpp | 1 + .../verification_key/verification_key.hpp | 8 +- .../verification_key.test.cpp | 1 + .../plonk/proof_system/verifier/verifier.cpp | 66 +- .../plonk/proof_system/verifier/verifier.hpp | 12 +- .../proof_system/verifier/verifier.test.cpp | 2 +- .../permutation_widget_impl.hpp | 423 ++- .../widgets/random_widgets/plookup_widget.hpp | 4 +- .../random_widgets/plookup_widget_impl.hpp | 356 +- .../transition_widgets/arithmetic_widget.hpp | 60 + .../create_dummy_transcript.hpp | 2 +- .../transition_widgets/elliptic_widget.hpp | 124 +- ..._base_widget.hpp => fixed_base_widget.hpp} | 105 +- .../genperm_sort_widget.hpp | 14 +- .../plookup_arithmetic_widget.hpp | 208 + .../plookup_auxiliary_widget.hpp | 256 ++ .../transition_widgets/transition_widget.hpp | 44 +- .../turbo_arithmetic_widget.hpp | 83 +- .../transition_widgets/turbo_logic_widget.hpp | 12 +- .../transition_widgets/turbo_range_widget.hpp | 9 +- cpp/src/aztec/plonk/transcript/manifest.hpp | 19 +- cpp/src/aztec/plonk/transcript/transcript.cpp | 45 +- cpp/src/aztec/plonk/transcript/transcript.hpp | 10 +- .../plonk/transcript/transcript_wrappers.hpp | 24 +- .../aztec/polynomials/evaluation_domain.cpp | 7 +- .../aztec/polynomials/evaluation_domain.hpp | 20 +- cpp/src/aztec/polynomials/polynomial.cpp | 17 + cpp/src/aztec/polynomials/polynomial.hpp | 17 +- .../polynomials/polynomial_arithmetic.cpp | 524 ++- .../polynomials/polynomial_arithmetic.hpp | 53 +- .../polynomial_arithmetic.test.cpp | 80 +- cpp/src/aztec/rollup/CMakeLists.txt | 5 +- .../aztec/rollup/ci_failsafe/CMakeLists.txt | 1 - .../rollup/ci_failsafe/failsafe.test.cpp | 13 - cpp/src/aztec/rollup/constants.hpp | 43 - cpp/src/aztec/rollup/proofs/CMakeLists.txt | 1 + .../aztec/rollup/proofs/account/account.cpp | 23 +- .../aztec/rollup/proofs/account/account.hpp | 6 +- .../rollup/proofs/account/account.test.cpp | 45 +- .../rollup/proofs/account/account_tx.hpp | 2 +- .../rollup/proofs/account/account_tx.test.cpp | 2 +- .../aztec/rollup/proofs/account/c_bind.cpp | 4 +- .../rollup/proofs/account/create_proof.hpp | 4 +- .../aztec/rollup/proofs/account/verify.hpp | 4 +- .../rollup/proofs/add_zero_public_inputs.hpp | 4 +- .../aztec/rollup/proofs/claim/claim.test.cpp | 35 +- .../rollup/proofs/claim/claim_circuit.hpp | 4 +- .../aztec/rollup/proofs/claim/claim_tx.hpp | 4 +- .../rollup/proofs/claim/create_proof.hpp | 4 +- .../aztec/rollup/proofs/claim/ratio_check.hpp | 4 +- .../rollup/proofs/claim/ratio_check.test.cpp | 56 +- cpp/src/aztec/rollup/proofs/claim/verify.hpp | 4 +- .../rollup/proofs/compute_circuit_data.hpp | 10 +- .../aztec/rollup/proofs/join_split/c_bind.cpp | 4 +- .../join_split/compute_circuit_data.cpp | 2 +- .../create_noop_join_split_proof.cpp | 8 +- .../rollup/proofs/join_split/create_proof.hpp | 4 +- .../rollup/proofs/join_split/join_split.cpp | 9 +- .../rollup/proofs/join_split/join_split.hpp | 6 +- .../proofs/join_split/join_split.test.cpp | 49 +- .../proofs/join_split/join_split_circuit.cpp | 1 - .../proofs/join_split/join_split_circuit.hpp | 4 +- .../join_split/join_split_js_parity.test.cpp | 2 +- .../proofs/join_split/join_split_tx.hpp | 4 +- .../proofs/join_split/join_split_tx.test.cpp | 2 +- .../aztec/rollup/proofs/mock/CMakeLists.txt | 6 + .../rollup/proofs/mock/mock_circuit.test.cpp | 38 + .../notes/circuit/account/account_note.hpp | 4 +- .../proofs/notes/circuit/account/commit.hpp | 4 +- .../rollup/proofs/notes/circuit/asset_id.cpp | 4 +- .../rollup/proofs/notes/circuit/asset_id.hpp | 4 +- .../proofs/notes/circuit/bridge_call_data.hpp | 4 +- .../proofs/notes/circuit/claim/claim_note.hpp | 4 +- .../claim/complete_partial_commitment.hpp | 4 +- .../notes/circuit/claim/compute_nullifier.hpp | 4 +- .../claim/create_partial_commitment.hpp | 4 +- .../notes/circuit/claim/witness_data.hpp | 4 +- .../defi_interaction/compute_nullifier.hpp | 4 +- .../notes/circuit/defi_interaction/note.hpp | 4 +- .../circuit/defi_interaction/witness_data.hpp | 4 +- .../value/complete_partial_commitment.hpp | 4 +- .../notes/circuit/value/compute_nullifier.cpp | 4 +- .../notes/circuit/value/compute_nullifier.hpp | 4 +- .../circuit/value/compute_nullifier.test.cpp | 4 +- .../value/create_partial_commitment.hpp | 4 +- .../proofs/notes/circuit/value/value_note.hpp | 4 +- .../notes/circuit/value/value_note.test.cpp | 20 +- .../notes/circuit/value/witness_data.hpp | 4 +- .../rollup/proofs/rollup/rollup_circuit.cpp | 13 +- .../rollup/proofs/rollup/rollup_circuit.hpp | 4 +- .../proofs/rollup/rollup_circuit.test.cpp | 2 +- .../rollup/rollup_circuit_full.test.cpp | 36 +- .../proofs/rollup/rollup_proof_data.hpp | 4 +- cpp/src/aztec/rollup/proofs/rollup/verify.cpp | 2 +- cpp/src/aztec/rollup/proofs/rollup/verify.hpp | 2 +- .../root_rollup/root_rollup_circuit.cpp | 13 +- .../root_rollup/root_rollup_circuit.hpp | 4 +- .../root_rollup/root_rollup_full.test.cpp | 36 +- .../root_rollup/root_rollup_proof_data.hpp | 4 +- .../rollup/proofs/root_rollup/verify.cpp | 2 +- .../rollup/proofs/root_rollup/verify.hpp | 2 +- .../root_verifier/root_verifier.test.cpp | 11 +- .../root_verifier/root_verifier_circuit.cpp | 3 +- .../root_verifier/root_verifier_circuit.hpp | 16 +- .../root_verifier/root_verifier_full.test.cpp | 36 +- .../rollup/proofs/standard_example/c_bind.cpp | 7 +- .../proofs/standard_example/c_bind.test.cpp | 2 +- .../standard_example/standard_example.cpp | 15 +- .../standard_example/standard_example.hpp | 11 +- .../standard_example.test.cpp | 5 +- cpp/src/aztec/rollup/proofs/verify.hpp | 56 +- cpp/src/aztec/rollup/rollup_cli/main.cpp | 1 - cpp/src/aztec/rollup/tx_factory/main.cpp | 2 +- cpp/src/aztec/srs/io.cpp | 173 +- cpp/src/aztec/srs/io.hpp | 32 +- .../lagrange_base.test.cpp | 94 +- .../file_reference_string.cpp | 16 +- .../file_reference_string.hpp | 53 +- .../reference_string/mem_reference_string.cpp | 17 +- .../reference_string/mem_reference_string.hpp | 14 +- .../mem_reference_string.test.cpp | 5 +- .../pippenger_reference_string.hpp | 14 +- .../reference_string/reference_string.hpp | 20 +- .../aztec/stdlib/encryption/aes128/aes128.cpp | 34 +- .../aztec/stdlib/encryption/aes128/aes128.hpp | 7 +- .../stdlib/encryption/aes128/aes128.test.cpp | 12 +- .../aztec/stdlib/encryption/ecdsa/ecdsa.hpp | 3 +- .../stdlib/encryption/ecdsa/ecdsa.test.cpp | 85 +- .../stdlib/encryption/ecdsa/ecdsa_impl.hpp | 35 +- .../stdlib/encryption/schnorr/schnorr.cpp | 14 +- .../stdlib/encryption/schnorr/schnorr.hpp | 13 +- .../encryption/schnorr/schnorr.test.cpp | 124 +- cpp/src/aztec/stdlib/hash/CMakeLists.txt | 1 + cpp/src/aztec/stdlib/hash/blake2s/blake2s.cpp | 75 +- cpp/src/aztec/stdlib/hash/blake2s/blake2s.hpp | 4 +- .../stdlib/hash/blake2s/blake2s.test.cpp | 75 +- .../stdlib/hash/blake2s/blake2s_plookup.cpp | 174 + .../stdlib/hash/blake2s/blake2s_plookup.hpp | 24 + .../aztec/stdlib/hash/blake2s/blake_util.hpp | 258 ++ .../aztec/stdlib/hash/blake3s/CMakeLists.txt | 1 + cpp/src/aztec/stdlib/hash/blake3s/blake3s.cpp | 267 ++ cpp/src/aztec/stdlib/hash/blake3s/blake3s.hpp | 19 + .../stdlib/hash/blake3s/blake3s.test.cpp | 123 + .../stdlib/hash/blake3s/blake3s_plookup.cpp | 267 ++ .../stdlib/hash/blake3s/blake3s_plookup.hpp | 24 + .../stdlib/hash/pedersen/pedersen.bench.cpp | 2 +- .../aztec/stdlib/hash/pedersen/pedersen.cpp | 101 +- .../aztec/stdlib/hash/pedersen/pedersen.hpp | 2 +- .../stdlib/hash/pedersen/pedersen.test.cpp | 176 +- .../stdlib/hash/pedersen/pedersen_plookup.cpp | 113 +- .../stdlib/hash/pedersen/pedersen_plookup.hpp | 17 +- .../aztec/stdlib/hash/sha256/sha256.bench.cpp | 6 +- cpp/src/aztec/stdlib/hash/sha256/sha256.cpp | 15 +- cpp/src/aztec/stdlib/hash/sha256/sha256.hpp | 10 +- .../aztec/stdlib/hash/sha256/sha256.test.cpp | 42 +- .../stdlib/hash/sha256/sha256_plookup.cpp | 129 +- .../stdlib/hash/sha256/sha256_plookup.hpp | 55 +- .../aztec/stdlib/merkle_tree/CMakeLists.txt | 2 +- .../aztec/stdlib/merkle_tree/hash.test.cpp | 4 +- .../stdlib/merkle_tree/membership.test.cpp | 4 +- .../stdlib/merkle_tree/memory_tree.test.cpp | 2 +- .../stdlib/merkle_tree/merkle_tree.test.cpp | 2 +- .../stdlib/primitives/bigfield/bigfield.hpp | 55 +- .../primitives/bigfield/bigfield.test.cpp | 131 +- .../primitives/bigfield/bigfield_impl.hpp | 1224 ++++-- .../stdlib/primitives/biggroup/biggroup.hpp | 888 +++-- .../primitives/biggroup/biggroup.test.cpp | 1318 +++---- .../biggroup/biggroup_batch_mul.hpp | 64 + .../primitives/biggroup/biggroup_bn254.hpp | 425 +++ .../primitives/biggroup/biggroup_impl.hpp | 538 +-- .../primitives/biggroup/biggroup_nafs.hpp | 500 +++ .../biggroup/biggroup_secp256k1.hpp | 141 + .../primitives/biggroup/biggroup_tables.hpp | 586 +++ .../primitives/byte_array/byte_array.cpp | 180 +- .../primitives/byte_array/byte_array.hpp | 23 +- .../primitives/byte_array/byte_array.test.cpp | 24 +- .../stdlib/primitives/composers/composers.hpp | 19 +- .../primitives/composers/composers_fwd.hpp | 19 +- .../aztec/stdlib/primitives/curves/bn254.hpp | 24 +- .../stdlib/primitives/curves/secp256k1.hpp | 33 + .../stdlib/primitives/curves/secp256r1.hpp | 28 +- .../aztec/stdlib/primitives/field/field.cpp | 445 ++- .../aztec/stdlib/primitives/field/field.hpp | 113 +- .../stdlib/primitives/field/field.test.cpp | 1388 ++++--- cpp/src/aztec/stdlib/primitives/field/pow.hpp | 29 - .../stdlib/primitives/field/pow.test.cpp | 151 - .../aztec/stdlib/primitives/group/group.hpp | 9 +- .../stdlib/primitives/group/group.test.cpp | 6 +- .../stdlib/primitives/memory/rom_table.cpp | 127 + .../stdlib/primitives/memory/rom_table.hpp | 47 + .../primitives/memory/rom_table.test.cpp | 71 + .../primitives/memory/twin_rom_table.cpp | 144 + .../primitives/memory/twin_rom_table.hpp | 49 + .../packed_byte_array/packed_byte_array.cpp | 6 +- .../packed_byte_array.test.cpp | 19 +- .../stdlib/primitives/plookup/plookup.cpp | 89 +- .../stdlib/primitives/plookup/plookup.hpp | 37 +- .../primitives/plookup/plookup.test.cpp | 543 ++- .../stdlib/primitives/safe_uint/safe_uint.hpp | 7 +- .../primitives/safe_uint/safe_uint.test.cpp | 145 +- .../stdlib/primitives/uint/arithmetic.cpp | 17 +- .../stdlib/primitives/uint/comparison.cpp | 10 +- .../aztec/stdlib/primitives/uint/logic.cpp | 24 - .../primitives/uint/plookup/arithmetic.cpp | 260 ++ .../primitives/uint/plookup/comparison.cpp | 81 + .../stdlib/primitives/uint/plookup/logic.cpp | 334 ++ .../stdlib/primitives/uint/plookup/uint.cpp | 249 ++ .../stdlib/primitives/uint/plookup/uint.hpp | 181 + cpp/src/aztec/stdlib/primitives/uint/uint.cpp | 58 +- cpp/src/aztec/stdlib/primitives/uint/uint.hpp | 39 +- .../stdlib/primitives/uint/uint.test.cpp | 3357 +++++++++-------- .../primitives/uint/uint_plookups.test.cpp | 540 --- cpp/src/aztec/stdlib/recursion/CMakeLists.txt | 2 +- .../recursion/transcript/transcript.hpp | 29 +- .../recursion/transcript/transcript.test.cpp | 5 +- .../verification_key/verification_key.hpp | 126 +- .../recursion/verifier/program_settings.hpp | 125 +- .../stdlib/recursion/verifier/verifier.hpp | 57 +- .../recursion/verifier/verifier.test.cpp | 315 +- .../verifier/verifier_turbo.test.cpp | 508 +++ cpp/src/aztec/stdlib/types/plookup.hpp | 54 - cpp/src/aztec/stdlib/types/standard.hpp | 66 - cpp/src/aztec/stdlib/types/turbo.hpp | 63 - cpp/src/aztec/stdlib/types/types.hpp | 96 + cpp/srs_db/download_ignition.sh | 3 + cpp/srs_db/download_ignition_lagrange.sh | 38 + cpp/ultra_dev_tests.sh | 14 + 344 files changed, 25513 insertions(+), 11145 deletions(-) create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 cpp/src/aztec/crypto/blake3s/CMakeLists.txt create mode 100644 cpp/src/aztec/crypto/blake3s/blake3-impl.hpp create mode 100644 cpp/src/aztec/crypto/blake3s/blake3s.cpp create mode 100644 cpp/src/aztec/crypto/blake3s/blake3s.hpp create mode 100644 cpp/src/aztec/crypto/blake3s/blake3s.test.cpp create mode 100644 cpp/src/aztec/crypto/blake3s/c_bind.cpp create mode 100644 cpp/src/aztec/crypto/blake3s_full/CMakeLists.txt create mode 100644 cpp/src/aztec/crypto/blake3s_full/blake3-impl.hpp create mode 100644 cpp/src/aztec/crypto/blake3s_full/blake3s.cpp create mode 100644 cpp/src/aztec/crypto/blake3s_full/blake3s.hpp create mode 100644 cpp/src/aztec/crypto/blake3s_full/blake3s.test.cpp create mode 100644 cpp/src/aztec/crypto/pedersen/convert_buffer_to_field.hpp create mode 100644 cpp/src/aztec/crypto/pedersen/pedersen_lookup.cpp create mode 100644 cpp/src/aztec/crypto/pedersen/pedersen_lookup.hpp create mode 100644 cpp/src/aztec/crypto/pedersen/pedersen_lookup.test.cpp delete mode 100644 cpp/src/aztec/crypto/pedersen/sidon_pedersen.cpp delete mode 100644 cpp/src/aztec/crypto/pedersen/sidon_pedersen.hpp delete mode 100644 cpp/src/aztec/crypto/pedersen/sidon_pedersen.test.cpp create mode 100644 cpp/src/aztec/ecc/curves/secp256k1/secp256k1_endo_notes.hpp create mode 100644 cpp/src/aztec/ecc/curves/types.hpp create mode 100644 cpp/src/aztec/lagrange_base_gen/CMakeLists.txt create mode 100755 cpp/src/aztec/lagrange_base_gen/lagrange_base_gen.sh create mode 100644 cpp/src/aztec/lagrange_base_gen/lagrange_base_processor.cpp delete mode 100644 cpp/src/aztec/plonk/composer/plookup/compute_verification_key.cpp delete mode 100644 cpp/src/aztec/plonk/composer/plookup/compute_verification_key.hpp delete mode 100644 cpp/src/aztec/plonk/composer/plookup_composer.cpp delete mode 100644 cpp/src/aztec/plonk/composer/plookup_composer.hpp create mode 100644 cpp/src/aztec/plonk/composer/plookup_tables/blake2s.hpp create mode 100644 cpp/src/aztec/plonk/composer/plookup_tables/non_native_group_generator.cpp create mode 100644 cpp/src/aztec/plonk/composer/plookup_tables/non_native_group_generator.hpp delete mode 100644 cpp/src/aztec/plonk/composer/plookup_tables/sidon_pedersen.hpp delete mode 100644 cpp/src/aztec/plonk/composer/standard/compute_verification_key.cpp delete mode 100644 cpp/src/aztec/plonk/composer/standard/compute_verification_key.hpp delete mode 100644 cpp/src/aztec/plonk/composer/turbo/compute_verification_key.cpp delete mode 100644 cpp/src/aztec/plonk/composer/turbo/compute_verification_key.hpp create mode 100644 cpp/src/aztec/plonk/composer/ultra_composer.cpp create mode 100644 cpp/src/aztec/plonk/composer/ultra_composer.hpp rename cpp/src/aztec/plonk/composer/{plookup_composer.test.cpp => ultra_composer.test.cpp} (64%) rename cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/{turbo_fixed_base_widget.hpp => fixed_base_widget.hpp} (73%) create mode 100644 cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/plookup_arithmetic_widget.hpp create mode 100644 cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/plookup_auxiliary_widget.hpp delete mode 100644 cpp/src/aztec/rollup/ci_failsafe/CMakeLists.txt delete mode 100644 cpp/src/aztec/rollup/ci_failsafe/failsafe.test.cpp create mode 100644 cpp/src/aztec/rollup/proofs/mock/CMakeLists.txt create mode 100644 cpp/src/aztec/rollup/proofs/mock/mock_circuit.test.cpp rename cpp/src/aztec/{plonk => srs}/reference_string/file_reference_string.cpp (62%) rename cpp/src/aztec/{plonk => srs}/reference_string/file_reference_string.hpp (65%) rename cpp/src/aztec/{plonk => srs}/reference_string/mem_reference_string.cpp (70%) rename cpp/src/aztec/{plonk => srs}/reference_string/mem_reference_string.hpp (72%) rename cpp/src/aztec/{plonk => srs}/reference_string/mem_reference_string.test.cpp (99%) rename cpp/src/aztec/{plonk => srs}/reference_string/pippenger_reference_string.hpp (79%) rename cpp/src/aztec/{plonk => srs}/reference_string/reference_string.hpp (75%) create mode 100644 cpp/src/aztec/stdlib/hash/blake2s/blake2s_plookup.cpp create mode 100644 cpp/src/aztec/stdlib/hash/blake2s/blake2s_plookup.hpp create mode 100644 cpp/src/aztec/stdlib/hash/blake2s/blake_util.hpp create mode 100644 cpp/src/aztec/stdlib/hash/blake3s/CMakeLists.txt create mode 100644 cpp/src/aztec/stdlib/hash/blake3s/blake3s.cpp create mode 100644 cpp/src/aztec/stdlib/hash/blake3s/blake3s.hpp create mode 100644 cpp/src/aztec/stdlib/hash/blake3s/blake3s.test.cpp create mode 100644 cpp/src/aztec/stdlib/hash/blake3s/blake3s_plookup.cpp create mode 100644 cpp/src/aztec/stdlib/hash/blake3s/blake3s_plookup.hpp create mode 100644 cpp/src/aztec/stdlib/primitives/biggroup/biggroup_batch_mul.hpp create mode 100644 cpp/src/aztec/stdlib/primitives/biggroup/biggroup_bn254.hpp create mode 100644 cpp/src/aztec/stdlib/primitives/biggroup/biggroup_nafs.hpp create mode 100644 cpp/src/aztec/stdlib/primitives/biggroup/biggroup_secp256k1.hpp create mode 100644 cpp/src/aztec/stdlib/primitives/biggroup/biggroup_tables.hpp create mode 100644 cpp/src/aztec/stdlib/primitives/curves/secp256k1.hpp delete mode 100644 cpp/src/aztec/stdlib/primitives/field/pow.hpp delete mode 100644 cpp/src/aztec/stdlib/primitives/field/pow.test.cpp create mode 100644 cpp/src/aztec/stdlib/primitives/memory/rom_table.cpp create mode 100644 cpp/src/aztec/stdlib/primitives/memory/rom_table.hpp create mode 100644 cpp/src/aztec/stdlib/primitives/memory/rom_table.test.cpp create mode 100644 cpp/src/aztec/stdlib/primitives/memory/twin_rom_table.cpp create mode 100644 cpp/src/aztec/stdlib/primitives/memory/twin_rom_table.hpp create mode 100644 cpp/src/aztec/stdlib/primitives/uint/plookup/arithmetic.cpp create mode 100644 cpp/src/aztec/stdlib/primitives/uint/plookup/comparison.cpp create mode 100644 cpp/src/aztec/stdlib/primitives/uint/plookup/logic.cpp create mode 100644 cpp/src/aztec/stdlib/primitives/uint/plookup/uint.cpp create mode 100644 cpp/src/aztec/stdlib/primitives/uint/plookup/uint.hpp delete mode 100644 cpp/src/aztec/stdlib/primitives/uint/uint_plookups.test.cpp create mode 100644 cpp/src/aztec/stdlib/recursion/verifier/verifier_turbo.test.cpp delete mode 100644 cpp/src/aztec/stdlib/types/plookup.hpp delete mode 100644 cpp/src/aztec/stdlib/types/standard.hpp delete mode 100644 cpp/src/aztec/stdlib/types/turbo.hpp create mode 100644 cpp/src/aztec/stdlib/types/types.hpp create mode 100755 cpp/srs_db/download_ignition_lagrange.sh create mode 100755 cpp/ultra_dev_tests.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 2eebb5aea0..2d9c182fa1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -145,7 +145,7 @@ jobs: - *setup_env - run: name: "Test" - command: cond_spot_run_tests barretenberg-x86_64-linux-clang-assert bb-tests + command: cond_spot_run_tests barretenberg-x86_64-linux-clang-assert 1 bb-tests - *save_logs stdlib-primitives-tests: @@ -157,10 +157,10 @@ jobs: - *setup_env - run: name: "Test" - command: cond_spot_run_tests barretenberg-x86_64-linux-clang-assert stdlib_primitives_tests + command: cond_spot_run_tests barretenberg-x86_64-linux-clang-assert 1 stdlib_primitives_tests - *save_logs - stdlib-recursion-tests: + stdlib-recursion-turbo-tests: docker: - image: aztecprotocol/alpine-build-image resource_class: small @@ -169,7 +169,19 @@ jobs: - *setup_env - run: name: "Test" - command: cond_spot_run_tests barretenberg-x86_64-linux-clang-assert stdlib_recursion_tests + command: cond_spot_run_tests barretenberg-x86_64-linux-clang-assert 1 stdlib_recursion_tests --gtest_filter=*turbo* + - *save_logs + + stdlib-recursion-ultra-tests: + docker: + - image: aztecprotocol/alpine-build-image + resource_class: small + steps: + - *checkout + - *setup_env + - run: + name: "Test" + command: cond_spot_run_tests barretenberg-x86_64-linux-clang-assert 3 stdlib_recursion_tests --gtest_filter=-*turbo* - *save_logs tx-rollup-tests: @@ -181,7 +193,7 @@ jobs: - *setup_env - run: name: "Test" - command: cond_spot_run_tests barretenberg-x86_64-linux-clang-assert rollup_proofs_tx_rollup_tests --gtest_filter=-rollup_full_tests.* + command: cond_spot_run_tests barretenberg-x86_64-linux-clang-assert 1 rollup_proofs_tx_rollup_tests --gtest_filter=-rollup_full_tests.* - *save_logs tx-rollup-full-tests: @@ -193,7 +205,7 @@ jobs: - *setup_env - run: name: "Test" - command: cond_spot_run_tests barretenberg-x86_64-linux-clang-assert rollup_proofs_tx_rollup_tests --gtest_filter=rollup_full_tests.* + command: cond_spot_run_tests barretenberg-x86_64-linux-clang-assert 1 rollup_proofs_tx_rollup_tests --gtest_filter=rollup_full_tests.* - *save_logs root-rollup-tests: @@ -205,7 +217,7 @@ jobs: - *setup_env - run: name: "Test" - command: cond_spot_run_tests barretenberg-x86_64-linux-clang-assert rollup_proofs_root_rollup_tests --gtest_filter=-root_rollup_full_tests.* + command: cond_spot_run_tests barretenberg-x86_64-linux-clang-assert 1 rollup_proofs_root_rollup_tests --gtest_filter=-root_rollup_full_tests.* - *save_logs root-rollup-full-tests: @@ -217,7 +229,7 @@ jobs: - *setup_env - run: name: "Test" - command: cond_spot_run_tests barretenberg-x86_64-linux-clang-assert rollup_proofs_root_rollup_tests --gtest_filter=root_rollup_full_tests.* + command: cond_spot_run_tests barretenberg-x86_64-linux-clang-assert 1 rollup_proofs_root_rollup_tests --gtest_filter=root_rollup_full_tests.* - *save_logs root-verifier-tests: @@ -229,7 +241,7 @@ jobs: - *setup_env - run: name: "Test" - command: cond_spot_run_tests barretenberg-x86_64-linux-clang-assert rollup_proofs_root_verifier_tests + command: cond_spot_run_tests barretenberg-x86_64-linux-clang-assert 3 rollup_proofs_root_verifier_tests - *save_logs benchmark-aggregator: @@ -260,7 +272,8 @@ workflows: - wasm-linux-clang - barretenberg-tests: *bb_test - stdlib-primitives-tests: *bb_test - - stdlib-recursion-tests: *bb_test + - stdlib-recursion-turbo-tests: *bb_test + - stdlib-recursion-ultra-tests: *bb_test - tx-rollup-tests: *bb_test - tx-rollup-full-tests: *bb_test - root-rollup-tests: *bb_test @@ -270,7 +283,8 @@ workflows: requires: - barretenberg-tests - stdlib-primitives-tests - - stdlib-recursion-tests + - stdlib-recursion-turbo-tests + - stdlib-recursion-ultra-tests - tx-rollup-tests - tx-rollup-full-tests - root-verifier-tests diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000000..d68111cc10 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,17 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**", + "${workspaceFolder}/cpp/src/aztec" + ], + "defines": [], + "compilerPath": "/usr/bin/clang", + "cStandard": "c11", + "cppStandard": "c++20", + "intelliSenseMode": "clang-x64" + } + ], + "version": 4 +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..0f8b74e7db --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "arcanis.vscode-zipfs", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "ms-vscode.cpptools", + "eamodio.gitlens" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..84927c8002 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.associations": { + "*.tf": "terraform", + "*.tfvars": "terraform", + "Makefile.*": "makefile", + "iosfwd": "cpp" + } +} diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index c346f05b71..d3b580308e 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -15,6 +15,7 @@ option(DISABLE_ADX "Disable ADX assembly variant" OFF) option(MULTITHREADING "Enable multi-threading" ON) option(TESTING "Build tests" ON) option(BENCHMARKS "Build benchmarks" ON) +option(DISABLE_TBB "Intel Thread Building Blocks" ON) if(ARM) message(STATUS "Compiling for ARM.") @@ -22,6 +23,7 @@ if(ARM) set(DISABLE_ADX ON) set(RUN_HAVE_STD_REGEX 0) set(RUN_HAVE_POSIX_REGEX 0) + set(DISABLE_TBB 0) endif() if(FUZZING) @@ -40,7 +42,7 @@ if(FUZZING) if(UNDEFINED_BEHAVIOUR_SANITIZER) set(SANITIZER_OPTIONS ${SANITIZER_OPTIONS} -fsanitize=undefined -fno-sanitize=alignment) endif() - + add_compile_options(-fsanitize=fuzzer-no-link ${SANITIZER_OPTIONS}) set(WASM OFF) @@ -53,6 +55,7 @@ if(WASM) set(DISABLE_ASM ON) set(MULTITHREADING OFF) set(BENCHMARKS OFF) + set(DISABLE_TBB 1) endif() set(CMAKE_C_STANDARD 11) @@ -62,10 +65,11 @@ set(CMAKE_CXX_STANDARD_REQUIRED TRUE) set(CMAKE_CXX_EXTENSIONS ON) include(cmake/build.cmake) +include(GNUInstallDirs) include(cmake/arch.cmake) include(cmake/threading.cmake) include(cmake/gtest.cmake) include(cmake/benchmark.cmake) include(cmake/module.cmake) -add_subdirectory(src/aztec) +add_subdirectory(src/aztec) \ No newline at end of file diff --git a/cpp/bootstrap.sh b/cpp/bootstrap.sh index 3f61cf59b3..cbdc8ae52e 100755 --- a/cpp/bootstrap.sh +++ b/cpp/bootstrap.sh @@ -6,13 +6,8 @@ rm -rf ./build rm -rf ./build-wasm # Install formatting git hook. -HOOKS_DIR=$(git rev-parse --git-path hooks) -# The pre-commit script will live in a barretenberg-specific hooks directory -# That may be just in the top level of this repository, -# or may be in a .git/modules/barretenberg subdirectory when this is actually a submodule -# Either way, running `git rev-parse --show-toplevel` from the hooks directory gives the path to barretenberg -echo "cd \$(git rev-parse --show-toplevel)/cpp && ./format.sh staged" > $HOOKS_DIR/pre-commit -chmod +x $HOOKS_DIR/pre-commit +echo "cd ./cpp && ./format.sh staged" > ../.git/hooks/pre-commit +chmod +x ../.git/hooks/pre-commit # Determine system. if [[ "$OSTYPE" == "darwin"* ]]; then @@ -27,6 +22,7 @@ fi # Download ignition transcripts. cd ./srs_db ./download_ignition.sh 3 +./download_ignition_lagrange.sh 12 cd .. # Pick native toolchain file. diff --git a/cpp/cmake/module.cmake b/cpp/cmake/module.cmake index 063e3f690a..3203763d09 100644 --- a/cpp/cmake/module.cmake +++ b/cpp/cmake/module.cmake @@ -34,6 +34,7 @@ function(barretenberg_module MODULE_NAME) ${MODULE_NAME} PUBLIC ${ARGN} + ${TBB_IMPORTED_TARGETS} ) set(MODULE_LINK_NAME ${MODULE_NAME}) @@ -51,6 +52,7 @@ function(barretenberg_module MODULE_NAME) ${MODULE_NAME}_test_objects PRIVATE gtest + ${TBB_IMPORTED_TARGETS} ) add_executable( @@ -89,6 +91,7 @@ function(barretenberg_module MODULE_NAME) ${ARGN} gtest gtest_main + ${TBB_IMPORTED_TARGETS} ) if(NOT WASM AND NOT CI) @@ -139,6 +142,7 @@ function(barretenberg_module MODULE_NAME) ${MODULE_NAME}_bench_objects PRIVATE benchmark + ${TBB_IMPORTED_TARGETS} ) add_executable( @@ -152,6 +156,7 @@ function(barretenberg_module MODULE_NAME) ${MODULE_LINK_NAME} ${ARGN} benchmark + ${TBB_IMPORTED_TARGETS} ) add_custom_target( diff --git a/cpp/cmake/threading.cmake b/cpp/cmake/threading.cmake index 3367e231a5..f91b86b385 100644 --- a/cpp/cmake/threading.cmake +++ b/cpp/cmake/threading.cmake @@ -23,3 +23,16 @@ else() message(STATUS "Multithreading is disabled.") add_definitions(-DNO_MULTITHREADING -DBOOST_SP_NO_ATOMIC_ACCESS) endif() + +if(DISABLE_TBB) + message(STATUS "Intel Thread Building Blocks is disabled.") + add_definitions(-DNO_TBB) +else() + find_package(TBB REQUIRED tbb) + if(${TBB_FOUND}) + message(STATUS "Intel Thread Building Blocks is enabled.") + else() + message(STATUS "Could not locate TBB.") + add_definitions(-DNO_TBB) + endif() +endif() diff --git a/cpp/dockerfiles/Dockerfile.x86_64-linux-clang-assert b/cpp/dockerfiles/Dockerfile.x86_64-linux-clang-assert index fb9ed4d28b..27e53543db 100644 --- a/cpp/dockerfiles/Dockerfile.x86_64-linux-clang-assert +++ b/cpp/dockerfiles/Dockerfile.x86_64-linux-clang-assert @@ -16,6 +16,7 @@ RUN git clone -b release/10.x --depth 1 https://github.com/llvm/llvm-project.git && make install \ && cd ../.. && rm -rf llvm-project WORKDIR /usr/src/barretenberg/cpp + COPY . . # Build everything to ensure everything builds. All tests will be run from the result of this build. RUN mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=RelWithAssert -DOpenMP_omp_LIBRARY=/usr/local/lib/libomp.a -DCI=ON .. && make -j$(nproc) diff --git a/cpp/scripts/bb-tests b/cpp/scripts/bb-tests index 806d8b637a..7b80a638b5 100644 --- a/cpp/scripts/bb-tests +++ b/cpp/scripts/bb-tests @@ -1,6 +1,6 @@ -ci_failsafe_tests crypto_aes128_tests crypto_blake2s_tests +crypto_blake3s_tests crypto_ecdsa_tests crypto_pedersen_tests crypto_schnorr_tests @@ -18,6 +18,7 @@ rollup_proofs_standard_example_tests srs_tests stdlib_aes128_tests stdlib_blake2s_tests +stdlib_blake3s_tests stdlib_ecdsa_tests stdlib_merkle_tree_tests stdlib_pedersen_tests diff --git a/cpp/scripts/run_tests b/cpp/scripts/run_tests index e6c8c8d28d..7093b41490 100755 --- a/cpp/scripts/run_tests +++ b/cpp/scripts/run_tests @@ -1,7 +1,9 @@ #!/bin/bash set -e -TESTS=$1 +NUM_TRANSCRIPTS=$1 +TESTS=$2 +shift shift $(aws ecr get-login --region us-east-2 --no-include-email) 2> /dev/null @@ -17,6 +19,7 @@ fi docker run --rm -t $IMAGE_URI /bin/sh -c "\ set -e; \ cd /usr/src/barretenberg/cpp/srs_db; \ - ./download_ignition.sh 1; \ + ./download_ignition.sh $NUM_TRANSCRIPTS; \ + ./download_ignition_lagrange.sh 12; \ cd /usr/src/barretenberg/cpp/build; \ - for BIN in $TESTS; do ./bin/\$BIN $@; done" \ No newline at end of file + for BIN in $TESTS; do ./bin/\$BIN $@; done" diff --git a/cpp/src/aztec/CMakeLists.txt b/cpp/src/aztec/CMakeLists.txt index c588d7bfb7..bfe5958762 100644 --- a/cpp/src/aztec/CMakeLists.txt +++ b/cpp/src/aztec/CMakeLists.txt @@ -34,6 +34,7 @@ add_subdirectory(polynomials) add_subdirectory(plonk) add_subdirectory(stdlib) add_subdirectory(rollup) +add_subdirectory(lagrange_base_gen) if(BENCHMARKS) add_subdirectory(benchmark) @@ -52,6 +53,7 @@ if(WASM) $ $ $ + $ $ $ $ @@ -62,6 +64,7 @@ if(WASM) $ $ $ + $ $ $ $ diff --git a/cpp/src/aztec/benchmark/pippenger_bench/CMakeLists.txt b/cpp/src/aztec/benchmark/pippenger_bench/CMakeLists.txt index 2c27d6e6c7..68c455e450 100644 --- a/cpp/src/aztec/benchmark/pippenger_bench/CMakeLists.txt +++ b/cpp/src/aztec/benchmark/pippenger_bench/CMakeLists.txt @@ -3,7 +3,7 @@ add_executable(pippenger_bench main.cpp) target_link_libraries( pippenger_bench polynomials - plonk + srs env ) diff --git a/cpp/src/aztec/benchmark/pippenger_bench/main.cpp b/cpp/src/aztec/benchmark/pippenger_bench/main.cpp index ee07a476fc..2c59ad4d41 100644 --- a/cpp/src/aztec/benchmark/pippenger_bench/main.cpp +++ b/cpp/src/aztec/benchmark/pippenger_bench/main.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include //#include diff --git a/cpp/src/aztec/crypto/CMakeLists.txt b/cpp/src/aztec/crypto/CMakeLists.txt index f0b3454e7e..8f2b22453b 100644 --- a/cpp/src/aztec/crypto/CMakeLists.txt +++ b/cpp/src/aztec/crypto/CMakeLists.txt @@ -1,5 +1,7 @@ add_subdirectory(hmac) add_subdirectory(blake2s) +add_subdirectory(blake3s) +add_subdirectory(blake3s_full) add_subdirectory(keccak) add_subdirectory(pedersen) add_subdirectory(schnorr) diff --git a/cpp/src/aztec/crypto/blake3s/CMakeLists.txt b/cpp/src/aztec/crypto/blake3s/CMakeLists.txt new file mode 100644 index 0000000000..31df0b6297 --- /dev/null +++ b/cpp/src/aztec/crypto/blake3s/CMakeLists.txt @@ -0,0 +1 @@ +barretenberg_module(crypto_blake3s crypto_blake2s) \ No newline at end of file diff --git a/cpp/src/aztec/crypto/blake3s/blake3-impl.hpp b/cpp/src/aztec/crypto/blake3s/blake3-impl.hpp new file mode 100644 index 0000000000..3b875b2b8b --- /dev/null +++ b/cpp/src/aztec/crypto/blake3s/blake3-impl.hpp @@ -0,0 +1,80 @@ +/* + BLAKE3 reference source code package - C implementations + + Intellectual property: + + The Rust code is copyright Jack O'Connor, 2019-2020. + The C code is copyright Samuel Neves and Jack O'Connor, 2019-2020. + The assembly code is copyright Samuel Neves, 2019-2020. + + This work is released into the public domain with CC0 1.0. Alternatively, it is licensed under the Apache + License 2.0. + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE3 hash function can be found at + https://github.com/BLAKE3-team/BLAKE3. +*/ + +#ifndef BLAKE3_IMPL_H +#define BLAKE3_IMPL_H + +#include +#include +#include +#include +#include + +#include "blake3s.hpp" + +namespace blake3 { + +// Right rotates 32 bit inputs +uint32_t rotr32(uint32_t w, uint32_t c) +{ + return (w >> c) | (w << (32 - c)); +} + +uint32_t load32(const void* src) +{ + const uint8_t* p = (const uint8_t*)src; + return ((uint32_t)(p[0]) << 0) | ((uint32_t)(p[1]) << 8) | ((uint32_t)(p[2]) << 16) | ((uint32_t)(p[3]) << 24); +} + +void load_key_words(const uint8_t key[BLAKE3_KEY_LEN], uint32_t key_words[8]) +{ + key_words[0] = load32(&key[0 * 4]); + key_words[1] = load32(&key[1 * 4]); + key_words[2] = load32(&key[2 * 4]); + key_words[3] = load32(&key[3 * 4]); + key_words[4] = load32(&key[4 * 4]); + key_words[5] = load32(&key[5 * 4]); + key_words[6] = load32(&key[6 * 4]); + key_words[7] = load32(&key[7 * 4]); +} + +void store32(void* dst, uint32_t w) +{ + uint8_t* p = (uint8_t*)dst; + p[0] = (uint8_t)(w >> 0); + p[1] = (uint8_t)(w >> 8); + p[2] = (uint8_t)(w >> 16); + p[3] = (uint8_t)(w >> 24); +} + +void store_cv_words(uint8_t bytes_out[32], uint32_t cv_words[8]) +{ + store32(&bytes_out[0 * 4], cv_words[0]); + store32(&bytes_out[1 * 4], cv_words[1]); + store32(&bytes_out[2 * 4], cv_words[2]); + store32(&bytes_out[3 * 4], cv_words[3]); + store32(&bytes_out[4 * 4], cv_words[4]); + store32(&bytes_out[5 * 4], cv_words[5]); + store32(&bytes_out[6 * 4], cv_words[6]); + store32(&bytes_out[7 * 4], cv_words[7]); +} + +} // namespace blake3 + +#endif /* BLAKE3_IMPL_H */ diff --git a/cpp/src/aztec/crypto/blake3s/blake3s.cpp b/cpp/src/aztec/crypto/blake3s/blake3s.cpp new file mode 100644 index 0000000000..8f04fe9424 --- /dev/null +++ b/cpp/src/aztec/crypto/blake3s/blake3s.cpp @@ -0,0 +1,281 @@ +/* + BLAKE3 reference source code package - C implementations + + Intellectual property: + + The Rust code is copyright Jack O'Connor, 2019-2020. + The C code is copyright Samuel Neves and Jack O'Connor, 2019-2020. + The assembly code is copyright Samuel Neves, 2019-2020. + + This work is released into the public domain with CC0 1.0. Alternatively, it is licensed under the Apache + License 2.0. + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE3 hash function can be found at + https://github.com/BLAKE3-team/BLAKE3. + + + NOTE: We have modified the original code from the BLAKE3 reference C implementation. + The following code works ONLY for inputs of size less than 1024 bytes. This kind of constraint + on the input size greatly simplifies the code and helps us get rid of the recursive merkle-tree + like operations on chunks (data of size 1024 bytes). This is because we would always be using BLAKE3 + hashing for inputs of size 32 bytes (or lesser) in barretenberg. The full C++ version of BLAKE3 + from the original authors is in the module `../crypto/blake3s_full`. + + Also, the length of the output in this specific implementation is fixed at 32 bytes which is the only + version relevant to Barretenberg. +*/ + +#include +#include +#include +#include +#include + +#include "blake3-impl.hpp" + +namespace blake3 { + +/* + * Core Blake3s functions. These are similar to that of Blake2s except for a few + * constant parameters and fewer rounds. + * + */ +void g(uint32_t* state, size_t a, size_t b, size_t c, size_t d, uint32_t x, uint32_t y) +{ + state[a] = state[a] + state[b] + x; + state[d] = rotr32(state[d] ^ state[a], 16); + state[c] = state[c] + state[d]; + state[b] = rotr32(state[b] ^ state[c], 12); + state[a] = state[a] + state[b] + y; + state[d] = rotr32(state[d] ^ state[a], 8); + state[c] = state[c] + state[d]; + state[b] = rotr32(state[b] ^ state[c], 7); +} + +void round_fn(uint32_t state[16], const uint32_t* msg, size_t round) +{ + // Select the message schedule based on the round. + const uint8_t* schedule = MSG_SCHEDULE[round]; + + // Mix the columns. + g(state, 0, 4, 8, 12, msg[schedule[0]], msg[schedule[1]]); + g(state, 1, 5, 9, 13, msg[schedule[2]], msg[schedule[3]]); + g(state, 2, 6, 10, 14, msg[schedule[4]], msg[schedule[5]]); + g(state, 3, 7, 11, 15, msg[schedule[6]], msg[schedule[7]]); + + // Mix the rows. + g(state, 0, 5, 10, 15, msg[schedule[8]], msg[schedule[9]]); + g(state, 1, 6, 11, 12, msg[schedule[10]], msg[schedule[11]]); + g(state, 2, 7, 8, 13, msg[schedule[12]], msg[schedule[13]]); + g(state, 3, 4, 9, 14, msg[schedule[14]], msg[schedule[15]]); +} + +void compress_pre( + uint32_t state[16], const uint32_t cv[8], const uint8_t block[BLAKE3_BLOCK_LEN], uint8_t block_len, uint8_t flags) +{ + uint32_t block_words[16]; + block_words[0] = load32(block + 4 * 0); + block_words[1] = load32(block + 4 * 1); + block_words[2] = load32(block + 4 * 2); + block_words[3] = load32(block + 4 * 3); + block_words[4] = load32(block + 4 * 4); + block_words[5] = load32(block + 4 * 5); + block_words[6] = load32(block + 4 * 6); + block_words[7] = load32(block + 4 * 7); + block_words[8] = load32(block + 4 * 8); + block_words[9] = load32(block + 4 * 9); + block_words[10] = load32(block + 4 * 10); + block_words[11] = load32(block + 4 * 11); + block_words[12] = load32(block + 4 * 12); + block_words[13] = load32(block + 4 * 13); + block_words[14] = load32(block + 4 * 14); + block_words[15] = load32(block + 4 * 15); + + state[0] = cv[0]; + state[1] = cv[1]; + state[2] = cv[2]; + state[3] = cv[3]; + state[4] = cv[4]; + state[5] = cv[5]; + state[6] = cv[6]; + state[7] = cv[7]; + state[8] = IV[0]; + state[9] = IV[1]; + state[10] = IV[2]; + state[11] = IV[3]; + state[12] = 0; + state[13] = 0; + state[14] = (uint32_t)block_len; + state[15] = (uint32_t)flags; + + round_fn(state, &block_words[0], 0); + round_fn(state, &block_words[0], 1); + round_fn(state, &block_words[0], 2); + round_fn(state, &block_words[0], 3); + round_fn(state, &block_words[0], 4); + round_fn(state, &block_words[0], 5); + round_fn(state, &block_words[0], 6); +} + +void blake3_compress_in_place(uint32_t cv[8], const uint8_t block[BLAKE3_BLOCK_LEN], uint8_t block_len, uint8_t flags) +{ + uint32_t state[16]; + compress_pre(state, cv, block, block_len, flags); + cv[0] = state[0] ^ state[8]; + cv[1] = state[1] ^ state[9]; + cv[2] = state[2] ^ state[10]; + cv[3] = state[3] ^ state[11]; + cv[4] = state[4] ^ state[12]; + cv[5] = state[5] ^ state[13]; + cv[6] = state[6] ^ state[14]; + cv[7] = state[7] ^ state[15]; +} + +void blake3_compress_xof( + const uint32_t cv[8], const uint8_t block[BLAKE3_BLOCK_LEN], uint8_t block_len, uint8_t flags, uint8_t out[64]) +{ + uint32_t state[16]; + compress_pre(state, cv, block, block_len, flags); + + store32(&out[0 * 4], state[0] ^ state[8]); + store32(&out[1 * 4], state[1] ^ state[9]); + store32(&out[2 * 4], state[2] ^ state[10]); + store32(&out[3 * 4], state[3] ^ state[11]); + store32(&out[4 * 4], state[4] ^ state[12]); + store32(&out[5 * 4], state[5] ^ state[13]); + store32(&out[6 * 4], state[6] ^ state[14]); + store32(&out[7 * 4], state[7] ^ state[15]); + store32(&out[8 * 4], state[8] ^ cv[0]); + store32(&out[9 * 4], state[9] ^ cv[1]); + store32(&out[10 * 4], state[10] ^ cv[2]); + store32(&out[11 * 4], state[11] ^ cv[3]); + store32(&out[12 * 4], state[12] ^ cv[4]); + store32(&out[13 * 4], state[13] ^ cv[5]); + store32(&out[14 * 4], state[14] ^ cv[6]); + store32(&out[15 * 4], state[15] ^ cv[7]); +} + +const char* blake3_version(void) +{ + return BLAKE3_VERSION_STRING; +} + +uint8_t maybe_start_flag(const blake3_hasher* self) +{ + if (self->blocks_compressed == 0) { + return CHUNK_START; + } else { + return 0; + } +} + +typedef struct output_t__ { + uint32_t input_cv[8]; + uint8_t block[BLAKE3_BLOCK_LEN]; + uint8_t block_len; + uint8_t flags; +} output_t; + +output_t make_output(const uint32_t input_cv[8], + const uint8_t block[BLAKE3_BLOCK_LEN], + uint8_t block_len, + uint8_t flags) +{ + output_t ret; + for (size_t i = 0; i < (BLAKE3_OUT_LEN >> 2); ++i) { + ret.input_cv[i] = input_cv[i]; + } + for (size_t i = 0; i < BLAKE3_BLOCK_LEN; i++) { + ret.block[i] = block[i]; + } + ret.block_len = block_len; + ret.flags = flags; + return ret; +} + +void blake3_hasher_init(blake3_hasher* self) +{ + for (size_t i = 0; i < (BLAKE3_KEY_LEN >> 2); ++i) { + self->key[i] = IV[i]; + self->cv[i] = IV[i]; + } + for (size_t i = 0; i < BLAKE3_BLOCK_LEN; i++) { + self->buf[i] = 0; + } + self->buf_len = 0; + self->blocks_compressed = 0; + self->flags = 0; +} + +void blake3_hasher_update(blake3_hasher* self, const uint8_t* input, size_t input_len) +{ + if (input_len == 0) { + return; + } + + while (input_len > BLAKE3_BLOCK_LEN) { + blake3_compress_in_place(self->cv, input, BLAKE3_BLOCK_LEN, self->flags | maybe_start_flag(self)); + + // static_assert(std::is_sameblocks_compressed), uint8_t>::value, "blocks compressed type err"); + // // uint8_t foo = self->blocks_compressed; + // // uint8_t bar(1U); + // std::cout << "owauefheaiufhawuifh" << std::endl; + // std::cout << "owauefheaiufhawuifh" << std::endl; + // std::cout << "owauefheaiufhawuifh" << std::endl; + // std::cout << "owauefheaiufhawuifh" << std::endl; + + // static_assert(std::is_same::value, "blocks compressed type err A "); + // static_assert(std::is_same::value, "blocks compressed type err B"); + // foo = foo + bar; + // std::cout << "owauefheaiufhawuifh" << std::endl; + // std::cout << "owauefheaiufhawuifh" << std::endl; + // std::cout << "owauefheaiufhawuifh" << std::endl; + // std::cout << "owauefheaiufhawuifh" << std::endl; + // std::cout << "owauefheaiufhawuifh" << std::endl; + + self->blocks_compressed = static_cast(self->blocks_compressed + 1U); + input += BLAKE3_BLOCK_LEN; + input_len -= BLAKE3_BLOCK_LEN; + } + + size_t take = BLAKE3_BLOCK_LEN - ((size_t)self->buf_len); + if (take > input_len) { + take = input_len; + } + uint8_t* dest = self->buf + ((size_t)self->buf_len); + for (size_t i = 0; i < take; i++) { + dest[i] = input[i]; + } + + self->buf_len = static_cast(self->buf_len + static_cast(take)); + input_len -= take; +} + +void blake3_hasher_finalize(const blake3_hasher* self, uint8_t* out) +{ + uint8_t block_flags = self->flags | maybe_start_flag(self) | CHUNK_END; + output_t output = make_output(self->cv, self->buf, self->buf_len, block_flags); + + uint8_t wide_buf[64]; + blake3_compress_xof(output.input_cv, output.block, output.block_len, output.flags | ROOT, wide_buf); + for (size_t i = 0; i < BLAKE3_OUT_LEN; i++) { + out[i] = wide_buf[i]; + } + return; +} + +std::vector blake3s(std::vector const& input) +{ + blake3_hasher hasher; + blake3_hasher_init(&hasher); + blake3_hasher_update(&hasher, (const uint8_t*)input.data(), input.size()); + + std::vector output(BLAKE3_OUT_LEN); + blake3_hasher_finalize(&hasher, &output[0]); + return output; +} + +} // namespace blake3 diff --git a/cpp/src/aztec/crypto/blake3s/blake3s.hpp b/cpp/src/aztec/crypto/blake3s/blake3s.hpp new file mode 100644 index 0000000000..eb581479a9 --- /dev/null +++ b/cpp/src/aztec/crypto/blake3s/blake3s.hpp @@ -0,0 +1,96 @@ +/* + BLAKE3 reference source code package - C implementations + + Intellectual property: + + The Rust code is copyright Jack O'Connor, 2019-2020. + The C code is copyright Samuel Neves and Jack O'Connor, 2019-2020. + The assembly code is copyright Samuel Neves, 2019-2020. + + This work is released into the public domain with CC0 1.0. Alternatively, it is licensed under the Apache + License 2.0. + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE3 hash function can be found at + https://github.com/BLAKE3-team/BLAKE3. + + + NOTE: We have modified the original code from the BLAKE3 reference C implementation. + The following code works ONLY for inputs of size less than 1024 bytes. This kind of constraint + on the input size greatly simplifies the code and helps us get rid of the recursive merkle-tree + like operations on chunks (data of size 1024 bytes). This is because we would always be using BLAKE3 + hashing for inputs of size 32 bytes (or lesser) in barretenberg. The full C++ version of BLAKE3 + from the original authors is in the module `../crypto/blake3s_full`. + + Also, the length of the output in this specific implementation is fixed at 32 bytes which is the only + version relevant to Barretenberg. +*/ + +#include +#include +#include + +namespace blake3 { + +#define BLAKE3_VERSION_STRING "0.3.7" + +// internal flags +enum blake3_flags { + CHUNK_START = 1 << 0, + CHUNK_END = 1 << 1, + PARENT = 1 << 2, + ROOT = 1 << 3, + KEYED_HASH = 1 << 4, + DERIVE_KEY_CONTEXT = 1 << 5, + DERIVE_KEY_MATERIAL = 1 << 6, +}; + +// constants +enum blake3s_constant { + BLAKE3_KEY_LEN = 32, + BLAKE3_OUT_LEN = 32, + BLAKE3_BLOCK_LEN = 64, + BLAKE3_CHUNK_LEN = 1024, + BLAKE3_MAX_DEPTH = 54 +}; + +static const uint32_t IV[8] = { 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL, + 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL }; + +static const uint8_t MSG_SCHEDULE[7][16] = { + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, { 2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8 }, + { 3, 4, 10, 12, 13, 2, 7, 14, 6, 5, 9, 0, 11, 15, 8, 1 }, { 10, 7, 12, 9, 14, 3, 13, 15, 4, 0, 11, 2, 5, 8, 1, 6 }, + { 12, 13, 9, 11, 15, 10, 14, 8, 7, 2, 5, 3, 0, 1, 6, 4 }, { 9, 14, 11, 5, 8, 12, 15, 1, 13, 3, 0, 10, 2, 6, 4, 7 }, + { 11, 15, 5, 0, 1, 9, 8, 6, 14, 10, 2, 12, 3, 4, 7, 13 }, +}; + +struct blake3_hasher { + uint32_t key[8]; + uint32_t cv[8]; + uint8_t buf[BLAKE3_BLOCK_LEN]; + uint8_t buf_len = 0; + uint8_t blocks_compressed = 0; + uint8_t flags = 0; +}; + +const char* blake3_version(void); + +void blake3_hasher_init(blake3_hasher* self); +void blake3_hasher_update(blake3_hasher* self, const uint8_t* input, size_t input_len); +void blake3_hasher_finalize(const blake3_hasher* self, uint8_t* out); + +void g(uint32_t* state, size_t a, size_t b, size_t c, size_t d, uint32_t x, uint32_t y); +void round_fn(uint32_t state[16], const uint32_t* msg, size_t round); + +void compress_pre( + uint32_t state[16], const uint32_t cv[8], const uint8_t block[BLAKE3_BLOCK_LEN], uint8_t block_len, uint8_t flags); + +void blake3_compress_in_place(uint32_t cv[8], const uint8_t block[BLAKE3_BLOCK_LEN], uint8_t block_len, uint8_t flags); + +void blake3_compress_xof( + const uint32_t cv[8], const uint8_t block[BLAKE3_BLOCK_LEN], uint8_t block_len, uint8_t flags, uint8_t out[64]); + +std::vector blake3s(std::vector const& input); +} // namespace blake3 diff --git a/cpp/src/aztec/crypto/blake3s/blake3s.test.cpp b/cpp/src/aztec/crypto/blake3s/blake3s.test.cpp new file mode 100644 index 0000000000..73b46dc864 --- /dev/null +++ b/cpp/src/aztec/crypto/blake3s/blake3s.test.cpp @@ -0,0 +1,398 @@ +#include "blake3s.hpp" +#include "../blake2s/blake2s.hpp" + +#include + +#include +#include +#include + +struct test_vector { + std::string input; + std::vector output; +}; + +test_vector test_vectors[] = { + { "", + { + 0xAF, 0x13, 0x49, 0xB9, 0xF5, 0xF9, 0xA1, 0xA6, 0xA0, 0x40, 0x4D, 0xEA, 0x36, 0xDC, 0xC9, 0x49, + 0x9B, 0xCB, 0x25, 0xC9, 0xAD, 0xC1, 0x12, 0xB7, 0xCC, 0x9A, 0x93, 0xCA, 0xE4, 0x1F, 0x32, 0x62, + } }, + { "a", + { + 0x17, 0x76, 0x2F, 0xDD, 0xD9, 0x69, 0xA4, 0x53, 0x92, 0x5D, 0x65, 0x71, 0x7A, 0xC3, 0xEE, 0xA2, + 0x13, 0x20, 0xB6, 0x6B, 0x54, 0x34, 0x2F, 0xDE, 0x15, 0x12, 0x8D, 0x6C, 0xAF, 0x21, 0x21, 0x5F, + } }, + { "ab", + { + 0x2D, 0xC9, 0x99, 0x99, 0xA6, 0xAA, 0xEF, 0x3F, 0x20, 0x34, 0x9D, 0x2E, 0xD4, 0x05, 0x7A, 0x2B, + 0x54, 0x41, 0x95, 0x45, 0xDA, 0xBB, 0x80, 0x9E, 0x63, 0x81, 0xDE, 0x1B, 0xAD, 0x83, 0x37, 0xE2, + } }, + { "abc", + { + 0x64, 0x37, 0xB3, 0xAC, 0x38, 0x46, 0x51, 0x33, 0xFF, 0xB6, 0x3B, 0x75, 0x27, 0x3A, 0x8D, 0xB5, + 0x48, 0xC5, 0x58, 0x46, 0x5D, 0x79, 0xDB, 0x03, 0xFD, 0x35, 0x9C, 0x6C, 0xD5, 0xBD, 0x9D, 0x85, + } }, + { "abcd", + { + 0x8C, 0x9C, 0x98, 0x81, 0x80, 0x5D, 0x1A, 0x84, 0x71, 0x02, 0xD7, 0xA4, 0x2E, 0x58, 0xB9, 0x90, + 0xD0, 0x88, 0xDD, 0x88, 0xA8, 0x4F, 0x73, 0x14, 0xD7, 0x1C, 0x83, 0x81, 0x07, 0x57, 0x1F, 0x2B, + } }, + { "abcde", + { + 0x06, 0x48, 0xC0, 0x3B, 0x5A, 0xD9, 0xBB, 0x6D, 0xDF, 0x83, 0x06, 0xEE, 0xF6, 0xA3, 0x3E, 0xBA, + 0xE8, 0xF8, 0x9C, 0xB4, 0x74, 0x11, 0x50, 0xC1, 0xAE, 0x9C, 0xD6, 0x62, 0xFD, 0xCC, 0x1E, 0xE2, + } }, + { "abcdef", + { + 0xB3, 0x4B, 0x56, 0x07, 0x67, 0x12, 0xFD, 0x7F, 0xB9, 0xC0, 0x67, 0x24, 0x5A, 0x6C, 0x85, 0xE1, + 0x61, 0x74, 0xB3, 0xEF, 0x2E, 0x35, 0xDF, 0x7B, 0x56, 0xB7, 0xF1, 0x64, 0xE5, 0xC3, 0x64, 0x46, + } }, + { "abcdefg", + { + 0xE2, 0xD1, 0x8D, 0x70, 0xDB, 0x12, 0x70, 0x5E, 0x18, 0x45, 0xFA, 0xF5, 0x00, 0xDE, 0x11, 0x98, + 0xA5, 0xBA, 0x14, 0x83, 0x72, 0x9D, 0x97, 0x93, 0x6F, 0x1D, 0x2B, 0x76, 0x09, 0x68, 0x31, 0x2E, + } }, + { "abcdefgh", + { + 0xDD, 0xAA, 0x2A, 0xC3, 0x0A, 0x98, 0x65, 0x59, 0x62, 0x97, 0x90, 0x19, 0xE4, 0x35, 0x38, 0x32, + 0x6A, 0xD7, 0xBE, 0xF0, 0xDA, 0x0E, 0x6A, 0xC2, 0xF3, 0xE5, 0x1F, 0xB3, 0x51, 0x3A, 0x5C, 0xDA, + } }, + { "abcdefghi", + { + 0x89, 0x9E, 0xAD, 0x67, 0x56, 0x1E, 0x6E, 0x71, 0x76, 0xDD, 0xCA, 0xD0, 0xB4, 0x47, 0xCA, 0xEC, + 0x42, 0xA6, 0x58, 0xB7, 0x0B, 0xB1, 0x81, 0x75, 0x7F, 0x14, 0x4C, 0xE9, 0xEB, 0xB1, 0x59, 0xC4, + } }, + { "abcdefghij", + { + 0xD1, 0x0C, 0x2A, 0xCB, 0x51, 0x8F, 0xD7, 0x4A, 0xE1, 0x30, 0xF6, 0x3E, 0x3A, 0x45, 0x2A, 0x9A, + 0x05, 0x54, 0x71, 0x16, 0x41, 0x81, 0xA6, 0x3A, 0x7D, 0x94, 0xC1, 0x82, 0xF5, 0x70, 0x34, 0x9B, + } }, + { "abcdefghijk", + { + 0x33, 0x69, 0x93, 0xBC, 0xB0, 0xAA, 0xCB, 0x8B, 0x33, 0xCB, 0xBF, 0x67, 0x70, 0x25, 0x59, 0x29, + 0x62, 0x60, 0x6A, 0x69, 0x0C, 0xEC, 0xBE, 0xD3, 0x85, 0x7C, 0x81, 0xF9, 0x49, 0x36, 0x4E, 0xA5, + } }, + { "abcdefghijkl", + { + 0xA7, 0x4A, 0x54, 0x2E, 0xA1, 0xF9, 0x95, 0x7F, 0x55, 0xBA, 0xE1, 0x99, 0xF8, 0x9A, 0xB4, 0x6B, + 0x90, 0xC8, 0xB4, 0x1E, 0x94, 0x04, 0x89, 0x07, 0x5E, 0xC9, 0x24, 0x49, 0x0F, 0xB6, 0xA9, 0x26, + } }, + { "abcdefghijklm", + { + 0xA1, 0xF7, 0xDF, 0x9D, 0x3A, 0x01, 0x44, 0x25, 0x22, 0x0D, 0x2B, 0x96, 0xDF, 0xB3, 0xCE, 0xBA, + 0x8A, 0xD1, 0x26, 0x4E, 0xD1, 0x65, 0x56, 0x50, 0x02, 0xA6, 0xEC, 0xC3, 0x02, 0xAF, 0x7A, 0xD0, + } }, + { "abcdefghijklmn", + { + 0x7A, 0x97, 0x9F, 0xCC, 0xF3, 0x89, 0x89, 0xFC, 0xDD, 0x63, 0x09, 0xDB, 0x94, 0x7D, 0x28, 0x6D, + 0xF2, 0xF4, 0xF7, 0xEC, 0x80, 0xDD, 0x11, 0x88, 0xEC, 0x07, 0x94, 0xE9, 0x1B, 0x8F, 0xBE, 0x2E, + } }, + { "abcdefghijklmno", + { + 0xF3, 0x50, 0x1B, 0x61, 0x52, 0x06, 0xCE, 0x9E, 0x7D, 0xC2, 0xC6, 0xAD, 0x16, 0xA0, 0xF2, 0xC6, + 0x44, 0x34, 0xDD, 0xF1, 0xA5, 0x33, 0xBB, 0xDC, 0x0A, 0x25, 0xA6, 0x0D, 0x20, 0xE4, 0x4E, 0x5E, + } }, + { "abcdefghijklmnop", + { + 0x00, 0x9E, 0x43, 0x83, 0x6D, 0x52, 0xF4, 0x8B, 0x87, 0x6D, 0x62, 0x74, 0xFF, 0x17, 0xFA, 0xF3, + 0xB5, 0xA3, 0x4A, 0xF7, 0x7E, 0x68, 0xD7, 0xFA, 0x73, 0x0E, 0x5E, 0xF9, 0xFE, 0xA2, 0xD3, 0x58, + } }, + { "abcdefghijklmnopq", + { + 0x26, 0xDC, 0x2E, 0x71, 0x55, 0x16, 0xD4, 0x06, 0xC3, 0x70, 0x02, 0x05, 0x68, 0x90, 0xFE, 0xD1, + 0x94, 0x64, 0x83, 0x38, 0x7E, 0xFB, 0xB8, 0x0E, 0x87, 0x33, 0x74, 0x32, 0x67, 0x37, 0x21, 0x61, + } }, + { "abcdefghijklmnopqr", + { + 0xD1, 0x32, 0x17, 0xD1, 0x80, 0xF3, 0x75, 0xED, 0xDD, 0x8A, 0x18, 0x9E, 0x03, 0x18, 0x31, 0x69, + 0x1F, 0xBD, 0x73, 0xB0, 0x28, 0xEE, 0x49, 0x7A, 0x5C, 0xAF, 0xC0, 0x8B, 0xD2, 0x9C, 0xEA, 0x6C, + } }, + { "abcdefghijklmnopqrs", + { + 0x24, 0x5F, 0xE8, 0x6C, 0xDE, 0x9B, 0x1E, 0x6F, 0xAD, 0xDB, 0xFA, 0xEE, 0xAF, 0x7F, 0x9C, 0x7C, + 0xD9, 0xC0, 0x9A, 0xD6, 0x2B, 0x38, 0x45, 0x2D, 0x10, 0x3F, 0x62, 0x09, 0x83, 0x4C, 0xBF, 0x23, + } }, + { "abcdefghijklmnopqrst", + { + 0x18, 0xC5, 0x4F, 0xED, 0x84, 0xD3, 0x23, 0xE2, 0xEE, 0x91, 0x94, 0x81, 0x19, 0x22, 0x4F, 0x31, + 0x59, 0xBF, 0xD4, 0xCD, 0xD3, 0xF5, 0x85, 0xF8, 0x2B, 0x56, 0xE0, 0xA6, 0x30, 0x92, 0xAD, 0xDE, + } }, + { "abcdefghijklmnopqrstu", + { + 0x61, 0x4A, 0x68, 0x5B, 0xE9, 0x1B, 0xC4, 0x46, 0x05, 0xEA, 0xE3, 0x31, 0x17, 0x5F, 0x45, 0xB8, + 0xDA, 0xC2, 0x6F, 0xE3, 0xD1, 0xC5, 0xFB, 0xCD, 0x5D, 0x76, 0x1E, 0x0F, 0x74, 0x12, 0xB8, 0x2F, + } }, + { "abcdefghijklmnopqrstuv", + { + 0x22, 0x37, 0x6F, 0x74, 0xFE, 0x12, 0x93, 0xC4, 0xB7, 0x3B, 0xA3, 0x53, 0x7F, 0x00, 0xA3, 0x52, + 0xE6, 0xA1, 0x2D, 0x67, 0xBF, 0xF9, 0x74, 0xC3, 0xBB, 0xA4, 0x4A, 0x5F, 0xC0, 0x3F, 0xED, 0xE7, + } }, + { "abcdefghijklmnopqrstuvw", + { + 0xC9, 0x65, 0xC1, 0xCC, 0xCE, 0x9C, 0xBC, 0xCB, 0xB8, 0x68, 0x31, 0x64, 0x91, 0xAA, 0x01, 0x86, + 0xAB, 0x83, 0x9C, 0xFE, 0x86, 0xEF, 0xA4, 0xFE, 0xDF, 0xF0, 0x79, 0x2C, 0x79, 0xCF, 0x4E, 0xF9, + } }, + { "abcdefghijklmnopqrstuvwx", + { + 0x3A, 0x00, 0x36, 0x42, 0xAB, 0x93, 0xEA, 0xD3, 0xDC, 0xEB, 0xDE, 0x1C, 0xD7, 0x4D, 0x48, 0x2A, + 0xEA, 0xB7, 0x6C, 0x51, 0x52, 0xA7, 0xF2, 0xE4, 0x02, 0x39, 0x63, 0xBF, 0x36, 0x57, 0x03, 0xD8, + } }, + { "abcdefghijklmnopqrstuvwxy", + { + 0xF7, 0xD9, 0x71, 0xE0, 0x5B, 0xAF, 0xD5, 0x8A, 0x22, 0x0F, 0x3A, 0x95, 0x34, 0x54, 0x2F, 0xE5, + 0x45, 0x60, 0x4A, 0x7F, 0x99, 0x16, 0x56, 0x49, 0xB6, 0x59, 0x09, 0x3A, 0xEB, 0xA5, 0xFA, 0x6A, + } }, + { "abcdefghijklmnopqrstuvwxyz", + { + 0x24, 0x68, 0xEE, 0xC8, 0x89, 0x4A, 0xCF, 0xB4, 0xE4, 0xDF, 0x3A, 0x51, 0xEA, 0x91, 0x6B, 0xA1, + 0x15, 0xD4, 0x82, 0x68, 0x28, 0x77, 0x54, 0x29, 0x0A, 0xAE, 0x8E, 0x9E, 0x62, 0x28, 0xE8, 0x5F, + } }, + { "abcdefghijklmnopqrstuvwxyz0", + { + 0xD6, 0xC9, 0xDE, 0x2C, 0x54, 0xD2, 0x7B, 0xDB, 0x4F, 0x68, 0xCD, 0x3C, 0x73, 0x42, 0x1D, 0x81, + 0xF5, 0x2C, 0xC8, 0x06, 0xD2, 0x55, 0xDA, 0x08, 0xE2, 0x25, 0x4A, 0x0D, 0x57, 0x03, 0x1F, 0xF0, + } }, + { "abcdefghijklmnopqrstuvwxyz01", + { + 0x4D, 0x0C, 0x6F, 0x2F, 0xB0, 0xC1, 0xEB, 0xC4, 0x1B, 0xF2, 0x3C, 0xBA, 0xED, 0x30, 0x88, 0x39, + 0xD7, 0x80, 0xAB, 0x47, 0xC8, 0xA3, 0x81, 0x23, 0xAF, 0x46, 0xB6, 0xE3, 0xAD, 0x82, 0x5F, 0xA4, + } }, + { "abcdefghijklmnopqrstuvwxyz012", + { + 0x4C, 0xEB, 0x7C, 0x49, 0x7A, 0xF4, 0xB6, 0x73, 0xC8, 0x58, 0xD8, 0x74, 0x6F, 0xDD, 0xBA, 0x3B, + 0xFA, 0x80, 0xFA, 0x1A, 0xCB, 0x84, 0xE2, 0xAE, 0x91, 0x76, 0x9D, 0x4B, 0xD8, 0x74, 0xEA, 0x70, + } }, + { "abcdefghijklmnopqrstuvwxyz0123", + { + 0xED, 0x4C, 0x4A, 0xC9, 0x6A, 0x2E, 0xB3, 0xC0, 0xCC, 0x80, 0x88, 0xB4, 0x30, 0x3F, 0xD6, 0x78, + 0x9B, 0x65, 0x16, 0x2F, 0x35, 0xD2, 0x77, 0xB2, 0xE6, 0xA8, 0x0F, 0xAF, 0x48, 0xCA, 0x67, 0x5E, + } }, + { "abcdefghijklmnopqrstuvwxyz01234", + { + 0x6A, 0x96, 0x16, 0x1F, 0x64, 0xDB, 0x0D, 0x56, 0xF0, 0x73, 0xFF, 0xE3, 0xC2, 0xC6, 0x66, 0xEB, + 0x70, 0x2F, 0xFF, 0xCA, 0xA1, 0xF0, 0x96, 0xEB, 0xB1, 0x97, 0x0F, 0x78, 0xFD, 0xB5, 0x2B, 0xC9, + } }, + { "abcdefghijklmnopqrstuvwxyz012345", + { + 0x35, 0x5E, 0x5F, 0xD6, 0x25, 0xA9, 0xCD, 0x5C, 0x27, 0xB3, 0x12, 0xB3, 0xC4, 0x20, 0x8D, 0x02, + 0x36, 0xED, 0x6D, 0x32, 0x56, 0x6B, 0xD5, 0xA0, 0x64, 0x25, 0x99, 0xC1, 0xC8, 0x99, 0x64, 0x06, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456", + { + 0x86, 0xDE, 0x08, 0xB3, 0x23, 0x46, 0xA1, 0x21, 0xDB, 0xC1, 0xBB, 0xB9, 0x0C, 0xFF, 0xCA, 0x94, + 0x29, 0xA5, 0x06, 0x8D, 0x79, 0x52, 0xFE, 0xF8, 0x97, 0x41, 0x6E, 0xBC, 0xE2, 0x47, 0xC6, 0xAE, + } }, + { "abcdefghijklmnopqrstuvwxyz01234567", + { + 0xFA, 0x75, 0xCD, 0x23, 0x99, 0x2C, 0x82, 0xCF, 0x11, 0x0B, 0x4C, 0xA1, 0xEE, 0x6A, 0x11, 0x86, + 0x15, 0x48, 0xE9, 0xBD, 0x9C, 0xCB, 0x63, 0x2C, 0x7B, 0x60, 0x1F, 0xC3, 0xCB, 0x10, 0x65, 0x9F, + } }, + { "abcdefghijklmnopqrstuvwxyz012345678", + { + 0x2F, 0x96, 0xFC, 0xD5, 0x47, 0x6D, 0x14, 0x65, 0xB0, 0xA9, 0x9B, 0x37, 0x31, 0xCA, 0xF2, 0x41, + 0x4B, 0xD2, 0xF0, 0x90, 0x10, 0xEE, 0x09, 0x44, 0x48, 0xBD, 0xB5, 0x59, 0x8A, 0xEC, 0xFF, 0xD2, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789", + { + 0xB0, 0xB9, 0x2F, 0x78, 0x81, 0x54, 0x3E, 0xFB, 0x77, 0xF3, 0x18, 0x6D, 0x81, 0x86, 0x09, 0x44, + 0x20, 0xA9, 0x00, 0x63, 0xBB, 0x5A, 0x38, 0xC7, 0x55, 0x1D, 0xFB, 0x3D, 0xAC, 0x2F, 0xEB, 0xB1, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789a", + { + 0x0B, 0xE1, 0x2A, 0x0C, 0xFC, 0xE8, 0xCF, 0xAF, 0xB4, 0x66, 0x2D, 0xBC, 0xFD, 0xD4, 0x21, 0xAF, + 0x55, 0x98, 0xE2, 0x01, 0x5D, 0xC8, 0xA3, 0x80, 0x5A, 0x68, 0xA7, 0x6D, 0x9D, 0xB0, 0xFE, 0xC3, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789ab", + { + 0x82, 0x0F, 0x15, 0xAE, 0x80, 0x73, 0x5E, 0x1B, 0x0A, 0x67, 0x6C, 0xA6, 0x9D, 0x36, 0xA1, 0x8D, + 0x86, 0x93, 0x21, 0x39, 0x84, 0xD5, 0x5B, 0x5E, 0x3A, 0x7C, 0xC9, 0x60, 0x45, 0x08, 0x49, 0x79, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abc", + { + 0xD8, 0x42, 0xF4, 0x26, 0xC3, 0x02, 0xDD, 0x36, 0xE7, 0x26, 0xD8, 0x59, 0xF0, 0xD3, 0x54, 0x3B, + 0xA9, 0xF3, 0x31, 0xE2, 0xA4, 0xFC, 0x93, 0xF9, 0x22, 0xC0, 0xDD, 0xFA, 0x60, 0x2E, 0x36, 0x32, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcd", + { + 0x50, 0xA4, 0xDE, 0x4F, 0x65, 0x9A, 0x28, 0x2C, 0xD0, 0x99, 0x68, 0x60, 0x12, 0xDB, 0xD9, 0xAF, + 0x2C, 0x1F, 0xD9, 0x7B, 0x50, 0x8E, 0xE8, 0x7B, 0xF6, 0x5F, 0x6F, 0x3E, 0x7F, 0x67, 0xB5, 0xF9, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcde", + { + 0x05, 0xC5, 0x21, 0xE1, 0x77, 0x93, 0xF5, 0x5D, 0xAF, 0x1D, 0x3A, 0xDD, 0xD1, 0x3A, 0xC8, 0xF7, + 0x84, 0x51, 0xAF, 0xE4, 0x1C, 0x3D, 0xAC, 0x3D, 0xE5, 0x5D, 0x9F, 0x11, 0xE8, 0x31, 0xED, 0x2B, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdef", + { + 0x1D, 0x7D, 0x82, 0xB2, 0xD2, 0x17, 0xD9, 0xDF, 0xCB, 0xC6, 0xD9, 0x72, 0x47, 0x22, 0x0C, 0xC5, + 0x2D, 0xF1, 0x0F, 0xAF, 0xD4, 0x51, 0x61, 0xD2, 0x6A, 0x36, 0x36, 0x5D, 0x0E, 0xB8, 0xDD, 0x65, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefg", + { + 0xC7, 0x82, 0x41, 0x6B, 0xF3, 0xE7, 0x40, 0x6B, 0x1A, 0xFD, 0xC8, 0x2A, 0xFB, 0x6D, 0xE6, 0xB9, + 0x15, 0xFF, 0x83, 0x48, 0x2D, 0x61, 0x11, 0x8E, 0xE1, 0xC0, 0x35, 0xE6, 0x48, 0x39, 0x9E, 0xE6, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefgh", + { + 0x0C, 0xB5, 0x2C, 0xD0, 0x1B, 0x97, 0x54, 0x0F, 0x87, 0x3E, 0xD6, 0x71, 0x9D, 0x5F, 0xF6, 0xFE, + 0xB1, 0xE1, 0x46, 0x91, 0x35, 0x50, 0x0E, 0x6E, 0xD5, 0x9D, 0x21, 0x41, 0x43, 0xD9, 0x50, 0xC2, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghi", + { + 0x6F, 0xE1, 0xD4, 0x04, 0x47, 0x58, 0x8C, 0x8D, 0xD9, 0x7B, 0x63, 0x72, 0xE4, 0x85, 0xD9, 0x33, + 0x63, 0x36, 0x2A, 0x5B, 0xF6, 0x4E, 0x4C, 0x1B, 0x34, 0x1B, 0xD7, 0xF7, 0xFF, 0x86, 0x81, 0xFC, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghij", + { + 0xB4, 0x75, 0xF1, 0x63, 0xEF, 0x54, 0x19, 0x19, 0x01, 0x9D, 0x5B, 0xF2, 0x87, 0xC5, 0x6E, 0xD6, + 0x47, 0x24, 0xFD, 0x54, 0x86, 0x5A, 0x6A, 0xC1, 0xF0, 0x1D, 0x20, 0x06, 0x23, 0x29, 0x85, 0x01, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijk", + { + 0x57, 0xA5, 0xD1, 0x5A, 0xAB, 0x13, 0x3A, 0x41, 0x25, 0xBA, 0x8E, 0xFC, 0x97, 0x90, 0x48, 0x16, + 0x6A, 0x21, 0x58, 0x5F, 0x47, 0xDA, 0xC9, 0x64, 0xA6, 0x4C, 0xCA, 0xD0, 0x49, 0xF9, 0x5B, 0xC1, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijkl", + { + 0x40, 0x9F, 0x20, 0x6E, 0xBE, 0x1D, 0x31, 0x1C, 0x2E, 0x97, 0x16, 0xC6, 0x8F, 0x81, 0xBF, 0x7D, + 0xA2, 0x2A, 0xC3, 0x27, 0x10, 0x07, 0xF6, 0x15, 0x54, 0x0D, 0xF8, 0xA3, 0x22, 0x54, 0x08, 0xA0, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklm", + { + 0xC1, 0x1A, 0x7C, 0x91, 0xAC, 0xC9, 0x02, 0xA6, 0xC5, 0x41, 0xFC, 0x0C, 0x79, 0x49, 0xDC, 0x86, + 0xF5, 0xBE, 0xCD, 0x3E, 0xFD, 0x21, 0x89, 0x64, 0xD2, 0x36, 0x1A, 0x9D, 0xEB, 0xC9, 0xD6, 0x24, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmn", + { + 0xC4, 0xBB, 0x86, 0x95, 0x20, 0x61, 0xEC, 0xB5, 0x97, 0x16, 0x3E, 0xB3, 0xAD, 0xD6, 0xAB, 0x55, + 0xEB, 0x76, 0x25, 0xCD, 0xA7, 0x43, 0x89, 0x39, 0xF0, 0x58, 0xFD, 0x37, 0x43, 0xF7, 0x50, 0xE6, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmno", + { + 0x52, 0x37, 0x53, 0x3B, 0x57, 0x4F, 0x97, 0xAE, 0x1F, 0x93, 0xEE, 0x00, 0x56, 0x59, 0xCD, 0xCB, + 0x2D, 0x93, 0xF5, 0x28, 0x2D, 0x88, 0x12, 0xCD, 0xCD, 0xF1, 0xB2, 0x3C, 0xE6, 0xC0, 0x5D, 0xE1, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnop", + { + 0x12, 0x31, 0x05, 0x55, 0x14, 0x80, 0x59, 0xFD, 0x7D, 0x68, 0x56, 0xD8, 0x66, 0x5D, 0xBB, 0xCF, + 0xC8, 0x27, 0x88, 0x7F, 0x4F, 0xE3, 0x3E, 0x60, 0x5B, 0x3F, 0xF8, 0x3D, 0x5F, 0x42, 0xCB, 0x4B, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopq", + { + 0xF1, 0xEF, 0x42, 0xBD, 0x61, 0x26, 0x88, 0x75, 0x92, 0x98, 0x37, 0x2B, 0x04, 0x3C, 0xBB, 0x22, + 0x71, 0xA6, 0x51, 0x12, 0x0D, 0x99, 0xA4, 0x02, 0x52, 0xC0, 0x75, 0xC8, 0x32, 0x57, 0x61, 0xA1, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqr", + { + 0x39, 0xC9, 0x89, 0x0B, 0x86, 0xAC, 0xDF, 0xD8, 0xB8, 0x76, 0x4C, 0x78, 0x34, 0x62, 0x25, 0xF9, + 0xD0, 0x69, 0xCC, 0x53, 0xB8, 0xD8, 0xC3, 0xB9, 0xD5, 0xD9, 0x99, 0x22, 0xBA, 0x4E, 0x2C, 0x43, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrs", + { + 0x94, 0x84, 0xD7, 0x8C, 0x2C, 0x64, 0x9C, 0x38, 0x41, 0xE5, 0x95, 0xCD, 0x20, 0xA4, 0xD0, 0x87, + 0xBF, 0x52, 0xCE, 0x14, 0x69, 0xE2, 0x57, 0x08, 0xA4, 0x18, 0x32, 0x58, 0xC6, 0x1E, 0xD2, 0xEF, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrst", + { + 0x38, 0x5C, 0xC1, 0x1A, 0x36, 0x61, 0x12, 0xBE, 0x5B, 0xF3, 0x36, 0x32, 0xB3, 0x63, 0xD4, 0x95, + 0x5D, 0x29, 0x5F, 0x1F, 0x2B, 0x4C, 0xF0, 0x08, 0xBB, 0x0E, 0x67, 0x90, 0xB1, 0x17, 0xD3, 0xE6, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstu", + { + 0x52, 0x35, 0x52, 0x89, 0x00, 0xF4, 0xBC, 0x82, 0xF5, 0x47, 0x46, 0x33, 0x05, 0x87, 0xD1, 0x1B, + 0x8F, 0x20, 0x3E, 0x66, 0x35, 0xD8, 0x3A, 0xB7, 0x08, 0xC6, 0x9A, 0x95, 0xBC, 0x6E, 0xC7, 0xAD, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuv", + { + 0xD1, 0x40, 0x7E, 0x7D, 0x6B, 0x47, 0x49, 0xF9, 0x9F, 0xEB, 0x9C, 0xAE, 0x77, 0xFF, 0x4B, 0x3B, + 0x32, 0xA6, 0xD0, 0xD3, 0x6E, 0xB1, 0xA2, 0x79, 0x28, 0xBD, 0xAB, 0x1A, 0x98, 0x21, 0xF0, 0xD7, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvw", + { + 0x4F, 0xE7, 0xCC, 0x9F, 0x96, 0x3F, 0x77, 0x03, 0xB4, 0x48, 0x26, 0xEC, 0x47, 0x6E, 0x63, 0x3F, + 0x22, 0xCA, 0x25, 0x97, 0xAE, 0x1A, 0x5B, 0x75, 0xF8, 0x4A, 0xFE, 0x6C, 0x8A, 0x04, 0xAD, 0x56, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwx", + { + 0xD0, 0x50, 0x10, 0x40, 0xA4, 0xCE, 0x8E, 0xB2, 0x73, 0x55, 0x19, 0xC3, 0xFB, 0xED, 0x76, 0x5E, + 0x9D, 0x80, 0x42, 0xDD, 0x3B, 0xD4, 0x3F, 0xF9, 0x07, 0xCC, 0xD9, 0x5D, 0xCC, 0x17, 0xC6, 0xCC, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxy", + { + 0xE8, 0x8C, 0xAF, 0x20, 0x5D, 0x3C, 0x9F, 0x8F, 0x82, 0x2F, 0x65, 0x3A, 0xD5, 0x80, 0x9F, 0x43, + 0xD1, 0xF9, 0xD4, 0x6A, 0x3E, 0x45, 0xA9, 0xEB, 0xCF, 0xF2, 0xE6, 0xC0, 0x64, 0x38, 0xF8, 0x7D, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz", + { + 0x23, 0xCE, 0xAA, 0xFD, 0xAC, 0x74, 0xFB, 0xB0, 0x87, 0x33, 0xC0, 0x03, 0x25, 0xA6, 0x96, 0x40, + 0xEE, 0x85, 0xC9, 0xB3, 0x32, 0x68, 0x2C, 0x5A, 0xE2, 0x68, 0xB4, 0x53, 0x90, 0x48, 0x7C, 0x6A, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0", + { + 0xD4, 0x91, 0x25, 0x0A, 0x64, 0xC0, 0xA6, 0xB6, 0xDB, 0x2D, 0xDE, 0x1A, 0xEA, 0x38, 0x92, 0xEE, + 0x56, 0x47, 0x8D, 0x2B, 0x26, 0xC4, 0x26, 0xE2, 0xA2, 0x52, 0xE5, 0x39, 0x37, 0x5F, 0xFB, 0x59, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01", + { + 0xF7, 0xF8, 0x54, 0x5A, 0x00, 0x36, 0x5D, 0xE0, 0x08, 0x90, 0xAF, 0x80, 0x89, 0x96, 0xED, 0x71, + 0x87, 0x8A, 0xDA, 0x34, 0x9A, 0x98, 0xD2, 0xCB, 0x5B, 0x91, 0x06, 0xC1, 0x95, 0x60, 0x71, 0x37, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz012", + { + 0xF5, 0x2B, 0x8E, 0x5E, 0x75, 0xC5, 0x6B, 0x8E, 0xAD, 0x21, 0xE5, 0xEF, 0x19, 0x19, 0xBD, 0xA7, + 0x30, 0x70, 0x8B, 0xA3, 0x3F, 0x9F, 0x24, 0x6A, 0x73, 0xC4, 0x03, 0xE1, 0x41, 0xCE, 0xED, 0x0F, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123", + { + 0x8D, 0x07, 0x4A, 0xB3, 0xB4, 0x1A, 0xF8, 0xCF, 0x10, 0xCB, 0x52, 0x60, 0x6A, 0xED, 0xEC, 0x0B, + 0xFB, 0x8D, 0xD9, 0xF0, 0xD5, 0x22, 0xA8, 0xAA, 0xD4, 0x5C, 0x4C, 0x50, 0x01, 0x60, 0xF3, 0x07, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01234", + { + 0x35, 0xFF, 0xDE, 0x6F, 0x4A, 0xF5, 0xE6, 0x5F, 0x5E, 0xCF, 0x17, 0x46, 0x4A, 0xE6, 0xDE, 0x27, + 0x56, 0x06, 0x5F, 0xAF, 0x72, 0xF6, 0x3A, 0xD9, 0x02, 0xBD, 0x0E, 0x56, 0x2B, 0xB7, 0x18, 0x94, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz012345", + { + 0xFE, 0x4F, 0x12, 0xD6, 0x2E, 0xC1, 0x73, 0x6E, 0x99, 0x4A, 0x78, 0xD8, 0xEF, 0x66, 0x7E, 0x5B, + 0x35, 0xB3, 0x03, 0x74, 0x85, 0x76, 0x0E, 0x8F, 0xFA, 0xDD, 0xE2, 0x41, 0xA6, 0x19, 0x34, 0x66, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456", + { + 0xA3, 0x27, 0xA8, 0xF0, 0xCE, 0xF2, 0x24, 0x4E, 0x39, 0x3F, 0xE9, 0x8B, 0xA7, 0xE5, 0x59, 0x5C, + 0x5E, 0x40, 0xE4, 0x35, 0x93, 0xE5, 0x87, 0xCE, 0x55, 0x43, 0x02, 0x1C, 0xD5, 0xF9, 0x4C, 0xAD, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01234567", + { + 0xD2, 0x17, 0xD0, 0xA3, 0xEC, 0x35, 0x28, 0x99, 0xDD, 0xB1, 0xD0, 0x38, 0xE5, 0x33, 0x6A, 0xE7, + 0x15, 0x56, 0xC0, 0xEA, 0x61, 0x2A, 0xF9, 0x70, 0xCB, 0x75, 0xD4, 0x9B, 0x1E, 0x25, 0x36, 0x6E, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz012345678", + { + 0x05, 0x5E, 0x47, 0x72, 0x8E, 0x3C, 0xE5, 0xD4, 0x83, 0xBD, 0xB4, 0x8F, 0x47, 0x14, 0x5C, 0xF6, + 0xF7, 0x31, 0xF5, 0x0F, 0xC9, 0x34, 0xA1, 0xF6, 0x4B, 0x58, 0xBD, 0xE6, 0x41, 0x38, 0x38, 0x07, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789", + { + 0xAA, 0x60, 0x21, 0x6D, 0xE8, 0x21, 0x17, 0x5F, 0x97, 0x3E, 0x38, 0x26, 0xED, 0x7A, 0x0B, 0x74, + 0x31, 0xEC, 0x87, 0xE8, 0xE2, 0x19, 0x2E, 0x80, 0x24, 0x12, 0x53, 0xB2, 0xA9, 0x4D, 0xB0, 0x11, + } }, +}; + +std::vector test_input(size_t input_len) +{ + std::vector input; + for (size_t i = 0; i < input_len; ++i) { + input.push_back(uint8_t(i % 251)); + } + return input; +} + +TEST(misc_blake3s, test_vectors) +{ + for (auto v : test_vectors) { + std::vector input(v.input.begin(), v.input.end()); + EXPECT_EQ(blake3::blake3s(input), v.output); + } +} diff --git a/cpp/src/aztec/crypto/blake3s/c_bind.cpp b/cpp/src/aztec/crypto/blake3s/c_bind.cpp new file mode 100644 index 0000000000..727fae4271 --- /dev/null +++ b/cpp/src/aztec/crypto/blake3s/c_bind.cpp @@ -0,0 +1,15 @@ +#include "blake3s.hpp" +#include + +#define WASM_EXPORT __attribute__((visibility("default"))) + +extern "C" { + +WASM_EXPORT void blake3s_to_field(uint8_t const* data, size_t length, uint8_t* r) +{ + std::vector inputv(data, data + length); + std::vector output = blake3::blake3s(inputv); + auto result = barretenberg::fr::serialize_from_buffer(output.data()); + barretenberg::fr::serialize_to_buffer(result, r); +} +} \ No newline at end of file diff --git a/cpp/src/aztec/crypto/blake3s_full/CMakeLists.txt b/cpp/src/aztec/crypto/blake3s_full/CMakeLists.txt new file mode 100644 index 0000000000..75e02deae2 --- /dev/null +++ b/cpp/src/aztec/crypto/blake3s_full/CMakeLists.txt @@ -0,0 +1 @@ +barretenberg_module(crypto_blake3s_full) \ No newline at end of file diff --git a/cpp/src/aztec/crypto/blake3s_full/blake3-impl.hpp b/cpp/src/aztec/crypto/blake3s_full/blake3-impl.hpp new file mode 100644 index 0000000000..036b9c2d4f --- /dev/null +++ b/cpp/src/aztec/crypto/blake3s_full/blake3-impl.hpp @@ -0,0 +1,294 @@ +/* + BLAKE3 reference source code package - C implementations + + Intellectual property: + + The Rust code is copyright Jack O'Connor, 2019-2020. + The C code is copyright Samuel Neves and Jack O'Connor, 2019-2020. + The assembly code is copyright Samuel Neves, 2019-2020. + + This work is released into the public domain with CC0 1.0. Alternatively, it is licensed under the Apache + License 2.0. + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE3 hash function can be found at + https://github.com/BLAKE3-team/BLAKE3. +*/ + +#ifndef BLAKE3_IMPL_H +#define BLAKE3_IMPL_H + +#include +#include +#include +#include +#include + +#include "blake3s.hpp" + +namespace blake3_full { + +// This C implementation tries to support recent versions of GCC, Clang, and +// MSVC. +#if defined(_MSC_VER) +#define INLINE static __forceinline +#else +#define INLINE static inline __attribute__((always_inline)) +#endif + +#if defined(__x86_64__) || defined(_M_X64) +#define IS_X86 +#define IS_X86_64 +#endif + +#if defined(__i386__) || defined(_M_IX86) +#define IS_X86 +#define IS_X86_32 +#endif + +#if defined(IS_X86) +#if defined(_MSC_VER) +#include +#endif +#include +#endif + +// #if defined(IS_X86) +// #define MAX_SIMD_DEGREE 16 +// #elif defined(BLAKE3_USE_NEON) +// #define MAX_SIMD_DEGREE 4 +// #else +#define MAX_SIMD_DEGREE 1 +// #endif + +// There are some places where we want a static size that's equal to the +// MAX_SIMD_DEGREE, but also at least 2. +#define MAX_SIMD_DEGREE_OR_2 (MAX_SIMD_DEGREE > 2 ? MAX_SIMD_DEGREE : 2) + +// The dynamically detected SIMD degree of the current platform. +/* + * Commenting out unnecessary parts as we currently don't need SIMD fo + * different hardwares. To be revisited later. + * + */ +size_t blake3_simd_degree(void) +{ + return 1; + // #if defined(IS_X86) + // const enum cpu_feature features = get_cpu_features(); + // MAYBE_UNUSED(features); + // #if !defined(BLAKE3_NO_AVX512) + // if ((features & (AVX512F|AVX512VL)) == (AVX512F|AVX512VL)) { + // return 16; + // } + // #endif + // #if !defined(BLAKE3_NO_AVX2) + // if (features & AVX2) { + // return 8; + // } + // #endif + // #if !defined(BLAKE3_NO_SSE41) + // if (features & SSE41) { + // return 4; + // } + // #endif + // #if !defined(BLAKE3_NO_SSE2) + // if (features & SSE2) { + // return 4; + // } + // #endif + // #endif + // #if defined(BLAKE3_USE_NEON) + // return 4; + // #endif + // return 1; +} + +/*---------------------------------------------------------------- + * + * Commenting out as we currently don't need SIMD for different hardwares. + * To be revisited later. + * + +enum cpu_feature get_cpu_features() { + if (g_cpu_features != UNDEFINED) { + return g_cpu_features; + } else { +#if defined(IS_X86) + uint32_t regs[4] = {0}; + uint32_t *eax = ®s[0], *ebx = ®s[1], *ecx = ®s[2], *edx = ®s[3]; + (void)edx; + enum cpu_feature features = 0; + cpuid(regs, 0); + const int max_id = *eax; + cpuid(regs, 1); +#if defined(__amd64__) || defined(_M_X64) + features |= SSE2; +#else + if (*edx & (1UL << 26)) + features |= SSE2; +#endif + if (*ecx & (1UL << 0)) + features |= SSSE3; + if (*ecx & (1UL << 19)) + features |= SSE41; + + if (*ecx & (1UL << 27)) { // OSXSAVE + const uint64_t mask = xgetbv(); + if ((mask & 6) == 6) { // SSE and AVX states + if (*ecx & (1UL << 28)) + features |= AVX; + if (max_id >= 7) { + cpuidex(regs, 7, 0); + if (*ebx & (1UL << 5)) + features |= AVX2; + if ((mask & 224) == 224) { // Opmask, ZMM_Hi256, Hi16_Zmm + if (*ebx & (1UL << 31)) + features |= AVX512VL; + if (*ebx & (1UL << 16)) + features |= AVX512F; + } + } + } + } + g_cpu_features = features; + return features; +#else + // How to detect NEON? + return 0; +#endif + } +} +----------------------------------------------------------------*/ + +/* Find index of the highest set bit */ +/* x is assumed to be nonzero. */ +static unsigned int highest_one(uint64_t x) +{ +#if defined(__GNUC__) || defined(__clang__) + return uint32_t(63) ^ uint32_t(__builtin_clzll(x)); +#elif defined(_MSC_VER) && defined(IS_X86_64) + unsigned long index; + _BitScanReverse64(&index, x); + return index; +#elif defined(_MSC_VER) && defined(IS_X86_32) + if (x >> 32) { + unsigned long index; + _BitScanReverse(&index, x >> 32); + return 32 + index; + } else { + unsigned long index; + _BitScanReverse(&index, x); + return index; + } +#else + unsigned int c = 0; + if (x & 0xffffffff00000000ULL) { + x >>= 32; + c += 32; + } + if (x & 0x00000000ffff0000ULL) { + x >>= 16; + c += 16; + } + if (x & 0x000000000000ff00ULL) { + x >>= 8; + c += 8; + } + if (x & 0x00000000000000f0ULL) { + x >>= 4; + c += 4; + } + if (x & 0x000000000000000cULL) { + x >>= 2; + c += 2; + } + if (x & 0x0000000000000002ULL) { + c += 1; + } + return c; +#endif +} + +// Count the number of 1 bits. +INLINE unsigned int popcnt(uint64_t x) +{ +#if defined(__GNUC__) || defined(__clang__) + return uint32_t(__builtin_popcountll(x)); +#else + unsigned int count = 0; + while (x != 0) { + count += 1; + x &= x - 1; + } + return count; +#endif +} + +// Right rotates 32 bit inputs +INLINE uint32_t rotr32(uint32_t w, uint32_t c) +{ + return (w >> c) | (w << (32 - c)); +} + +// Largest power of two less than or equal to x. As a special case, returns 1 +// when x is 0. +INLINE uint64_t round_down_to_power_of_2(uint64_t x) +{ + return 1ULL << highest_one(x | 1); +} + +INLINE uint32_t counter_low(uint64_t counter) +{ + return (uint32_t)counter; +} + +INLINE uint32_t counter_high(uint64_t counter) +{ + return (uint32_t)(counter >> 32); +} + +INLINE uint32_t load32(const void* src) +{ + const uint8_t* p = (const uint8_t*)src; + return ((uint32_t)(p[0]) << 0) | ((uint32_t)(p[1]) << 8) | ((uint32_t)(p[2]) << 16) | ((uint32_t)(p[3]) << 24); +} + +INLINE void load_key_words(const uint8_t key[BLAKE3_KEY_LEN], uint32_t key_words[8]) +{ + key_words[0] = load32(&key[0 * 4]); + key_words[1] = load32(&key[1 * 4]); + key_words[2] = load32(&key[2 * 4]); + key_words[3] = load32(&key[3 * 4]); + key_words[4] = load32(&key[4 * 4]); + key_words[5] = load32(&key[5 * 4]); + key_words[6] = load32(&key[6 * 4]); + key_words[7] = load32(&key[7 * 4]); +} + +INLINE void store32(void* dst, uint32_t w) +{ + uint8_t* p = (uint8_t*)dst; + p[0] = (uint8_t)(w >> 0); + p[1] = (uint8_t)(w >> 8); + p[2] = (uint8_t)(w >> 16); + p[3] = (uint8_t)(w >> 24); +} + +INLINE void store_cv_words(uint8_t bytes_out[32], uint32_t cv_words[8]) +{ + store32(&bytes_out[0 * 4], cv_words[0]); + store32(&bytes_out[1 * 4], cv_words[1]); + store32(&bytes_out[2 * 4], cv_words[2]); + store32(&bytes_out[3 * 4], cv_words[3]); + store32(&bytes_out[4 * 4], cv_words[4]); + store32(&bytes_out[5 * 4], cv_words[5]); + store32(&bytes_out[6 * 4], cv_words[6]); + store32(&bytes_out[7 * 4], cv_words[7]); +} + +} // namespace blake3_full + +#endif /* BLAKE3_IMPL_H */ diff --git a/cpp/src/aztec/crypto/blake3s_full/blake3s.cpp b/cpp/src/aztec/crypto/blake3s_full/blake3s.cpp new file mode 100644 index 0000000000..b9bc4dc98b --- /dev/null +++ b/cpp/src/aztec/crypto/blake3s_full/blake3s.cpp @@ -0,0 +1,884 @@ +/* + BLAKE3 reference source code package - C implementations + + Intellectual property: + + The Rust code is copyright Jack O'Connor, 2019-2020. + The C code is copyright Samuel Neves and Jack O'Connor, 2019-2020. + The assembly code is copyright Samuel Neves, 2019-2020. + + This work is released into the public domain with CC0 1.0. Alternatively, it is licensed under the Apache + License 2.0. + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE3 hash function can be found at + https://github.com/BLAKE3-team/BLAKE3. +*/ + +#include +#include +#include +#include + +#include "blake3-impl.hpp" + +namespace blake3_full { + +const char* blake3_version(void) +{ + return BLAKE3_VERSION_STRING; +} + +INLINE void chunk_state_init(blake3_chunk_state* self, const uint32_t key[8], uint8_t flags) +{ + for (size_t i = 0; i < 8; ++i) { + self->cv[i] = key[i]; + } + self->chunk_counter = 0; + for (size_t i = 0; i < BLAKE3_BLOCK_LEN; ++i) { + self->buf[i] = 0; + } + self->buf_len = 0; + self->blocks_compressed = 0; + self->flags = flags; +} + +INLINE void chunk_state_reset(blake3_chunk_state* self, const uint32_t key[8], uint64_t chunk_counter) +{ + for (size_t i = 0; i < 8; ++i) { + self->cv[i] = key[i]; + } + self->chunk_counter = chunk_counter; + self->blocks_compressed = 0; + for (size_t i = 0; i < BLAKE3_BLOCK_LEN; ++i) { + self->buf[i] = 0; + } + self->buf_len = 0; +} + +INLINE size_t chunk_state_len(const blake3_chunk_state* self) +{ + return (BLAKE3_BLOCK_LEN * (size_t)self->blocks_compressed) + ((size_t)self->buf_len); +} + +INLINE size_t chunk_state_fill_buf(blake3_chunk_state* self, const uint8_t* input, size_t input_len) +{ + size_t take = BLAKE3_BLOCK_LEN - ((size_t)self->buf_len); + if (take > input_len) { + take = input_len; + } + uint8_t* dest = self->buf + ((size_t)self->buf_len); + for (size_t i = 0; i < take; ++i) { + dest[i] = input[i]; + } + self->buf_len = static_cast(self->buf_len + static_cast(take)); + return take; +} + +INLINE uint8_t chunk_state_maybe_start_flag(const blake3_chunk_state* self) +{ + if (self->blocks_compressed == 0) { + return CHUNK_START; + } else { + return 0; + } +} + +typedef struct output_t__ { + uint32_t input_cv[8]; + uint64_t counter; + uint8_t block[BLAKE3_BLOCK_LEN]; + uint8_t block_len; + uint8_t flags; +} output_t; + +INLINE output_t make_output(const uint32_t input_cv[8], + const uint8_t block[BLAKE3_BLOCK_LEN], + uint8_t block_len, + uint64_t counter, + uint8_t flags) +{ + output_t ret; + for (size_t i = 0; i < 8; ++i) { + ret.input_cv[i] = input_cv[i]; + } + for (size_t i = 0; i < BLAKE3_BLOCK_LEN; ++i) { + ret.block[i] = block[i]; + } + ret.block_len = block_len; + ret.counter = counter; + ret.flags = flags; + return ret; +} + +// Chaining values within a given chunk (specifically the compress_in_place +// interface) are represented as words. This avoids unnecessary bytes<->words +// conversion overhead in the portable implementation. However, the hash_many +// interface handles both user input and parent node blocks, so it accepts +// bytes. For that reason, chaining values in the CV stack are represented as +// bytes. +INLINE void output_chaining_value(const output_t* self, uint8_t cv[32]) +{ + uint32_t cv_words[8]; + for (size_t i = 0; i < 8; ++i) { + cv_words[i] = self->input_cv[i]; + } + blake3_compress_in_place(cv_words, self->block, self->block_len, self->counter, self->flags); + store_cv_words(cv, cv_words); +} + +INLINE void output_root_bytes(const output_t* self, uint64_t seek, uint8_t* out, size_t out_len) +{ + uint64_t output_block_counter = seek / 64; + size_t offset_within_block = seek % 64; + uint8_t wide_buf[64]; + while (out_len > 0) { + blake3_compress_xof( + self->input_cv, self->block, self->block_len, output_block_counter, self->flags | ROOT, wide_buf); + size_t available_bytes = 64 - offset_within_block; + size_t memcpy_len; + if (out_len > available_bytes) { + memcpy_len = available_bytes; + } else { + memcpy_len = out_len; + } + for (size_t i = 0; i < memcpy_len; ++i) { + out[i] = wide_buf[i + offset_within_block]; + } + + out += memcpy_len; + out_len -= memcpy_len; + output_block_counter += 1; + offset_within_block = 0; + } +} + +INLINE void chunk_state_update(blake3_chunk_state* self, const uint8_t* input, size_t input_len) +{ + if (self->buf_len > 0) { + size_t take = chunk_state_fill_buf(self, input, input_len); + input += take; + input_len -= take; + if (input_len > 0) { + blake3_compress_in_place(self->cv, + self->buf, + BLAKE3_BLOCK_LEN, + self->chunk_counter, + self->flags | chunk_state_maybe_start_flag(self)); + self->blocks_compressed = static_cast(self->blocks_compressed + 1); + self->buf_len = 0; + for (size_t i = 0; i < BLAKE3_BLOCK_LEN; i++) { + self->buf[i] = 0; + } + } + } + + while (input_len > BLAKE3_BLOCK_LEN) { + blake3_compress_in_place( + self->cv, input, BLAKE3_BLOCK_LEN, self->chunk_counter, self->flags | chunk_state_maybe_start_flag(self)); + self->blocks_compressed = static_cast(self->blocks_compressed + 1); + input += BLAKE3_BLOCK_LEN; + input_len -= BLAKE3_BLOCK_LEN; + } + + size_t take = chunk_state_fill_buf(self, input, input_len); + input += take; + input_len -= take; +} + +INLINE output_t chunk_state_output(const blake3_chunk_state* self) +{ + uint8_t block_flags = self->flags | chunk_state_maybe_start_flag(self) | CHUNK_END; + return make_output(self->cv, self->buf, self->buf_len, self->chunk_counter, block_flags); +} + +INLINE output_t parent_output(const uint8_t block[BLAKE3_BLOCK_LEN], const uint32_t key[8], uint8_t flags) +{ + return make_output(key, block, BLAKE3_BLOCK_LEN, 0, flags | PARENT); +} + +// Given some input larger than one chunk, return the number of bytes that +// should go in the left subtree. This is the largest power-of-2 number of +// chunks that leaves at least 1 byte for the right subtree. +INLINE size_t left_len(size_t content_len) +{ + // Subtract 1 to reserve at least one byte for the right side. content_len + // should always be greater than BLAKE3_CHUNK_LEN. + size_t full_chunks = (content_len - 1) / BLAKE3_CHUNK_LEN; + return round_down_to_power_of_2(full_chunks) * BLAKE3_CHUNK_LEN; +} + +// Use SIMD parallelism to hash up to MAX_SIMD_DEGREE chunks at the same time +// on a single thread. Write out the chunk chaining values and return the +// number of chunks hashed. These chunks are never the root and never empty; +// those cases use a different codepath. +INLINE size_t compress_chunks_parallel( + const uint8_t* input, size_t input_len, const uint32_t key[8], uint64_t chunk_counter, uint8_t flags, uint8_t* out) +{ +#if defined(BLAKE3_TESTING) + assert(0 < input_len); + assert(input_len <= MAX_SIMD_DEGREE * BLAKE3_CHUNK_LEN); +#endif + + const uint8_t* chunks_array[MAX_SIMD_DEGREE]; + size_t input_position = 0; + size_t chunks_array_len = 0; + while (input_len - input_position >= BLAKE3_CHUNK_LEN) { + chunks_array[chunks_array_len] = &input[input_position]; + input_position += BLAKE3_CHUNK_LEN; + chunks_array_len += 1; + } + + blake3_hash_many(chunks_array, + chunks_array_len, + BLAKE3_CHUNK_LEN / BLAKE3_BLOCK_LEN, + key, + chunk_counter, + true, + flags, + CHUNK_START, + CHUNK_END, + out); + + // Hash the remaining partial chunk, if there is one. Note that the empty + // chunk (meaning the empty message) is a different codepath. + if (input_len > input_position) { + uint64_t counter = chunk_counter + (uint64_t)chunks_array_len; + blake3_chunk_state chunk_state; + chunk_state_init(&chunk_state, key, flags); + chunk_state.chunk_counter = counter; + chunk_state_update(&chunk_state, &input[input_position], input_len - input_position); + output_t output = chunk_state_output(&chunk_state); + output_chaining_value(&output, &out[chunks_array_len * BLAKE3_OUT_LEN]); + return chunks_array_len + 1; + } else { + return chunks_array_len; + } +} + +// Use SIMD parallelism to hash up to MAX_SIMD_DEGREE parents at the same time +// on a single thread. Write out the parent chaining values and return the +// number of parents hashed. (If there's an odd input chaining value left over, +// return it as an additional output.) These parents are never the root and +// never empty; those cases use a different codepath. +INLINE size_t compress_parents_parallel(const uint8_t* child_chaining_values, + size_t num_chaining_values, + const uint32_t key[8], + uint8_t flags, + uint8_t* out) +{ +#if defined(BLAKE3_TESTING) + assert(2 <= num_chaining_values); + assert(num_chaining_values <= 2 * MAX_SIMD_DEGREE_OR_2); +#endif + + const uint8_t* parents_array[MAX_SIMD_DEGREE_OR_2]; + size_t parents_array_len = 0; + while (num_chaining_values - (2 * parents_array_len) >= 2) { + parents_array[parents_array_len] = &child_chaining_values[2 * parents_array_len * BLAKE3_OUT_LEN]; + parents_array_len += 1; + } + + blake3_hash_many(parents_array, + parents_array_len, + 1, + key, + 0, // Parents always use counter 0. + false, + flags | PARENT, + 0, // Parents have no start flags. + 0, // Parents have no end flags. + out); + + // If there's an odd child left over, it becomes an output. + if (num_chaining_values > 2 * parents_array_len) { + for (size_t i = 0; i < BLAKE3_OUT_LEN; i++) { + out[parents_array_len * BLAKE3_OUT_LEN + i] = + child_chaining_values[2 * parents_array_len * BLAKE3_OUT_LEN + i]; + } + + return parents_array_len + 1; + } else { + return parents_array_len; + } +} + +// The wide helper function returns (writes out) an array of chaining values +// and returns the length of that array. The number of chaining values returned +// is the dyanmically detected SIMD degree, at most MAX_SIMD_DEGREE. Or fewer, +// if the input is shorter than that many chunks. The reason for maintaining a +// wide array of chaining values going back up the tree, is to allow the +// implementation to hash as many parents in parallel as possible. +// +// As a special case when the SIMD degree is 1, this function will still return +// at least 2 outputs. This guarantees that this function doesn't perform the +// root compression. (If it did, it would use the wrong flags, and also we +// wouldn't be able to implement exendable ouput.) Note that this function is +// not used when the whole input is only 1 chunk long; that's a different +// codepath. +// +// Why not just have the caller split the input on the first update(), instead +// of implementing this special rule? Because we don't want to limit SIMD o +// multi-threading parallelism for that update(). +static size_t blake3_compress_subtree_wide( + const uint8_t* input, size_t input_len, const uint32_t key[8], uint64_t chunk_counter, uint8_t flags, uint8_t* out) +{ + // Note that the single chunk case does *not* bump the SIMD degree up to 2 + // when it is 1. If this implementation adds multi-threading in the future, + // this gives us the option of multi-threading even the 2-chunk case, which + // can help performance on smaller platforms. + if (input_len <= blake3_simd_degree() * BLAKE3_CHUNK_LEN) { + return compress_chunks_parallel(input, input_len, key, chunk_counter, flags, out); + } + + // With more than simd_degree chunks, we need to recurse. Start by dividing + // the input into left and right subtrees. (Note that this is only optimal + // as long as the SIMD degree is a power of 2. If we ever get a SIMD degree + // of 3 or something, we'll need a more complicated strategy.) + size_t left_input_len = left_len(input_len); + size_t right_input_len = input_len - left_input_len; + const uint8_t* right_input = &input[left_input_len]; + uint64_t right_chunk_counter = chunk_counter + (uint64_t)(left_input_len / BLAKE3_CHUNK_LEN); + + // Make space for the child outputs. Here we use MAX_SIMD_DEGREE_OR_2 to + // account for the special case of returning 2 outputs when the SIMD degree + // is 1. + uint8_t cv_array[2 * MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN]; + size_t degree = blake3_simd_degree(); + if (left_input_len > BLAKE3_CHUNK_LEN && degree == 1) { + // The special case: We always use a degree of at least two, to make + // sure there are two outputs. Except, as noted above, at the chunk + // level, where we allow degree=1. (Note that the 1-chunk-input case is + // a different codepath.) + degree = 2; + } + uint8_t* right_cvs = &cv_array[degree * BLAKE3_OUT_LEN]; + + // Recurse! If this implementation adds multi-threading support in the + // future, this is where it will go. + size_t left_n = blake3_compress_subtree_wide(input, left_input_len, key, chunk_counter, flags, cv_array); + size_t right_n = + blake3_compress_subtree_wide(right_input, right_input_len, key, right_chunk_counter, flags, right_cvs); + + // The special case again. If simd_degree=1, then we'll have left_n=1 and + // right_n=1. Rather than compressing them into a single output, return + // them directly, to make sure we always have at least two outputs. + if (left_n == 1) { + for (size_t i = 0; i < 2 * BLAKE3_OUT_LEN; i++) { + out[i] = cv_array[i]; + } + + return 2; + } + + // Otherwise, do one layer of parent node compression. + size_t num_chaining_values = left_n + right_n; + return compress_parents_parallel(cv_array, num_chaining_values, key, flags, out); +} + +// Hash a subtree with compress_subtree_wide(), and then condense the resulting +// list of chaining values down to a single parent node. Don't compress that +// last parent node, however. Instead, return its message bytes (the +// concatenated chaining values of its children). This is necessary when the +// first call to update() supplies a complete subtree, because the topmost +// parent node of that subtree could end up being the root. It's also necessary +// for extended output in the general case. +// +// As with compress_subtree_wide(), this function is not used on inputs of 1 +// chunk or less. That's a different codepath. +INLINE void compress_subtree_to_parent_node(const uint8_t* input, + size_t input_len, + const uint32_t key[8], + uint64_t chunk_counter, + uint8_t flags, + uint8_t out[2 * BLAKE3_OUT_LEN]) +{ +#if defined(BLAKE3_TESTING) + assert(input_len > BLAKE3_CHUNK_LEN); +#endif + + // We need the size of cv_array to be atleast 4 * 32 + uint8_t cv_array[MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN * 2]; + size_t num_cvs = blake3_compress_subtree_wide(input, input_len, key, chunk_counter, flags, cv_array); + + // If MAX_SIMD_DEGREE is greater than 2 and there's enough input, + // compress_subtree_wide() returns more than 2 chaining values. Condense + // them into 2 by forming parent nodes repeatedly. + uint8_t out_array[MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN]; + while (num_cvs > 2) { + num_cvs = compress_parents_parallel(cv_array, num_cvs, key, flags, out_array); + for (size_t i = 0; i < num_cvs * BLAKE3_OUT_LEN; i++) { + cv_array[i] = out_array[i]; + } + } + for (size_t i = 0; i < 2 * BLAKE3_OUT_LEN; i++) { + out[i] = cv_array[i]; + } +} + +INLINE void hasher_init_base(blake3_hasher* self, const uint32_t key[8], uint8_t flags) +{ + for (size_t i = 0; i < 8; i++) { + self->key[i] = key[i]; + } + + chunk_state_init(&self->chunk, key, flags); + self->cv_stack_len = 0; +} + +void blake3_hasher_init(blake3_hasher* self) +{ + hasher_init_base(self, IV, 0); +} + +void blake3_hasher_init_keyed(blake3_hasher* self, const uint8_t key[BLAKE3_KEY_LEN]) +{ + uint32_t key_words[8]; + load_key_words(key, key_words); + hasher_init_base(self, key_words, KEYED_HASH); +} + +void blake3_hasher_init_derive_key_raw(blake3_hasher* self, const void* context, size_t context_len) +{ + blake3_hasher context_hasher; + hasher_init_base(&context_hasher, IV, DERIVE_KEY_CONTEXT); + blake3_hasher_update(&context_hasher, context, context_len); + uint8_t context_key[BLAKE3_KEY_LEN]; + blake3_hasher_finalize(&context_hasher, context_key, BLAKE3_KEY_LEN); + uint32_t context_key_words[8]; + load_key_words(context_key, context_key_words); + hasher_init_base(self, context_key_words, DERIVE_KEY_MATERIAL); +} + +void blake3_hasher_init_derive_key(blake3_hasher* self, const char* context) +{ + blake3_hasher_init_derive_key_raw(self, context, strlen(context)); +} + +// As described in hasher_push_cv() below, we do "lazy merging", delaying +// merges until right before the next CV is about to be added. This is +// different from the reference implementation. Another difference is that we +// aren't always merging 1 chunk at a time. Instead, each CV might represent +// any power-of-two number of chunks, as long as the smaller-above-larger stack +// order is maintained. Instead of the "count the trailing 0-bits" algorithm +// described in the spec, we use a "count the total number of 1-bits" variant +// that doesn't require us to retain the subtree size of the CV on top of the +// stack. The principle is the same: each CV that should remain in the stack is +// represented by a 1-bit in the total number of chunks (or bytes) so far. +INLINE void hasher_merge_cv_stack(blake3_hasher* self, uint64_t total_len) +{ + size_t post_merge_stack_len = (size_t)popcnt(total_len); + while (self->cv_stack_len > post_merge_stack_len) { + uint8_t* parent_node = &self->cv_stack[(self->cv_stack_len - 2) * BLAKE3_OUT_LEN]; + output_t output = parent_output(parent_node, self->key, self->chunk.flags); + output_chaining_value(&output, parent_node); + self->cv_stack_len = static_cast(self->cv_stack_len - 1); + } +} + +// In reference_impl.rs, we merge the new CV with existing CVs from the stack +// before pushing it. We can do that because we know more input is coming, so +// we know none of the merges are root. +// +// This setting is different. We want to feed as much input as possible to +// compress_subtree_wide(), without setting aside anything for the chunk_state. +// If the user gives us 64 KiB, we want to parallelize over all 64 KiB at once +// as a single subtree, if at all possible. +// +// This leads to two problems: +// 1) This 64 KiB input might be the only call that ever gets made to update. +// In this case, the root node of the 64 KiB subtree would be the root node +// of the whole tree, and it would need to be ROOT finalized. We can't +// compress it until we know. +// 2) This 64 KiB input might complete a larger tree, whose root node is +// similarly going to be the the root of the whole tree. For example, maybe +// we have 196 KiB (that is, 128 + 64) hashed so far. We can't compress the +// node at the root of the 256 KiB subtree until we know how to finalize it. +// +// The second problem is solved with "lazy merging". That is, when we're about +// to add a CV to the stack, we don't merge it with anything first, as the +// reference impl does. Instead we do merges using the *previous* CV that was +// added, which is sitting on top of the stack, and we put the new CV +// (unmerged) on top of the stack afterwards. This guarantees that we neve +// merge the root node until finalize(). +// +// Solving the first problem requires an additional tool, +// compress_subtree_to_parent_node(). That function always returns the top +// *two* chaining values of the subtree it's compressing. We then do lazy +// merging with each of them separately, so that the second CV will always +// remain unmerged. (That also helps us support extendable output when we're +// hashing an input all-at-once.) +INLINE void hasher_push_cv(blake3_hasher* self, uint8_t new_cv[BLAKE3_OUT_LEN], uint64_t chunk_counter) +{ + hasher_merge_cv_stack(self, chunk_counter); + for (int i = 0; i < BLAKE3_OUT_LEN; i++) { + self->cv_stack[self->cv_stack_len * BLAKE3_OUT_LEN + i] = new_cv[i]; + } + + self->cv_stack_len = static_cast(self->cv_stack_len + 1); +} + +void blake3_hasher_update(blake3_hasher* self, const void* input, size_t input_len) +{ + // Explicitly checking for zero avoids causing UB by passing a null pointe + // to memcpy. This comes up in practice with things like: + // std::vector v; + // blake3_hasher_update(&hasher, v.data(), v.size()); + if (input_len == 0) { + return; + } + + const uint8_t* input_bytes = (const uint8_t*)input; + + // If we have some partial chunk bytes in the internal chunk_state, we need + // to finish that chunk first. + if (chunk_state_len(&self->chunk) > 0) { + size_t take = BLAKE3_CHUNK_LEN - chunk_state_len(&self->chunk); + if (take > input_len) { + take = input_len; + } + chunk_state_update(&self->chunk, input_bytes, take); + input_bytes += take; + input_len -= take; + // If we've filled the current chunk and there's more coming, finalize this + // chunk and proceed. In this case we know it's not the root. + if (input_len > 0) { + output_t output = chunk_state_output(&self->chunk); + uint8_t chunk_cv[32]; + output_chaining_value(&output, chunk_cv); + hasher_push_cv(self, chunk_cv, self->chunk.chunk_counter); + chunk_state_reset(&self->chunk, self->key, self->chunk.chunk_counter + 1); + } else { + return; + } + } + + // Now the chunk_state is clear, and we have more input. If there's more than + // a single chunk (so, definitely not the root chunk), hash the largest whole + // subtree we can, with the full benefits of SIMD (and maybe in the future, + // multi-threading) parallelism. Two restrictions: + // - The subtree has to be a power-of-2 number of chunks. Only subtrees along + // the right edge can be incomplete, and we don't know where the right edge + // is going to be until we get to finalize(). + // - The subtree must evenly divide the total number of chunks up until this + // point (if total is not 0). If the current incomplete subtree is only + // waiting for 1 more chunk, we can't hash a subtree of 4 chunks. We have + // to complete the current subtree first. + // Because we might need to break up the input to form powers of 2, or to + // evenly divide what we already have, this part runs in a loop. + while (input_len > BLAKE3_CHUNK_LEN) { + size_t subtree_len = round_down_to_power_of_2(input_len); + uint64_t count_so_far = self->chunk.chunk_counter * BLAKE3_CHUNK_LEN; + // Shrink the subtree_len until it evenly divides the count so far. We know + // that subtree_len itself is a power of 2, so we can use a bitmasking + // trick instead of an actual remainder operation. (Note that if the calle + // consistently passes power-of-2 inputs of the same size, as is hopefully + // typical, this loop condition will always fail, and subtree_len will + // always be the full length of the input.) + // + // An aside: We don't have to shrink subtree_len quite this much. Fo + // example, if count_so_far is 1, we could pass 2 chunks to + // compress_subtree_to_parent_node. Since we'll get 2 CVs back, we'll still + // get the right answer in the end, and we might get to use 2-way SIMD + // parallelism. The problem with this optimization, is that it gets us + // stuck always hashing 2 chunks. The total number of chunks will remain + // odd, and we'll never graduate to higher degrees of parallelism. See + // https://github.com/BLAKE3-team/BLAKE3/issues/69. + while ((((uint64_t)(subtree_len - 1)) & count_so_far) != 0) { + subtree_len /= 2; + } + // The shrunken subtree_len might now be 1 chunk long. If so, hash that one + // chunk by itself. Otherwise, compress the subtree into a pair of CVs. + uint64_t subtree_chunks = subtree_len / BLAKE3_CHUNK_LEN; + if (subtree_len <= BLAKE3_CHUNK_LEN) { + blake3_chunk_state chunk_state; + chunk_state_init(&chunk_state, self->key, self->chunk.flags); + chunk_state.chunk_counter = self->chunk.chunk_counter; + chunk_state_update(&chunk_state, input_bytes, subtree_len); + output_t output = chunk_state_output(&chunk_state); + uint8_t cv[BLAKE3_OUT_LEN]; + output_chaining_value(&output, cv); + hasher_push_cv(self, cv, chunk_state.chunk_counter); + } else { + // This is the high-performance happy path, though getting here depends + // on the caller giving us a long enough input. + uint8_t cv_pair[2 * BLAKE3_OUT_LEN]; + compress_subtree_to_parent_node( + input_bytes, subtree_len, self->key, self->chunk.chunk_counter, self->chunk.flags, cv_pair); + hasher_push_cv(self, cv_pair, self->chunk.chunk_counter); + hasher_push_cv(self, &cv_pair[BLAKE3_OUT_LEN], self->chunk.chunk_counter + (subtree_chunks / 2)); + } + self->chunk.chunk_counter += subtree_chunks; + input_bytes += subtree_len; + input_len -= subtree_len; + } + + // If there's any remaining input less than a full chunk, add it to the chunk + // state. In that case, also do a final merge loop to make sure the subtree + // stack doesn't contain any unmerged pairs. The remaining input means we + // know these merges are non-root. This merge loop isn't strictly necessary + // here, because hasher_push_chunk_cv already does its own merge loop, but it + // simplifies blake3_hasher_finalize below. + if (input_len > 0) { + chunk_state_update(&self->chunk, input_bytes, input_len); + hasher_merge_cv_stack(self, self->chunk.chunk_counter); + } +} + +void blake3_hasher_finalize(const blake3_hasher* self, uint8_t* out, size_t out_len) +{ + blake3_hasher_finalize_seek(self, 0, out, out_len); +} + +void blake3_hasher_finalize_seek(const blake3_hasher* self, uint64_t seek, uint8_t* out, size_t out_len) +{ + // Explicitly checking for zero avoids causing UB by passing a null pointe + // to memcpy. This comes up in practice with things like: + // std::vector v; + // blake3_hasher_finalize(&hasher, v.data(), v.size()); + if (out_len == 0) { + return; + } + + // If the subtree stack is empty, then the current chunk is the root. + if (self->cv_stack_len == 0) { + output_t output = chunk_state_output(&self->chunk); + output_root_bytes(&output, seek, out, out_len); + return; + } + // If there are any bytes in the chunk state, finalize that chunk and do a + // roll-up merge between that chunk hash and every subtree in the stack. In + // this case, the extra merge loop at the end of blake3_hasher_update + // guarantees that none of the subtrees in the stack need to be merged with + // each other first. Otherwise, if there are no bytes in the chunk state, + // then the top of the stack is a chunk hash, and we start the merge from + // that. + output_t output; + size_t cvs_remaining; + if (chunk_state_len(&self->chunk) > 0) { + cvs_remaining = self->cv_stack_len; + output = chunk_state_output(&self->chunk); + } else { + // There are always at least 2 CVs in the stack in this case. + cvs_remaining = static_cast(self->cv_stack_len - 2); + output = parent_output(&self->cv_stack[cvs_remaining * 32], self->key, self->chunk.flags); + } + while (cvs_remaining > 0) { + cvs_remaining -= 1; + uint8_t parent_block[BLAKE3_BLOCK_LEN]; + for (size_t i = 0; i < 32; i++) { + parent_block[i] = self->cv_stack[cvs_remaining * 32 + i]; + } + + output_chaining_value(&output, &parent_block[32]); + output = parent_output(parent_block, self->key, self->chunk.flags); + } + output_root_bytes(&output, seek, out, out_len); +} + +void g(uint32_t* state, size_t a, size_t b, size_t c, size_t d, uint32_t x, uint32_t y) +{ + state[a] = state[a] + state[b] + x; + state[d] = rotr32(state[d] ^ state[a], 16); + state[c] = state[c] + state[d]; + state[b] = rotr32(state[b] ^ state[c], 12); + state[a] = state[a] + state[b] + y; + state[d] = rotr32(state[d] ^ state[a], 8); + state[c] = state[c] + state[d]; + state[b] = rotr32(state[b] ^ state[c], 7); +} + +void round_fn(uint32_t state[16], const uint32_t* msg, size_t round) +{ + // Select the message schedule based on the round. + const uint8_t* schedule = MSG_SCHEDULE[round]; + + // Mix the columns. + g(state, 0, 4, 8, 12, msg[schedule[0]], msg[schedule[1]]); + g(state, 1, 5, 9, 13, msg[schedule[2]], msg[schedule[3]]); + g(state, 2, 6, 10, 14, msg[schedule[4]], msg[schedule[5]]); + g(state, 3, 7, 11, 15, msg[schedule[6]], msg[schedule[7]]); + + // Mix the rows. + g(state, 0, 5, 10, 15, msg[schedule[8]], msg[schedule[9]]); + g(state, 1, 6, 11, 12, msg[schedule[10]], msg[schedule[11]]); + g(state, 2, 7, 8, 13, msg[schedule[12]], msg[schedule[13]]); + g(state, 3, 4, 9, 14, msg[schedule[14]], msg[schedule[15]]); +} + +void compress_pre(uint32_t state[16], + const uint32_t cv[8], + const uint8_t block[BLAKE3_BLOCK_LEN], + uint8_t block_len, + uint64_t counter, + uint8_t flags) +{ + uint32_t block_words[16]; + block_words[0] = load32(block + 4 * 0); + block_words[1] = load32(block + 4 * 1); + block_words[2] = load32(block + 4 * 2); + block_words[3] = load32(block + 4 * 3); + block_words[4] = load32(block + 4 * 4); + block_words[5] = load32(block + 4 * 5); + block_words[6] = load32(block + 4 * 6); + block_words[7] = load32(block + 4 * 7); + block_words[8] = load32(block + 4 * 8); + block_words[9] = load32(block + 4 * 9); + block_words[10] = load32(block + 4 * 10); + block_words[11] = load32(block + 4 * 11); + block_words[12] = load32(block + 4 * 12); + block_words[13] = load32(block + 4 * 13); + block_words[14] = load32(block + 4 * 14); + block_words[15] = load32(block + 4 * 15); + + state[0] = cv[0]; + state[1] = cv[1]; + state[2] = cv[2]; + state[3] = cv[3]; + state[4] = cv[4]; + state[5] = cv[5]; + state[6] = cv[6]; + state[7] = cv[7]; + state[8] = IV[0]; + state[9] = IV[1]; + state[10] = IV[2]; + state[11] = IV[3]; + state[12] = counter_low(counter); + state[13] = counter_high(counter); + state[14] = (uint32_t)block_len; + state[15] = (uint32_t)flags; + + round_fn(state, &block_words[0], 0); + round_fn(state, &block_words[0], 1); + round_fn(state, &block_words[0], 2); + round_fn(state, &block_words[0], 3); + round_fn(state, &block_words[0], 4); + round_fn(state, &block_words[0], 5); + round_fn(state, &block_words[0], 6); +} + +void blake3_compress_in_place( + uint32_t cv[8], const uint8_t block[BLAKE3_BLOCK_LEN], uint8_t block_len, uint64_t counter, uint8_t flags) +{ + uint32_t state[16]; + compress_pre(state, cv, block, block_len, counter, flags); + cv[0] = state[0] ^ state[8]; + cv[1] = state[1] ^ state[9]; + cv[2] = state[2] ^ state[10]; + cv[3] = state[3] ^ state[11]; + cv[4] = state[4] ^ state[12]; + cv[5] = state[5] ^ state[13]; + cv[6] = state[6] ^ state[14]; + cv[7] = state[7] ^ state[15]; +} + +void blake3_compress_xof(const uint32_t cv[8], + const uint8_t block[BLAKE3_BLOCK_LEN], + uint8_t block_len, + uint64_t counter, + uint8_t flags, + uint8_t out[64]) +{ + uint32_t state[16]; + compress_pre(state, cv, block, block_len, counter, flags); + + store32(&out[0 * 4], state[0] ^ state[8]); + store32(&out[1 * 4], state[1] ^ state[9]); + store32(&out[2 * 4], state[2] ^ state[10]); + store32(&out[3 * 4], state[3] ^ state[11]); + store32(&out[4 * 4], state[4] ^ state[12]); + store32(&out[5 * 4], state[5] ^ state[13]); + store32(&out[6 * 4], state[6] ^ state[14]); + store32(&out[7 * 4], state[7] ^ state[15]); + store32(&out[8 * 4], state[8] ^ cv[0]); + store32(&out[9 * 4], state[9] ^ cv[1]); + store32(&out[10 * 4], state[10] ^ cv[2]); + store32(&out[11 * 4], state[11] ^ cv[3]); + store32(&out[12 * 4], state[12] ^ cv[4]); + store32(&out[13 * 4], state[13] ^ cv[5]); + store32(&out[14 * 4], state[14] ^ cv[6]); + store32(&out[15 * 4], state[15] ^ cv[7]); +} + +void blake3s_hash_one(const uint8_t* input, + size_t blocks, + const uint32_t key[8], + uint64_t counter, + uint8_t flags, + uint8_t flags_start, + uint8_t flags_end, + uint8_t out[BLAKE3_OUT_LEN]) +{ + uint32_t cv[8]; + for (size_t i = 0; i < 8; i++) { + cv[i] = key[i]; + } + + uint8_t block_flags = flags | flags_start; + while (blocks > 0) { + if (blocks == 1) { + block_flags |= flags_end; + } + blake3_compress_in_place(cv, input, BLAKE3_BLOCK_LEN, counter, block_flags); + input = &input[BLAKE3_BLOCK_LEN]; + blocks -= 1; + block_flags = flags; + } + store_cv_words(out, cv); +} + +void blake3_hash_many(const uint8_t* const* inputs, + size_t num_inputs, + size_t blocks, + const uint32_t key[8], + uint64_t counter, + bool increment_counter, + uint8_t flags, + uint8_t flags_start, + uint8_t flags_end, + uint8_t* out) +{ + while (num_inputs > 0) { + blake3s_hash_one(inputs[0], blocks, key, counter, flags, flags_start, flags_end, out); + if (increment_counter) { + counter += 1; + } + inputs += 1; + num_inputs -= 1; + out = &out[BLAKE3_OUT_LEN]; + } +} + +std::vector blake3s(std::vector const& input, + const mode mode_id, + const uint8_t key[BLAKE3_KEY_LEN], + const char* context) +{ + // Initialize the hasher. + blake3_hasher hasher; + blake3_hasher_init(&hasher); + + switch (mode_id) { + case HASH_MODE: + blake3_hasher_init(&hasher); + break; + case KEYED_HASH_MODE: + blake3_hasher_init_keyed(&hasher, key); + break; + case DERIVE_KEY_MODE: + blake3_hasher_init_derive_key(&hasher, context); + break; + default: + abort(); + } + + blake3_hasher_update(&hasher, (const uint8_t*)input.data(), input.size()); + + std::vector output(BLAKE3_OUT_LEN); + blake3_hasher_finalize(&hasher, &output[0], BLAKE3_OUT_LEN); + return output; +} + +} // namespace blake3_full diff --git a/cpp/src/aztec/crypto/blake3s_full/blake3s.hpp b/cpp/src/aztec/crypto/blake3s_full/blake3s.hpp new file mode 100644 index 0000000000..8f233c09b8 --- /dev/null +++ b/cpp/src/aztec/crypto/blake3s_full/blake3s.hpp @@ -0,0 +1,140 @@ +/* + BLAKE3 reference source code package - C implementations + + Intellectual property: + + The Rust code is copyright Jack O'Connor, 2019-2020. + The C code is copyright Samuel Neves and Jack O'Connor, 2019-2020. + The assembly code is copyright Samuel Neves, 2019-2020. + + This work is released into the public domain with CC0 1.0. Alternatively, it is licensed under the Apache + License 2.0. + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE3 hash function can be found at + https://github.com/BLAKE3-team/BLAKE3. +*/ + +#include +#include +#include + +namespace blake3_full { + +#define BLAKE3_VERSION_STRING "0.3.7" + +// internal flags +enum blake3_flags { + CHUNK_START = 1 << 0, + CHUNK_END = 1 << 1, + PARENT = 1 << 2, + ROOT = 1 << 3, + KEYED_HASH = 1 << 4, + DERIVE_KEY_CONTEXT = 1 << 5, + DERIVE_KEY_MATERIAL = 1 << 6, +}; + +// constants +enum blake3s_constant { + BLAKE3_KEY_LEN = 32, + BLAKE3_OUT_LEN = 32, + BLAKE3_BLOCK_LEN = 64, + BLAKE3_CHUNK_LEN = 1024, + BLAKE3_MAX_DEPTH = 54 +}; + +// modes +enum mode { HASH_MODE = 0, KEYED_HASH_MODE = 1, DERIVE_KEY_MODE = 2 }; + +static const uint32_t IV[8] = { 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL, + 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL }; + +static const uint8_t MSG_SCHEDULE[7][16] = { + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, { 2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8 }, + { 3, 4, 10, 12, 13, 2, 7, 14, 6, 5, 9, 0, 11, 15, 8, 1 }, { 10, 7, 12, 9, 14, 3, 13, 15, 4, 0, 11, 2, 5, 8, 1, 6 }, + { 12, 13, 9, 11, 15, 10, 14, 8, 7, 2, 5, 3, 0, 1, 6, 4 }, { 9, 14, 11, 5, 8, 12, 15, 1, 13, 3, 0, 10, 2, 6, 4, 7 }, + { 11, 15, 5, 0, 1, 9, 8, 6, 14, 10, 2, 12, 3, 4, 7, 13 }, +}; + +// This struct is a private implementation detail. It has to be here because +// it's part of blake3_hasher below. +typedef struct blake3_chunk_state__ { + uint32_t cv[8]; + uint64_t chunk_counter; + uint8_t buf[BLAKE3_BLOCK_LEN]; + uint8_t buf_len; + uint8_t blocks_compressed; + uint8_t flags; +} blake3_chunk_state; + +typedef struct blake3_hasher__ { + uint32_t key[8]; + blake3_chunk_state chunk; + uint8_t cv_stack_len; + // The stack size is MAX_DEPTH + 1 because we do lazy merging. For example, + // with 7 chunks, we have 3 entries in the stack. Adding an 8th chunk + // requires a 4th entry, rather than merging everything down to 1, because we + // don't know whether more input is coming. This is different from how the + // reference implementation does things. + uint8_t cv_stack[(BLAKE3_MAX_DEPTH + 1) * BLAKE3_OUT_LEN]; +} blake3_hasher; + +const char* blake3_version(void); +void blake3_hasher_init(blake3_hasher* self); +void blake3_hasher_init_keyed(blake3_hasher* self, const uint8_t key[BLAKE3_KEY_LEN]); + +void blake3_hasher_init_derive_key(blake3_hasher* self, const char* context); +void blake3_hasher_init_derive_key_raw(blake3_hasher* self, const void* context, size_t context_len); + +void blake3_hasher_update(blake3_hasher* self, const void* input, size_t input_len); +void blake3_hasher_finalize(const blake3_hasher* self, uint8_t* out, size_t out_len); +void blake3_hasher_finalize_seek(const blake3_hasher* self, uint64_t seek, uint8_t* out, size_t out_len); + +void g(uint32_t* state, size_t a, size_t b, size_t c, size_t d, uint32_t x, uint32_t y); +void round_fn(uint32_t state[16], const uint32_t* msg, size_t round); + +void compress_pre(uint32_t state[16], + const uint32_t cv[8], + const uint8_t block[BLAKE3_BLOCK_LEN], + uint8_t block_len, + uint64_t counter, + uint8_t flags); + +void blake3_compress_in_place( + uint32_t cv[8], const uint8_t block[BLAKE3_BLOCK_LEN], uint8_t block_len, uint64_t counter, uint8_t flags); + +void blake3_compress_xof(const uint32_t cv[8], + const uint8_t block[BLAKE3_BLOCK_LEN], + uint8_t block_len, + uint64_t counter, + uint8_t flags, + uint8_t out[64]); + +void blak3s_hash_one(const uint8_t* input, + size_t blocks, + const uint32_t key[8], + uint64_t counter, + uint8_t flags, + uint8_t flags_start, + uint8_t flags_end, + uint8_t out[BLAKE3_OUT_LEN]); + +void blake3_hash_many(const uint8_t* const* inputs, + size_t num_inputs, + size_t blocks, + const uint32_t key[8], + uint64_t counter, + bool increment_counter, + uint8_t flags, + uint8_t flags_start, + uint8_t flags_end, + uint8_t* out); + +std::vector blake3s(std::vector const& input, + const mode mode_id = HASH_MODE, + const uint8_t key[BLAKE3_KEY_LEN] = nullptr, + const char* context = nullptr); + +} // namespace blake3_full diff --git a/cpp/src/aztec/crypto/blake3s_full/blake3s.test.cpp b/cpp/src/aztec/crypto/blake3s_full/blake3s.test.cpp new file mode 100644 index 0000000000..63e6e3cdf7 --- /dev/null +++ b/cpp/src/aztec/crypto/blake3s_full/blake3s.test.cpp @@ -0,0 +1,896 @@ +#include "blake3s.hpp" + +#include + +#include +#include +#include + +struct test_vector { + std::string input; + std::vector output; +}; + +struct full_test_vector { + size_t input_len; + std::string hash; + std::string keyed_hash; + std::string derive_key; +}; + +struct blake3_test_vector { + size_t input_len; + std::vector hash; + std::vector keyed_hash; + std::vector derive_key; +}; + +/* + * The below test vector is taken from the Blake3 reference implementation. + * Note that we have converted the string formats from the source file into vector format for convenience. + * + * Link: https://github.com/BLAKE3-team/BLAKE3/blob/master/test_vectors/test_vectors.json + */ +blake3_test_vector full_test_vector[] = { + { 0, + { + 0xAF, 0x13, 0x49, 0xB9, 0xF5, 0xF9, 0xA1, 0xA6, 0xA0, 0x40, 0x4D, 0xEA, 0x36, 0xDC, 0xC9, 0x49, + 0x9B, 0xCB, 0x25, 0xC9, 0xAD, 0xC1, 0x12, 0xB7, 0xCC, 0x9A, 0x93, 0xCA, 0xE4, 0x1F, 0x32, 0x62, + }, + { + 0x92, 0xB2, 0xB7, 0x56, 0x04, 0xED, 0x3C, 0x76, 0x1F, 0x9D, 0x6F, 0x62, 0x39, 0x2C, 0x8A, 0x92, + 0x27, 0xAD, 0x0E, 0xA3, 0xF0, 0x95, 0x73, 0xE7, 0x83, 0xF1, 0x49, 0x8A, 0x4E, 0xD6, 0x0D, 0x26, + }, + { + 0x2C, 0xC3, 0x97, 0x83, 0xC2, 0x23, 0x15, 0x4F, 0xEA, 0x8D, 0xFB, 0x7C, 0x1B, 0x16, 0x60, 0xF2, + 0xAC, 0x2D, 0xCB, 0xD1, 0xC1, 0xDE, 0x82, 0x77, 0xB0, 0xB0, 0xDD, 0x39, 0xB7, 0xE5, 0x0D, 0x7D, + } }, + { 1, + { + 0x2D, 0x3A, 0xDE, 0xDF, 0xF1, 0x1B, 0x61, 0xF1, 0x4C, 0x88, 0x6E, 0x35, 0xAF, 0xA0, 0x36, 0x73, + 0x6D, 0xCD, 0x87, 0xA7, 0x4D, 0x27, 0xB5, 0xC1, 0x51, 0x02, 0x25, 0xD0, 0xF5, 0x92, 0xE2, 0x13, + }, + { + 0x6D, 0x78, 0x78, 0xDF, 0xFF, 0x2F, 0x48, 0x56, 0x35, 0xD3, 0x90, 0x13, 0x27, 0x8A, 0xE1, 0x4F, + 0x14, 0x54, 0xB8, 0xC0, 0xA3, 0xA2, 0xD3, 0x4B, 0xC1, 0xAB, 0x38, 0x22, 0x8A, 0x80, 0xC9, 0x5B, + }, + { + 0xB3, 0xE2, 0xE3, 0x40, 0xA1, 0x17, 0xA4, 0x99, 0xC6, 0xCF, 0x23, 0x98, 0xA1, 0x9E, 0xE0, 0xD2, + 0x9C, 0xCA, 0x2B, 0xB7, 0x40, 0x4C, 0x73, 0x06, 0x33, 0x82, 0x69, 0x3B, 0xF6, 0x6C, 0xB0, 0x6C, + } }, + { 2, + { + 0x7B, 0x70, 0x15, 0xBB, 0x92, 0xCF, 0x0B, 0x31, 0x80, 0x37, 0x70, 0x2A, 0x6C, 0xDD, 0x81, 0xDE, + 0xE4, 0x12, 0x24, 0xF7, 0x34, 0x68, 0x4C, 0x2C, 0x12, 0x2C, 0xD6, 0x35, 0x9C, 0xB1, 0xEE, 0x63, + }, + { + 0x53, 0x92, 0xDD, 0xAE, 0x0E, 0x0A, 0x69, 0xD5, 0xF4, 0x01, 0x60, 0x46, 0x2C, 0xBD, 0x9B, 0xD8, + 0x89, 0x37, 0x50, 0x82, 0xFF, 0x22, 0x4A, 0xC9, 0xC7, 0x58, 0x80, 0x2B, 0x7A, 0x6F, 0xD2, 0x0A, + }, + { + 0x1F, 0x16, 0x65, 0x65, 0xA7, 0xDF, 0x00, 0x98, 0xEE, 0x65, 0x92, 0x2D, 0x7F, 0xEA, 0x42, 0x5F, + 0xB1, 0x8B, 0x99, 0x43, 0xF1, 0x9D, 0x61, 0x61, 0xE2, 0xD1, 0x79, 0x39, 0x35, 0x61, 0x68, 0xE6, + } }, + { 3, + { + 0xE1, 0xBE, 0x4D, 0x7A, 0x8A, 0xB5, 0x56, 0x0A, 0xA4, 0x19, 0x9E, 0xEA, 0x33, 0x98, 0x49, 0xBA, + 0x8E, 0x29, 0x3D, 0x55, 0xCA, 0x0A, 0x81, 0x00, 0x67, 0x26, 0xD1, 0x84, 0x51, 0x9E, 0x64, 0x7F, + }, + { + 0x39, 0xE6, 0x7B, 0x76, 0xB5, 0xA0, 0x07, 0xD4, 0x92, 0x19, 0x69, 0x77, 0x9F, 0xE6, 0x66, 0xDA, + 0x67, 0xB5, 0x21, 0x3B, 0x09, 0x60, 0x84, 0xAB, 0x67, 0x47, 0x42, 0xF0, 0xD5, 0xEC, 0x62, 0xB9, + }, + { + 0x44, 0x0A, 0xBA, 0x35, 0xCB, 0x00, 0x6B, 0x61, 0xFC, 0x17, 0xC0, 0x52, 0x92, 0x55, 0xDE, 0x43, + 0x8E, 0xFC, 0x06, 0xA8, 0xC9, 0xEB, 0xF3, 0xF2, 0xDD, 0xAC, 0x3B, 0x5A, 0x86, 0x70, 0x57, 0x97, + } }, + { 4, + { + 0xF3, 0x0F, 0x5A, 0xB2, 0x8F, 0xE0, 0x47, 0x90, 0x40, 0x37, 0xF7, 0x7B, 0x6D, 0xA4, 0xFE, 0xA1, + 0xE2, 0x72, 0x41, 0xC5, 0xD1, 0x32, 0x63, 0x8D, 0x8B, 0xED, 0xCE, 0x9D, 0x40, 0x49, 0x4F, 0x32, + }, + { + 0x76, 0x71, 0xDD, 0xE5, 0x90, 0xC9, 0x5D, 0x5A, 0xC9, 0x61, 0x66, 0x51, 0xFF, 0x5A, 0xA0, 0xA2, + 0x7B, 0xEE, 0x59, 0x13, 0xA3, 0x48, 0xE0, 0x53, 0xB8, 0xAA, 0x91, 0x08, 0x91, 0x7F, 0xE0, 0x70, + }, + { + 0xF4, 0x60, 0x85, 0xC8, 0x19, 0x0D, 0x69, 0x02, 0x23, 0x69, 0xCE, 0x1A, 0x18, 0x88, 0x0E, 0x9B, + 0x36, 0x9C, 0x13, 0x5E, 0xB9, 0x3F, 0x3C, 0x63, 0x55, 0x0D, 0x3E, 0x76, 0x30, 0xE9, 0x10, 0x60, + } }, + { 5, + { + 0xB4, 0x0B, 0x44, 0xDF, 0xD9, 0x7E, 0x7A, 0x84, 0xA9, 0x96, 0xA9, 0x1A, 0xF8, 0xB8, 0x51, 0x88, + 0xC6, 0x6C, 0x12, 0x69, 0x40, 0xBA, 0x7A, 0xAD, 0x2E, 0x7A, 0xE6, 0xB3, 0x85, 0x40, 0x2A, 0xA2, + }, + { + 0x73, 0xAC, 0x69, 0xEE, 0xCF, 0x28, 0x68, 0x94, 0xD8, 0x10, 0x20, 0x18, 0xA6, 0xFC, 0x72, 0x9F, + 0x4B, 0x1F, 0x42, 0x47, 0xD3, 0x70, 0x3F, 0x69, 0xBD, 0xC6, 0xA5, 0xFE, 0x3E, 0x0C, 0x84, 0x61, + }, + { + 0x1F, 0x24, 0xED, 0xA6, 0x9D, 0xBC, 0xB7, 0x52, 0x84, 0x7E, 0xC3, 0xEB, 0xB5, 0xDD, 0x42, 0x83, + 0x6D, 0x86, 0xE5, 0x85, 0x00, 0xC7, 0xC9, 0x8D, 0x90, 0x6E, 0xCD, 0x82, 0xED, 0x9A, 0xE4, 0x7F, + } }, + { 6, + { + 0x06, 0xC4, 0xE8, 0xFF, 0xB6, 0x87, 0x2F, 0xAD, 0x96, 0xF9, 0xAA, 0xCA, 0x5E, 0xEE, 0x15, 0x53, + 0xEB, 0x62, 0xAE, 0xD0, 0xAD, 0x71, 0x98, 0xCE, 0xF4, 0x2E, 0x87, 0xF6, 0xA6, 0x16, 0xC8, 0x44, + }, + { + 0x82, 0xD3, 0x19, 0x9D, 0x00, 0x13, 0x03, 0x56, 0x82, 0xCC, 0x7F, 0x2A, 0x39, 0x9D, 0x4C, 0x21, + 0x25, 0x44, 0x37, 0x6A, 0x83, 0x9A, 0xA8, 0x63, 0xA0, 0xF4, 0xC9, 0x12, 0x20, 0xCA, 0x7A, 0x6D, + }, + { + 0xBE, 0x96, 0xB3, 0x0B, 0x37, 0x91, 0x9F, 0xE4, 0x37, 0x9D, 0xFB, 0xE7, 0x52, 0xAE, 0x77, 0xB4, + 0xF7, 0xE2, 0xAB, 0x92, 0xF7, 0xFF, 0x27, 0x43, 0x5F, 0x76, 0xF2, 0xF0, 0x65, 0xF6, 0xA5, 0xF4, + } }, + { 7, + { + 0x3F, 0x87, 0x70, 0xF3, 0x87, 0xFA, 0xAD, 0x08, 0xFA, 0xA9, 0xD8, 0x41, 0x4E, 0x9F, 0x44, 0x9A, + 0xC6, 0x8E, 0x6F, 0xF0, 0x41, 0x7F, 0x67, 0x3F, 0x60, 0x2A, 0x64, 0x6A, 0x89, 0x14, 0x19, 0xFE, + }, + { + 0xAF, 0x0A, 0x7E, 0xC3, 0x82, 0xAE, 0xDC, 0x0C, 0xFD, 0x62, 0x6E, 0x49, 0xE7, 0x62, 0x8B, 0xC7, + 0xA3, 0x53, 0xA4, 0xCB, 0x10, 0x88, 0x55, 0x54, 0x1A, 0x56, 0x51, 0xBF, 0x64, 0xFB, 0xB2, 0x8A, + }, + { + 0xDC, 0x3B, 0x64, 0x85, 0xF9, 0xD9, 0x49, 0x35, 0x32, 0x94, 0x42, 0x91, 0x6B, 0x0D, 0x05, 0x96, + 0x85, 0xBA, 0x81, 0x5A, 0x1F, 0xA2, 0xA1, 0x41, 0x07, 0x21, 0x74, 0x53, 0xA7, 0xFC, 0x9F, 0x0E, + } }, + { 8, + { + 0x23, 0x51, 0x20, 0x7D, 0x04, 0xFC, 0x16, 0xAD, 0xE4, 0x3C, 0xCA, 0xB0, 0x86, 0x00, 0x93, 0x9C, + 0x7C, 0x1F, 0xA7, 0x0A, 0x5C, 0x0A, 0xAC, 0xA7, 0x60, 0x63, 0xD0, 0x4C, 0x32, 0x28, 0xEA, 0xEB, + }, + { + 0xBE, 0x2F, 0x54, 0x95, 0xC6, 0x1C, 0xBA, 0x1B, 0xB3, 0x48, 0xA3, 0x49, 0x48, 0xC0, 0x04, 0x04, + 0x5E, 0x3B, 0xD4, 0xDA, 0xE8, 0xF0, 0xFE, 0x82, 0xBF, 0x44, 0xD0, 0xDA, 0x24, 0x5A, 0x06, 0x00, + }, + { + 0x2B, 0x16, 0x69, 0x78, 0xCE, 0xF1, 0x4D, 0x9D, 0x43, 0x80, 0x46, 0xC7, 0x20, 0x51, 0x9D, 0x8B, + 0x1C, 0xAD, 0x70, 0x7E, 0x19, 0x97, 0x46, 0xF1, 0x56, 0x2D, 0x0C, 0x87, 0xFB, 0xD3, 0x29, 0x40, + } }, + { 63, + { + 0xE9, 0xBC, 0x37, 0xA5, 0x94, 0xDA, 0xAD, 0x83, 0xBE, 0x94, 0x70, 0xDF, 0x7F, 0x7B, 0x37, 0x98, + 0x29, 0x7C, 0x3D, 0x83, 0x4C, 0xE8, 0x0B, 0xA8, 0x5D, 0x6E, 0x20, 0x76, 0x27, 0xB7, 0xDB, 0x7B, + }, + { + 0xBB, 0x1E, 0xB5, 0xD4, 0xAF, 0xA7, 0x93, 0xC1, 0xEB, 0xDD, 0x9F, 0xB0, 0x8D, 0xEF, 0x6C, 0x36, + 0xD1, 0x00, 0x96, 0x98, 0x6A, 0xE0, 0xCF, 0xE1, 0x48, 0xCD, 0x10, 0x11, 0x70, 0xCE, 0x37, 0xAE, + }, + { + 0xB6, 0x45, 0x1E, 0x30, 0xB9, 0x53, 0xC2, 0x06, 0xE3, 0x46, 0x44, 0xC6, 0x80, 0x37, 0x24, 0xE9, + 0xD2, 0x72, 0x5E, 0x08, 0x93, 0x03, 0x9C, 0xFC, 0x49, 0x58, 0x4F, 0x99, 0x1F, 0x45, 0x1A, 0xF3, + } }, + { 64, + { + 0x4E, 0xED, 0x71, 0x41, 0xEA, 0x4A, 0x5C, 0xD4, 0xB7, 0x88, 0x60, 0x6B, 0xD2, 0x3F, 0x46, 0xE2, + 0x12, 0xAF, 0x9C, 0xAC, 0xEB, 0xAC, 0xDC, 0x7D, 0x1F, 0x4C, 0x6D, 0xC7, 0xF2, 0x51, 0x1B, 0x98, + }, + { + 0xBA, 0x8C, 0xED, 0x36, 0xF3, 0x27, 0x70, 0x0D, 0x21, 0x3F, 0x12, 0x0B, 0x1A, 0x20, 0x7A, 0x3B, + 0x8C, 0x04, 0x33, 0x05, 0x28, 0x58, 0x6F, 0x41, 0x4D, 0x09, 0xF2, 0xF7, 0xD9, 0xCC, 0xB7, 0xE6, + }, + { + 0xA5, 0xC4, 0xA7, 0x05, 0x3F, 0xA8, 0x6B, 0x64, 0x74, 0x6D, 0x4B, 0xB6, 0x88, 0xD0, 0x6A, 0xD1, + 0xF0, 0x2A, 0x18, 0xFC, 0xE9, 0xAF, 0xD3, 0xE8, 0x18, 0xFE, 0xFA, 0xA7, 0x12, 0x6B, 0xF7, 0x3E, + } }, + { 65, + { + 0xDE, 0x1E, 0x5F, 0xA0, 0xBE, 0x70, 0xDF, 0x6D, 0x2B, 0xE8, 0xFF, 0xFD, 0x0E, 0x99, 0xCE, 0xAA, + 0x8E, 0xB6, 0xE8, 0xC9, 0x3A, 0x63, 0xF2, 0xD8, 0xD1, 0xC3, 0x0E, 0xCB, 0x6B, 0x26, 0x3D, 0xEE, + }, + { + 0xC0, 0xA4, 0xED, 0xEF, 0xA2, 0xD2, 0xAC, 0xCB, 0x92, 0x77, 0xC3, 0x71, 0xAC, 0x12, 0xFC, 0xDB, + 0xB5, 0x29, 0x88, 0xA8, 0x6E, 0xDC, 0x54, 0xF0, 0x71, 0x6E, 0x15, 0x91, 0xB4, 0x32, 0x6E, 0x72, + }, + { + 0x51, 0xFD, 0x05, 0xC3, 0xC1, 0xCF, 0xBC, 0x8E, 0xD6, 0x7D, 0x13, 0x9A, 0xD7, 0x6F, 0x5C, 0xF8, + 0x23, 0x6C, 0xD2, 0xAC, 0xD2, 0x66, 0x27, 0xA3, 0x0C, 0x10, 0x4D, 0xFD, 0x9D, 0x3F, 0xF8, 0xA8, + } }, + { 127, + { + 0xD8, 0x12, 0x93, 0xFD, 0xA8, 0x63, 0xF0, 0x08, 0xC0, 0x9E, 0x92, 0xFC, 0x38, 0x2A, 0x81, 0xF5, + 0xA0, 0xB4, 0xA1, 0x25, 0x1C, 0xBA, 0x16, 0x34, 0x01, 0x6A, 0x0F, 0x86, 0xA6, 0xBD, 0x64, 0x0D, + }, + { + 0xC6, 0x42, 0x00, 0xAE, 0x7D, 0xFA, 0xF3, 0x55, 0x77, 0xAC, 0x5A, 0x95, 0x21, 0xC4, 0x78, 0x63, + 0xFB, 0x71, 0x51, 0x4A, 0x3B, 0xCA, 0xD1, 0x88, 0x19, 0x21, 0x8B, 0x81, 0x8D, 0xE8, 0x58, 0x18, + }, + { + 0xC9, 0x1C, 0x09, 0x0C, 0xEE, 0xE3, 0xA3, 0xAC, 0x81, 0x90, 0x2D, 0xA3, 0x18, 0x38, 0x01, 0x26, + 0x25, 0xBB, 0xCD, 0x73, 0xFC, 0xB9, 0x2E, 0x7D, 0x7E, 0x56, 0xF7, 0x8D, 0xEB, 0xA4, 0xF0, 0xC3, + } }, + { 128, + { + 0xF1, 0x7E, 0x57, 0x05, 0x64, 0xB2, 0x65, 0x78, 0xC3, 0x3B, 0xB7, 0xF4, 0x46, 0x43, 0xF5, 0x39, + 0x62, 0x4B, 0x05, 0xDF, 0x1A, 0x76, 0xC8, 0x1F, 0x30, 0xAC, 0xD5, 0x48, 0xC4, 0x4B, 0x45, 0xEF, + }, + { + 0xB0, 0x4F, 0xE1, 0x55, 0x77, 0x45, 0x72, 0x67, 0xFF, 0x3B, 0x6F, 0x3C, 0x94, 0x7D, 0x93, 0xBE, + 0x58, 0x1E, 0x7E, 0x3A, 0x4B, 0x01, 0x86, 0x79, 0x12, 0x5E, 0xAF, 0x86, 0xF6, 0xA6, 0x28, 0xEC, + }, + { + 0x81, 0x72, 0x0F, 0x34, 0x45, 0x2F, 0x58, 0xA0, 0x12, 0x0A, 0x58, 0xB6, 0xB4, 0x60, 0x83, 0x84, + 0xB5, 0xC5, 0x1D, 0x11, 0xF3, 0x9C, 0xE9, 0x71, 0x61, 0xA0, 0xC0, 0xE4, 0x42, 0xCA, 0x02, 0x25, + } }, + { 129, + { + 0x68, 0x3A, 0xAA, 0xE9, 0xF3, 0xC5, 0xBA, 0x37, 0xEA, 0xAF, 0x07, 0x2A, 0xED, 0x0F, 0x9E, 0x30, + 0xBA, 0xC0, 0x86, 0x51, 0x37, 0xBA, 0xE6, 0x8B, 0x1F, 0xDE, 0x4C, 0xA2, 0xAE, 0xBD, 0xCB, 0x12, + }, + { + 0xD4, 0xA6, 0x4D, 0xAE, 0x6C, 0xDC, 0xCB, 0xAC, 0x1E, 0x52, 0x87, 0xF5, 0x4F, 0x17, 0xC5, 0xF9, + 0x85, 0x10, 0x54, 0x57, 0xC1, 0xA2, 0xEC, 0x18, 0x78, 0xEB, 0xD4, 0xB5, 0x7E, 0x20, 0xD3, 0x8F, + }, + { + 0x93, 0x8D, 0x2D, 0x44, 0x35, 0xBE, 0x30, 0xEA, 0xFD, 0xBB, 0x2B, 0x70, 0x31, 0xF7, 0x85, 0x7C, + 0x98, 0xB0, 0x48, 0x81, 0x22, 0x73, 0x91, 0xDC, 0x40, 0xDB, 0x3C, 0x7B, 0x21, 0xF4, 0x1F, 0xC1, + } }, + { 1023, + { + 0x10, 0x10, 0x89, 0x70, 0xEE, 0xDA, 0x3E, 0xB9, 0x32, 0xBA, 0xAC, 0x14, 0x28, 0xC7, 0xA2, 0x16, + 0x3B, 0x0E, 0x92, 0x4C, 0x9A, 0x9E, 0x25, 0xB3, 0x5B, 0xBA, 0x72, 0xB2, 0x8F, 0x70, 0xBD, 0x11, + }, + { + 0xC9, 0x51, 0xEC, 0xDF, 0x03, 0x28, 0x8D, 0x0F, 0xCC, 0x96, 0xEE, 0x34, 0x13, 0x56, 0x3D, 0x8A, + 0x6D, 0x35, 0x89, 0x54, 0x7F, 0x2C, 0x2F, 0xB3, 0x6D, 0x97, 0x86, 0x47, 0x0F, 0x1B, 0x9D, 0x6E, + }, + { + 0x74, 0xA1, 0x6C, 0x1C, 0x3D, 0x44, 0x36, 0x8A, 0x86, 0xE1, 0xCA, 0x6D, 0xF6, 0x4B, 0xE6, 0xA2, + 0xF6, 0x4C, 0xCE, 0x8F, 0x09, 0x22, 0x07, 0x87, 0x45, 0x07, 0x22, 0xD8, 0x57, 0x25, 0xDE, 0xA5, + } }, + { 1024, + { + 0x42, 0x21, 0x47, 0x39, 0xF0, 0x95, 0xA4, 0x06, 0xF3, 0xFC, 0x83, 0xDE, 0xB8, 0x89, 0x74, 0x4A, + 0xC0, 0x0D, 0xF8, 0x31, 0xC1, 0x0D, 0xAA, 0x55, 0x18, 0x9B, 0x5D, 0x12, 0x1C, 0x85, 0x5A, 0xF7, + }, + { + 0x75, 0xC4, 0x6F, 0x6F, 0x3D, 0x9E, 0xB4, 0xF5, 0x5E, 0xCA, 0xAE, 0xE4, 0x80, 0xDB, 0x73, 0x2E, + 0x6C, 0x21, 0x05, 0x54, 0x6F, 0x1E, 0x67, 0x50, 0x03, 0x68, 0x7C, 0x31, 0x71, 0x9C, 0x7B, 0xA4, + }, + { + 0x73, 0x56, 0xCD, 0x77, 0x20, 0xD5, 0xB6, 0x6B, 0x6D, 0x06, 0x97, 0xEB, 0x31, 0x77, 0xD9, 0xF8, + 0xD7, 0x3A, 0x4A, 0x5C, 0x5E, 0x96, 0x88, 0x96, 0xEB, 0x6A, 0x68, 0x96, 0x84, 0x30, 0x27, 0x06, + } }, + { 1025, + { + 0xD0, 0x02, 0x78, 0xAE, 0x47, 0xEB, 0x27, 0xB3, 0x4F, 0xAE, 0xCF, 0x67, 0xB4, 0xFE, 0x26, 0x3F, + 0x82, 0xD5, 0x41, 0x29, 0x16, 0xC1, 0xFF, 0xD9, 0x7C, 0x8C, 0xB7, 0xFB, 0x81, 0x4B, 0x84, 0x44, + }, + { + 0x35, 0x7D, 0xC5, 0x5D, 0xE0, 0xC7, 0xE3, 0x82, 0xC9, 0x00, 0xFD, 0x6E, 0x32, 0x0A, 0xCC, 0x04, + 0x14, 0x6B, 0xE0, 0x1D, 0xB6, 0xA8, 0xCE, 0x72, 0x10, 0xB7, 0x18, 0x9B, 0xD6, 0x64, 0xEA, 0x69, + }, + { + 0xEF, 0xFA, 0xA2, 0x45, 0xF0, 0x65, 0xFB, 0xF8, 0x2A, 0xC1, 0x86, 0x83, 0x9A, 0x24, 0x97, 0x07, + 0xC3, 0xBD, 0xDF, 0x6D, 0x3F, 0xDD, 0xA2, 0x2D, 0x1B, 0x95, 0xA3, 0xC9, 0x70, 0x37, 0x9B, 0xCB, + } }, + { 2048, + { + 0xE7, 0x76, 0xB6, 0x02, 0x8C, 0x7C, 0xD2, 0x2A, 0x4D, 0x0B, 0xA1, 0x82, 0xA8, 0xBF, 0x62, 0x20, + 0x5D, 0x2E, 0xF5, 0x76, 0x46, 0x7E, 0x83, 0x8E, 0xD6, 0xF2, 0x52, 0x9B, 0x85, 0xFB, 0xA2, 0x4A, + }, + { + 0x87, 0x9C, 0xF1, 0xFA, 0x2E, 0xA0, 0xE7, 0x91, 0x26, 0xCB, 0x10, 0x63, 0x61, 0x7A, 0x05, 0xB6, + 0xAD, 0x9D, 0x0B, 0x69, 0x6D, 0x0D, 0x75, 0x7C, 0xF0, 0x53, 0x43, 0x9F, 0x60, 0xA9, 0x9D, 0xD1, + }, + { + 0x7B, 0x29, 0x45, 0xCB, 0x4F, 0xEF, 0x70, 0x88, 0x5C, 0xC5, 0xD7, 0x8A, 0x87, 0xBF, 0x6F, 0x62, + 0x07, 0xDD, 0x90, 0x1F, 0xF2, 0x39, 0x20, 0x13, 0x51, 0xFF, 0xAC, 0x04, 0xE1, 0x08, 0x8A, 0x23, + } }, + { 2049, + { + 0x5F, 0x4D, 0x72, 0xF4, 0x0D, 0x7A, 0x5F, 0x82, 0xB1, 0x5C, 0xA2, 0xB2, 0xE4, 0x4B, 0x1D, 0xE3, + 0xC2, 0xEF, 0x86, 0xC4, 0x26, 0xC9, 0x5C, 0x1A, 0xF0, 0xB6, 0x87, 0x95, 0x22, 0x56, 0x30, 0x30, + }, + { + 0x9F, 0x29, 0x70, 0x09, 0x02, 0xF7, 0xC8, 0x6E, 0x51, 0x4D, 0xDC, 0x4D, 0xF1, 0xE3, 0x04, 0x9F, + 0x25, 0x8B, 0x24, 0x72, 0xB6, 0xDD, 0x52, 0x67, 0xF6, 0x1B, 0xF1, 0x39, 0x83, 0xB7, 0x8D, 0xD5, + }, + { + 0x2E, 0xA4, 0x77, 0xC5, 0x51, 0x5C, 0xC3, 0xDD, 0x60, 0x65, 0x12, 0xEE, 0x72, 0xBB, 0x3E, 0x0E, + 0x75, 0x8C, 0xFA, 0xE7, 0x23, 0x28, 0x26, 0xF3, 0x5F, 0xB9, 0x8C, 0xA1, 0xBC, 0xBD, 0xF2, 0x73, + } }, + { 3072, + { + 0xB9, 0x8C, 0xB0, 0xFF, 0x36, 0x23, 0xBE, 0x03, 0x32, 0x6B, 0x37, 0x3D, 0xE6, 0xB9, 0x09, 0x52, + 0x18, 0x51, 0x3E, 0x64, 0xF1, 0xEE, 0x2E, 0xDD, 0x25, 0x25, 0xC7, 0xAD, 0x1E, 0x5C, 0xFF, 0xD2, + }, + { + 0x04, 0x4A, 0x0E, 0x7B, 0x17, 0x2A, 0x31, 0x2D, 0xC0, 0x2A, 0x4C, 0x9A, 0x81, 0x8C, 0x03, 0x6F, + 0xFA, 0x27, 0x76, 0x36, 0x8D, 0x7F, 0x52, 0x82, 0x68, 0xD2, 0xE6, 0xB5, 0xDF, 0x19, 0x17, 0x70, + }, + { + 0x05, 0x0D, 0xF9, 0x7F, 0x8C, 0x2E, 0xAD, 0x65, 0x4D, 0x9B, 0xB3, 0xAB, 0x8C, 0x91, 0x78, 0xED, + 0xCD, 0x90, 0x2A, 0x32, 0xF8, 0x49, 0x59, 0x49, 0xFE, 0xAD, 0xCC, 0x1E, 0x04, 0x80, 0xC4, 0x6B, + } }, + { 3073, + { + 0x71, 0x24, 0xB4, 0x95, 0x01, 0x01, 0x2F, 0x81, 0xCC, 0x7F, 0x11, 0xCA, 0x06, 0x9E, 0xC9, 0x22, + 0x6C, 0xEC, 0xB8, 0xA2, 0xC8, 0x50, 0xCF, 0xE6, 0x44, 0xE3, 0x27, 0xD2, 0x2D, 0x3E, 0x1C, 0xD3, + }, + { + 0x68, 0xDE, 0xDE, 0x9B, 0xEF, 0x00, 0xBA, 0x89, 0xE4, 0x3F, 0x31, 0xA6, 0x82, 0x5F, 0x4C, 0xF4, + 0x33, 0x38, 0x9F, 0xED, 0xAE, 0x75, 0xC0, 0x4E, 0xE9, 0xF0, 0xCF, 0x16, 0xA4, 0x27, 0xC9, 0x5A, + }, + { + 0x72, 0x61, 0x3C, 0x9E, 0xC9, 0xFF, 0x7E, 0x40, 0xF8, 0xF5, 0xC1, 0x73, 0x78, 0x4C, 0x53, 0x2A, + 0xD8, 0x52, 0xE8, 0x27, 0xDB, 0xA2, 0xBF, 0x85, 0xB2, 0xAB, 0x4B, 0x76, 0xF7, 0x07, 0x90, 0x81, + } }, + { 4096, + { + 0x01, 0x50, 0x94, 0x01, 0x3F, 0x57, 0xA5, 0x27, 0x7B, 0x59, 0xD8, 0x47, 0x5C, 0x05, 0x01, 0x04, + 0x2C, 0x0B, 0x64, 0x2E, 0x53, 0x1B, 0x0A, 0x1C, 0x8F, 0x58, 0xD2, 0x16, 0x32, 0x29, 0xE9, 0x69, + }, + { + 0xBE, 0xFC, 0x66, 0x0A, 0xEA, 0x2F, 0x17, 0x18, 0x88, 0x4C, 0xD8, 0xDE, 0xB9, 0x90, 0x28, 0x11, + 0xD3, 0x32, 0xF4, 0xFC, 0x4A, 0x38, 0xCF, 0x7C, 0x73, 0x00, 0xD5, 0x97, 0xA0, 0x81, 0xBF, 0xC0, + }, + { + 0x1E, 0x0D, 0x7F, 0x3D, 0xB8, 0xC4, 0x14, 0xC9, 0x7C, 0x63, 0x07, 0xCB, 0xDA, 0x6C, 0xD2, 0x7A, + 0xC3, 0xB0, 0x30, 0x94, 0x9D, 0xA8, 0xE2, 0x3B, 0xE1, 0xA1, 0xA9, 0x24, 0xAD, 0x2F, 0x25, 0xB9, + } }, + { 4097, + { + 0x9B, 0x40, 0x52, 0xB3, 0x8F, 0x1C, 0x5F, 0xC8, 0xB1, 0xF9, 0xFF, 0x7A, 0xC7, 0xB2, 0x7C, 0xD2, + 0x42, 0x48, 0x7B, 0x3D, 0x89, 0x0D, 0x15, 0xC9, 0x6A, 0x1C, 0x25, 0xB8, 0xAA, 0x0F, 0xB9, 0x95, + }, + { + 0x00, 0xDF, 0x94, 0x0C, 0xD3, 0x6B, 0xB9, 0xFA, 0x7C, 0xBB, 0xC3, 0x55, 0x67, 0x44, 0xE0, 0xDB, + 0xC8, 0x19, 0x14, 0x01, 0xAF, 0xE7, 0x05, 0x20, 0xBA, 0x29, 0x2E, 0xE3, 0xCA, 0x80, 0xAB, 0xBC, + }, + { + 0xAC, 0xA5, 0x10, 0x29, 0x62, 0x6B, 0x55, 0xFD, 0xA7, 0x11, 0x7B, 0x42, 0xA7, 0xC2, 0x11, 0xF8, + 0xC6, 0xE9, 0xBA, 0x4F, 0xE5, 0xB7, 0xA8, 0xCA, 0x92, 0x2F, 0x34, 0x29, 0x95, 0x00, 0xEA, 0xD8, + } }, + { 5120, + { + 0x9C, 0xAD, 0xC1, 0x5F, 0xED, 0x8B, 0x5D, 0x85, 0x45, 0x62, 0xB2, 0x6A, 0x95, 0x36, 0xD9, 0x70, + 0x7C, 0xAD, 0xED, 0xA9, 0xB1, 0x43, 0x97, 0x8F, 0x31, 0x9A, 0xB3, 0x42, 0x30, 0x53, 0x58, 0x33, + }, + { + 0x2C, 0x49, 0x3E, 0x48, 0xE9, 0xB9, 0xBF, 0x31, 0xE0, 0x55, 0x3A, 0x22, 0xB2, 0x35, 0x03, 0xC0, + 0xA3, 0x38, 0x8F, 0x03, 0x5C, 0xEC, 0xE6, 0x8E, 0xB4, 0x38, 0xD2, 0x2F, 0xA1, 0x94, 0x3E, 0x20, + }, + { + 0x7A, 0x7A, 0xCA, 0xC8, 0xA0, 0x2A, 0xDC, 0xF3, 0x03, 0x8D, 0x74, 0xCD, 0xD1, 0xD3, 0x45, 0x27, + 0xDE, 0x8A, 0x0F, 0xCC, 0x0E, 0xE3, 0x39, 0x9D, 0x12, 0x62, 0x39, 0x7C, 0xE5, 0x81, 0x7F, 0x60, + } }, + { 5121, + { + 0x62, 0x8B, 0xD2, 0xCB, 0x20, 0x04, 0x69, 0x4A, 0xDA, 0xAB, 0x7B, 0xBD, 0x77, 0x8A, 0x25, 0xDF, + 0x25, 0xC4, 0x7B, 0x9D, 0x41, 0x55, 0xA5, 0x5F, 0x8F, 0xBD, 0x79, 0xF2, 0xFE, 0x15, 0x4C, 0xFF, + }, + { + 0x6C, 0xCF, 0x1C, 0x34, 0x75, 0x3E, 0x7A, 0x04, 0x4D, 0xB8, 0x07, 0x98, 0xEC, 0xD0, 0x78, 0x2A, + 0x8F, 0x76, 0xF3, 0x35, 0x63, 0xAC, 0xCA, 0xDD, 0xBF, 0xBB, 0x2E, 0x0E, 0xA4, 0xB2, 0xD0, 0x24, + }, + { + 0xB0, 0x7F, 0x01, 0xE5, 0x18, 0xE7, 0x02, 0xF7, 0xCC, 0xB4, 0x4A, 0x26, 0x7E, 0x9E, 0x11, 0x2D, + 0x40, 0x3A, 0x7B, 0x3F, 0x48, 0x83, 0xA4, 0x7F, 0xFB, 0xED, 0x4B, 0x48, 0x33, 0x9B, 0x3C, 0x34, + } }, + { 6144, + { + 0x3E, 0x2E, 0x5B, 0x74, 0xE0, 0x48, 0xF3, 0xAD, 0xD6, 0xD2, 0x1F, 0xAA, 0xB3, 0xF8, 0x3A, 0xA4, + 0x4D, 0x3B, 0x22, 0x78, 0xAF, 0xB8, 0x3B, 0x80, 0xB3, 0xC3, 0x51, 0x64, 0xEB, 0xEC, 0xA2, 0x05, + }, + { + 0x3D, 0x6B, 0x6D, 0x21, 0x28, 0x1D, 0x0A, 0xDE, 0x5B, 0x2B, 0x01, 0x6A, 0xE4, 0x03, 0x4C, 0x5D, + 0xEC, 0x10, 0xCA, 0x7E, 0x47, 0x5F, 0x90, 0xF7, 0x6E, 0xAC, 0x71, 0x38, 0xE9, 0xBC, 0x8F, 0x1D, + }, + { + 0x2A, 0x95, 0xBE, 0xAE, 0x63, 0xDD, 0xCE, 0x52, 0x37, 0x62, 0x35, 0x5C, 0xF4, 0xB9, 0xC1, 0xD8, + 0xF1, 0x31, 0x46, 0x57, 0x80, 0xA3, 0x91, 0x28, 0x6A, 0x5D, 0x01, 0xAB, 0xB5, 0x68, 0x3A, 0x15, + } }, + { 6145, + { + 0xF1, 0x32, 0x3A, 0x86, 0x31, 0x44, 0x6C, 0xC5, 0x05, 0x36, 0xA9, 0xF7, 0x05, 0xEE, 0x5C, 0xB6, + 0x19, 0x42, 0x4D, 0x46, 0x88, 0x7F, 0x3C, 0x37, 0x6C, 0x69, 0x5B, 0x70, 0xE0, 0xF0, 0x50, 0x7F, + }, + { + 0x9A, 0xC3, 0x01, 0xE9, 0xE3, 0x9E, 0x45, 0xE3, 0x25, 0x0A, 0x7E, 0x3B, 0x3D, 0xF7, 0x01, 0xAA, + 0x0F, 0xB6, 0x88, 0x9F, 0xBD, 0x80, 0xEE, 0xEC, 0xF2, 0x8D, 0xBC, 0x63, 0x00, 0xFB, 0xC5, 0x39, + }, + { + 0x37, 0x9B, 0xCC, 0x61, 0xD0, 0x05, 0x1D, 0xD4, 0x89, 0xF6, 0x86, 0xC1, 0x3D, 0xE0, 0x0D, 0x5B, + 0x14, 0xC5, 0x05, 0x24, 0x51, 0x03, 0xDC, 0x04, 0x0D, 0x9E, 0x4D, 0xD1, 0xFA, 0xCA, 0xB8, 0xE5, + } }, + { 7168, + { + 0x61, 0xDA, 0x95, 0x7E, 0xC2, 0x49, 0x9A, 0x95, 0xD6, 0xB8, 0x02, 0x3E, 0x2B, 0x0E, 0x60, 0x4E, + 0xC7, 0xF6, 0xB5, 0x0E, 0x80, 0xA9, 0x67, 0x8B, 0x89, 0xD2, 0x62, 0x8E, 0x99, 0xAD, 0xA7, 0x7A, + }, + { + 0xB4, 0x28, 0x35, 0xE4, 0x0E, 0x9D, 0x4A, 0x7F, 0x42, 0xAD, 0x8C, 0xC0, 0x4F, 0x85, 0xA9, 0x63, + 0xA7, 0x6E, 0x18, 0x19, 0x83, 0x77, 0xED, 0x84, 0xAD, 0xDD, 0xEA, 0xEC, 0xAC, 0xC6, 0xF3, 0xFC, + }, + { + 0x11, 0xC3, 0x7A, 0x11, 0x27, 0x65, 0x37, 0x0C, 0x94, 0xA5, 0x14, 0x15, 0xD0, 0xD6, 0x51, 0x19, + 0x0C, 0x28, 0x85, 0x66, 0xE2, 0x95, 0xD5, 0x05, 0xDE, 0xFD, 0xAD, 0x89, 0x5D, 0xAE, 0x22, 0x37, + } }, + { 7169, + { + 0xA0, 0x03, 0xFC, 0x7A, 0x51, 0x75, 0x4A, 0x9B, 0x3C, 0x7F, 0xAE, 0x03, 0x67, 0xAB, 0x3D, 0x78, + 0x2D, 0xCC, 0xF2, 0x88, 0x55, 0xA0, 0x3D, 0x43, 0x5F, 0x8C, 0xFE, 0x74, 0x60, 0x5E, 0x78, 0x17, + }, + { + 0xED, 0x9B, 0x1A, 0x92, 0x2C, 0x04, 0x6F, 0xDB, 0x3D, 0x42, 0x3A, 0xE3, 0x4E, 0x14, 0x3B, 0x05, + 0xCA, 0x1B, 0xF2, 0x8B, 0x71, 0x04, 0x32, 0x85, 0x7B, 0xF7, 0x38, 0xBC, 0xED, 0xBF, 0xA5, 0x11, + }, + { + 0x55, 0x4B, 0x0A, 0x5E, 0xFE, 0xA9, 0xEF, 0x18, 0x3F, 0x2F, 0x9B, 0x93, 0x1B, 0x74, 0x97, 0x99, + 0x5D, 0x9E, 0xB2, 0x6F, 0x5C, 0x5C, 0x6D, 0xAD, 0x2B, 0x97, 0xD6, 0x2F, 0xC5, 0xAC, 0x31, 0xD9, + } }, + { 8192, + { + 0xAA, 0xE7, 0x92, 0x48, 0x4C, 0x8E, 0xFE, 0x4F, 0x19, 0xE2, 0xCA, 0x7D, 0x37, 0x1D, 0x8C, 0x46, + 0x7F, 0xFB, 0x10, 0x74, 0x8D, 0x8A, 0x5A, 0x1A, 0xE5, 0x79, 0x94, 0x8F, 0x71, 0x8A, 0x2A, 0x63, + }, + { + 0xDC, 0x96, 0x37, 0xC8, 0x84, 0x5A, 0x77, 0x0B, 0x4C, 0xBF, 0x76, 0xB8, 0xDA, 0xEC, 0x0E, 0xEB, + 0xF7, 0xDC, 0x2E, 0xAC, 0x11, 0x49, 0x85, 0x17, 0xF0, 0x8D, 0x44, 0xC8, 0xFC, 0x00, 0xD5, 0x8A, + }, + { + 0xAD, 0x01, 0xD7, 0xAE, 0x4A, 0xD0, 0x59, 0xB0, 0xD3, 0x3B, 0xAA, 0x3C, 0x01, 0x31, 0x9D, 0xCF, + 0x80, 0x88, 0x09, 0x4D, 0x03, 0x59, 0xE5, 0xFD, 0x45, 0xD6, 0xAE, 0xAA, 0x8B, 0x2D, 0x0C, 0x3D, + } }, + { 8193, + { + 0xBA, 0xB6, 0xC0, 0x9C, 0xB8, 0xCE, 0x8C, 0xF4, 0x59, 0x26, 0x13, 0x98, 0xD2, 0xE7, 0xAE, 0xF3, + 0x57, 0x00, 0xBF, 0x48, 0x81, 0x16, 0xCE, 0xB9, 0x4A, 0x36, 0xD0, 0xF5, 0xF1, 0xB7, 0xBC, 0x3B, + }, + { + 0x95, 0x4A, 0x2A, 0x75, 0x42, 0x0C, 0x8D, 0x65, 0x47, 0xE3, 0xBA, 0x5B, 0x98, 0xD9, 0x63, 0xE6, + 0xFA, 0x64, 0x91, 0xAD, 0xDC, 0x8C, 0x02, 0x31, 0x89, 0xCC, 0x51, 0x98, 0x21, 0xB4, 0xA1, 0xF5, + }, + { + 0xAF, 0x1E, 0x03, 0x46, 0xE3, 0x89, 0xB1, 0x7C, 0x23, 0x20, 0x02, 0x70, 0xA6, 0x4A, 0xA4, 0xE1, + 0xEA, 0xD9, 0x8C, 0x61, 0x69, 0x5D, 0x91, 0x7D, 0xE7, 0xD5, 0xB0, 0x04, 0x91, 0xC9, 0xB0, 0xF1, + } }, + { 16384, + { + 0xF8, 0x75, 0xD6, 0x64, 0x6D, 0xE2, 0x89, 0x85, 0x64, 0x6F, 0x34, 0xEE, 0x13, 0xBE, 0x9A, 0x57, + 0x6F, 0xD5, 0x15, 0xF7, 0x6B, 0x5B, 0x0A, 0x26, 0xBB, 0x32, 0x47, 0x35, 0x04, 0x1D, 0xDD, 0xE4, + }, + { + 0x9E, 0x9F, 0xC4, 0xEB, 0x7C, 0xF0, 0x81, 0xEA, 0x7C, 0x47, 0xD1, 0x80, 0x77, 0x90, 0xED, 0x21, + 0x1B, 0xFE, 0xC5, 0x6A, 0xA2, 0x5B, 0xB7, 0x03, 0x77, 0x84, 0xC1, 0x3C, 0x4B, 0x70, 0x7B, 0x0D, + }, + { + 0x16, 0x0E, 0x18, 0xB5, 0x87, 0x8C, 0xD0, 0xDF, 0x1C, 0x3A, 0xF8, 0x5E, 0xB2, 0x5A, 0x0D, 0xB5, + 0x34, 0x4D, 0x43, 0xA6, 0xFB, 0xD7, 0xA8, 0xEF, 0x4E, 0xD9, 0x8D, 0x07, 0x14, 0xC3, 0xF7, 0xE1, + } }, + { 31744, + { + 0x62, 0xB6, 0x96, 0x0E, 0x1A, 0x44, 0xBC, 0xC1, 0xEB, 0x1A, 0x61, 0x1A, 0x8D, 0x62, 0x35, 0xB6, + 0xB4, 0xB7, 0x8F, 0x32, 0xE7, 0xAB, 0xC4, 0xFB, 0x4C, 0x6C, 0xDC, 0xCE, 0x94, 0x89, 0x5C, 0x47, + }, + { + 0xEF, 0xA5, 0x3B, 0x38, 0x9A, 0xB6, 0x7C, 0x59, 0x3D, 0xBA, 0x62, 0x4D, 0x89, 0x8D, 0x0F, 0x73, + 0x53, 0xAB, 0x99, 0xE4, 0xAC, 0x9D, 0x42, 0x30, 0x2E, 0xE6, 0x4C, 0xBF, 0x99, 0x39, 0xA4, 0x19, + }, + { + 0x39, 0x77, 0x2A, 0xEF, 0x80, 0xE0, 0xEB, 0xE6, 0x05, 0x96, 0x36, 0x1E, 0x45, 0xB0, 0x61, 0xE8, + 0xF4, 0x17, 0x42, 0x9D, 0x52, 0x91, 0x71, 0xB6, 0x76, 0x44, 0x68, 0xC2, 0x29, 0x28, 0xE2, 0x8E, + } }, + { 102400, + { + 0xBC, 0x3E, 0x3D, 0x41, 0xA1, 0x14, 0x6B, 0x06, 0x9A, 0xBF, 0xFA, 0xD3, 0xC0, 0xD4, 0x48, 0x60, + 0xCF, 0x66, 0x43, 0x90, 0xAF, 0xCE, 0x4D, 0x96, 0x61, 0xF7, 0x90, 0x2E, 0x79, 0x43, 0xE0, 0x85, + }, + { + 0x1C, 0x35, 0xD1, 0xA5, 0x81, 0x10, 0x83, 0xFD, 0x71, 0x19, 0xF5, 0xD5, 0xD1, 0xBA, 0x02, 0x7B, + 0x4D, 0x01, 0xC0, 0xC6, 0xC4, 0x9F, 0xB6, 0xFF, 0x2C, 0xF7, 0x53, 0x93, 0xEA, 0x5D, 0xB4, 0xA7, + }, + { + 0x46, 0x52, 0xCF, 0xF7, 0xA3, 0xF3, 0x85, 0xA6, 0x10, 0x3B, 0x5C, 0x26, 0x0F, 0xC1, 0x59, 0x3E, + 0x13, 0xC7, 0x78, 0xDB, 0xE6, 0x08, 0xEF, 0xB0, 0x92, 0xFE, 0x7E, 0xE6, 0x9D, 0xF6, 0xE9, 0xC6, + } } +}; + +test_vector test_vectors[] = { + { "", + { + 0xAF, 0x13, 0x49, 0xB9, 0xF5, 0xF9, 0xA1, 0xA6, 0xA0, 0x40, 0x4D, 0xEA, 0x36, 0xDC, 0xC9, 0x49, + 0x9B, 0xCB, 0x25, 0xC9, 0xAD, 0xC1, 0x12, 0xB7, 0xCC, 0x9A, 0x93, 0xCA, 0xE4, 0x1F, 0x32, 0x62, + } }, + { "a", + { + 0x17, 0x76, 0x2F, 0xDD, 0xD9, 0x69, 0xA4, 0x53, 0x92, 0x5D, 0x65, 0x71, 0x7A, 0xC3, 0xEE, 0xA2, + 0x13, 0x20, 0xB6, 0x6B, 0x54, 0x34, 0x2F, 0xDE, 0x15, 0x12, 0x8D, 0x6C, 0xAF, 0x21, 0x21, 0x5F, + } }, + { "ab", + { + 0x2D, 0xC9, 0x99, 0x99, 0xA6, 0xAA, 0xEF, 0x3F, 0x20, 0x34, 0x9D, 0x2E, 0xD4, 0x05, 0x7A, 0x2B, + 0x54, 0x41, 0x95, 0x45, 0xDA, 0xBB, 0x80, 0x9E, 0x63, 0x81, 0xDE, 0x1B, 0xAD, 0x83, 0x37, 0xE2, + } }, + { "abc", + { + 0x64, 0x37, 0xB3, 0xAC, 0x38, 0x46, 0x51, 0x33, 0xFF, 0xB6, 0x3B, 0x75, 0x27, 0x3A, 0x8D, 0xB5, + 0x48, 0xC5, 0x58, 0x46, 0x5D, 0x79, 0xDB, 0x03, 0xFD, 0x35, 0x9C, 0x6C, 0xD5, 0xBD, 0x9D, 0x85, + } }, + { "abcd", + { + 0x8C, 0x9C, 0x98, 0x81, 0x80, 0x5D, 0x1A, 0x84, 0x71, 0x02, 0xD7, 0xA4, 0x2E, 0x58, 0xB9, 0x90, + 0xD0, 0x88, 0xDD, 0x88, 0xA8, 0x4F, 0x73, 0x14, 0xD7, 0x1C, 0x83, 0x81, 0x07, 0x57, 0x1F, 0x2B, + } }, + { "abcde", + { + 0x06, 0x48, 0xC0, 0x3B, 0x5A, 0xD9, 0xBB, 0x6D, 0xDF, 0x83, 0x06, 0xEE, 0xF6, 0xA3, 0x3E, 0xBA, + 0xE8, 0xF8, 0x9C, 0xB4, 0x74, 0x11, 0x50, 0xC1, 0xAE, 0x9C, 0xD6, 0x62, 0xFD, 0xCC, 0x1E, 0xE2, + } }, + { "abcdef", + { + 0xB3, 0x4B, 0x56, 0x07, 0x67, 0x12, 0xFD, 0x7F, 0xB9, 0xC0, 0x67, 0x24, 0x5A, 0x6C, 0x85, 0xE1, + 0x61, 0x74, 0xB3, 0xEF, 0x2E, 0x35, 0xDF, 0x7B, 0x56, 0xB7, 0xF1, 0x64, 0xE5, 0xC3, 0x64, 0x46, + } }, + { "abcdefg", + { + 0xE2, 0xD1, 0x8D, 0x70, 0xDB, 0x12, 0x70, 0x5E, 0x18, 0x45, 0xFA, 0xF5, 0x00, 0xDE, 0x11, 0x98, + 0xA5, 0xBA, 0x14, 0x83, 0x72, 0x9D, 0x97, 0x93, 0x6F, 0x1D, 0x2B, 0x76, 0x09, 0x68, 0x31, 0x2E, + } }, + { "abcdefgh", + { + 0xDD, 0xAA, 0x2A, 0xC3, 0x0A, 0x98, 0x65, 0x59, 0x62, 0x97, 0x90, 0x19, 0xE4, 0x35, 0x38, 0x32, + 0x6A, 0xD7, 0xBE, 0xF0, 0xDA, 0x0E, 0x6A, 0xC2, 0xF3, 0xE5, 0x1F, 0xB3, 0x51, 0x3A, 0x5C, 0xDA, + } }, + { "abcdefghi", + { + 0x89, 0x9E, 0xAD, 0x67, 0x56, 0x1E, 0x6E, 0x71, 0x76, 0xDD, 0xCA, 0xD0, 0xB4, 0x47, 0xCA, 0xEC, + 0x42, 0xA6, 0x58, 0xB7, 0x0B, 0xB1, 0x81, 0x75, 0x7F, 0x14, 0x4C, 0xE9, 0xEB, 0xB1, 0x59, 0xC4, + } }, + { "abcdefghij", + { + 0xD1, 0x0C, 0x2A, 0xCB, 0x51, 0x8F, 0xD7, 0x4A, 0xE1, 0x30, 0xF6, 0x3E, 0x3A, 0x45, 0x2A, 0x9A, + 0x05, 0x54, 0x71, 0x16, 0x41, 0x81, 0xA6, 0x3A, 0x7D, 0x94, 0xC1, 0x82, 0xF5, 0x70, 0x34, 0x9B, + } }, + { "abcdefghijk", + { + 0x33, 0x69, 0x93, 0xBC, 0xB0, 0xAA, 0xCB, 0x8B, 0x33, 0xCB, 0xBF, 0x67, 0x70, 0x25, 0x59, 0x29, + 0x62, 0x60, 0x6A, 0x69, 0x0C, 0xEC, 0xBE, 0xD3, 0x85, 0x7C, 0x81, 0xF9, 0x49, 0x36, 0x4E, 0xA5, + } }, + { "abcdefghijkl", + { + 0xA7, 0x4A, 0x54, 0x2E, 0xA1, 0xF9, 0x95, 0x7F, 0x55, 0xBA, 0xE1, 0x99, 0xF8, 0x9A, 0xB4, 0x6B, + 0x90, 0xC8, 0xB4, 0x1E, 0x94, 0x04, 0x89, 0x07, 0x5E, 0xC9, 0x24, 0x49, 0x0F, 0xB6, 0xA9, 0x26, + } }, + { "abcdefghijklm", + { + 0xA1, 0xF7, 0xDF, 0x9D, 0x3A, 0x01, 0x44, 0x25, 0x22, 0x0D, 0x2B, 0x96, 0xDF, 0xB3, 0xCE, 0xBA, + 0x8A, 0xD1, 0x26, 0x4E, 0xD1, 0x65, 0x56, 0x50, 0x02, 0xA6, 0xEC, 0xC3, 0x02, 0xAF, 0x7A, 0xD0, + } }, + { "abcdefghijklmn", + { + 0x7A, 0x97, 0x9F, 0xCC, 0xF3, 0x89, 0x89, 0xFC, 0xDD, 0x63, 0x09, 0xDB, 0x94, 0x7D, 0x28, 0x6D, + 0xF2, 0xF4, 0xF7, 0xEC, 0x80, 0xDD, 0x11, 0x88, 0xEC, 0x07, 0x94, 0xE9, 0x1B, 0x8F, 0xBE, 0x2E, + } }, + { "abcdefghijklmno", + { + 0xF3, 0x50, 0x1B, 0x61, 0x52, 0x06, 0xCE, 0x9E, 0x7D, 0xC2, 0xC6, 0xAD, 0x16, 0xA0, 0xF2, 0xC6, + 0x44, 0x34, 0xDD, 0xF1, 0xA5, 0x33, 0xBB, 0xDC, 0x0A, 0x25, 0xA6, 0x0D, 0x20, 0xE4, 0x4E, 0x5E, + } }, + { "abcdefghijklmnop", + { + 0x00, 0x9E, 0x43, 0x83, 0x6D, 0x52, 0xF4, 0x8B, 0x87, 0x6D, 0x62, 0x74, 0xFF, 0x17, 0xFA, 0xF3, + 0xB5, 0xA3, 0x4A, 0xF7, 0x7E, 0x68, 0xD7, 0xFA, 0x73, 0x0E, 0x5E, 0xF9, 0xFE, 0xA2, 0xD3, 0x58, + } }, + { "abcdefghijklmnopq", + { + 0x26, 0xDC, 0x2E, 0x71, 0x55, 0x16, 0xD4, 0x06, 0xC3, 0x70, 0x02, 0x05, 0x68, 0x90, 0xFE, 0xD1, + 0x94, 0x64, 0x83, 0x38, 0x7E, 0xFB, 0xB8, 0x0E, 0x87, 0x33, 0x74, 0x32, 0x67, 0x37, 0x21, 0x61, + } }, + { "abcdefghijklmnopqr", + { + 0xD1, 0x32, 0x17, 0xD1, 0x80, 0xF3, 0x75, 0xED, 0xDD, 0x8A, 0x18, 0x9E, 0x03, 0x18, 0x31, 0x69, + 0x1F, 0xBD, 0x73, 0xB0, 0x28, 0xEE, 0x49, 0x7A, 0x5C, 0xAF, 0xC0, 0x8B, 0xD2, 0x9C, 0xEA, 0x6C, + } }, + { "abcdefghijklmnopqrs", + { + 0x24, 0x5F, 0xE8, 0x6C, 0xDE, 0x9B, 0x1E, 0x6F, 0xAD, 0xDB, 0xFA, 0xEE, 0xAF, 0x7F, 0x9C, 0x7C, + 0xD9, 0xC0, 0x9A, 0xD6, 0x2B, 0x38, 0x45, 0x2D, 0x10, 0x3F, 0x62, 0x09, 0x83, 0x4C, 0xBF, 0x23, + } }, + { "abcdefghijklmnopqrst", + { + 0x18, 0xC5, 0x4F, 0xED, 0x84, 0xD3, 0x23, 0xE2, 0xEE, 0x91, 0x94, 0x81, 0x19, 0x22, 0x4F, 0x31, + 0x59, 0xBF, 0xD4, 0xCD, 0xD3, 0xF5, 0x85, 0xF8, 0x2B, 0x56, 0xE0, 0xA6, 0x30, 0x92, 0xAD, 0xDE, + } }, + { "abcdefghijklmnopqrstu", + { + 0x61, 0x4A, 0x68, 0x5B, 0xE9, 0x1B, 0xC4, 0x46, 0x05, 0xEA, 0xE3, 0x31, 0x17, 0x5F, 0x45, 0xB8, + 0xDA, 0xC2, 0x6F, 0xE3, 0xD1, 0xC5, 0xFB, 0xCD, 0x5D, 0x76, 0x1E, 0x0F, 0x74, 0x12, 0xB8, 0x2F, + } }, + { "abcdefghijklmnopqrstuv", + { + 0x22, 0x37, 0x6F, 0x74, 0xFE, 0x12, 0x93, 0xC4, 0xB7, 0x3B, 0xA3, 0x53, 0x7F, 0x00, 0xA3, 0x52, + 0xE6, 0xA1, 0x2D, 0x67, 0xBF, 0xF9, 0x74, 0xC3, 0xBB, 0xA4, 0x4A, 0x5F, 0xC0, 0x3F, 0xED, 0xE7, + } }, + { "abcdefghijklmnopqrstuvw", + { + 0xC9, 0x65, 0xC1, 0xCC, 0xCE, 0x9C, 0xBC, 0xCB, 0xB8, 0x68, 0x31, 0x64, 0x91, 0xAA, 0x01, 0x86, + 0xAB, 0x83, 0x9C, 0xFE, 0x86, 0xEF, 0xA4, 0xFE, 0xDF, 0xF0, 0x79, 0x2C, 0x79, 0xCF, 0x4E, 0xF9, + } }, + { "abcdefghijklmnopqrstuvwx", + { + 0x3A, 0x00, 0x36, 0x42, 0xAB, 0x93, 0xEA, 0xD3, 0xDC, 0xEB, 0xDE, 0x1C, 0xD7, 0x4D, 0x48, 0x2A, + 0xEA, 0xB7, 0x6C, 0x51, 0x52, 0xA7, 0xF2, 0xE4, 0x02, 0x39, 0x63, 0xBF, 0x36, 0x57, 0x03, 0xD8, + } }, + { "abcdefghijklmnopqrstuvwxy", + { + 0xF7, 0xD9, 0x71, 0xE0, 0x5B, 0xAF, 0xD5, 0x8A, 0x22, 0x0F, 0x3A, 0x95, 0x34, 0x54, 0x2F, 0xE5, + 0x45, 0x60, 0x4A, 0x7F, 0x99, 0x16, 0x56, 0x49, 0xB6, 0x59, 0x09, 0x3A, 0xEB, 0xA5, 0xFA, 0x6A, + } }, + { "abcdefghijklmnopqrstuvwxyz", + { + 0x24, 0x68, 0xEE, 0xC8, 0x89, 0x4A, 0xCF, 0xB4, 0xE4, 0xDF, 0x3A, 0x51, 0xEA, 0x91, 0x6B, 0xA1, + 0x15, 0xD4, 0x82, 0x68, 0x28, 0x77, 0x54, 0x29, 0x0A, 0xAE, 0x8E, 0x9E, 0x62, 0x28, 0xE8, 0x5F, + } }, + { "abcdefghijklmnopqrstuvwxyz0", + { + 0xD6, 0xC9, 0xDE, 0x2C, 0x54, 0xD2, 0x7B, 0xDB, 0x4F, 0x68, 0xCD, 0x3C, 0x73, 0x42, 0x1D, 0x81, + 0xF5, 0x2C, 0xC8, 0x06, 0xD2, 0x55, 0xDA, 0x08, 0xE2, 0x25, 0x4A, 0x0D, 0x57, 0x03, 0x1F, 0xF0, + } }, + { "abcdefghijklmnopqrstuvwxyz01", + { + 0x4D, 0x0C, 0x6F, 0x2F, 0xB0, 0xC1, 0xEB, 0xC4, 0x1B, 0xF2, 0x3C, 0xBA, 0xED, 0x30, 0x88, 0x39, + 0xD7, 0x80, 0xAB, 0x47, 0xC8, 0xA3, 0x81, 0x23, 0xAF, 0x46, 0xB6, 0xE3, 0xAD, 0x82, 0x5F, 0xA4, + } }, + { "abcdefghijklmnopqrstuvwxyz012", + { + 0x4C, 0xEB, 0x7C, 0x49, 0x7A, 0xF4, 0xB6, 0x73, 0xC8, 0x58, 0xD8, 0x74, 0x6F, 0xDD, 0xBA, 0x3B, + 0xFA, 0x80, 0xFA, 0x1A, 0xCB, 0x84, 0xE2, 0xAE, 0x91, 0x76, 0x9D, 0x4B, 0xD8, 0x74, 0xEA, 0x70, + } }, + { "abcdefghijklmnopqrstuvwxyz0123", + { + 0xED, 0x4C, 0x4A, 0xC9, 0x6A, 0x2E, 0xB3, 0xC0, 0xCC, 0x80, 0x88, 0xB4, 0x30, 0x3F, 0xD6, 0x78, + 0x9B, 0x65, 0x16, 0x2F, 0x35, 0xD2, 0x77, 0xB2, 0xE6, 0xA8, 0x0F, 0xAF, 0x48, 0xCA, 0x67, 0x5E, + } }, + { "abcdefghijklmnopqrstuvwxyz01234", + { + 0x6A, 0x96, 0x16, 0x1F, 0x64, 0xDB, 0x0D, 0x56, 0xF0, 0x73, 0xFF, 0xE3, 0xC2, 0xC6, 0x66, 0xEB, + 0x70, 0x2F, 0xFF, 0xCA, 0xA1, 0xF0, 0x96, 0xEB, 0xB1, 0x97, 0x0F, 0x78, 0xFD, 0xB5, 0x2B, 0xC9, + } }, + { "abcdefghijklmnopqrstuvwxyz012345", + { + 0x35, 0x5E, 0x5F, 0xD6, 0x25, 0xA9, 0xCD, 0x5C, 0x27, 0xB3, 0x12, 0xB3, 0xC4, 0x20, 0x8D, 0x02, + 0x36, 0xED, 0x6D, 0x32, 0x56, 0x6B, 0xD5, 0xA0, 0x64, 0x25, 0x99, 0xC1, 0xC8, 0x99, 0x64, 0x06, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456", + { + 0x86, 0xDE, 0x08, 0xB3, 0x23, 0x46, 0xA1, 0x21, 0xDB, 0xC1, 0xBB, 0xB9, 0x0C, 0xFF, 0xCA, 0x94, + 0x29, 0xA5, 0x06, 0x8D, 0x79, 0x52, 0xFE, 0xF8, 0x97, 0x41, 0x6E, 0xBC, 0xE2, 0x47, 0xC6, 0xAE, + } }, + { "abcdefghijklmnopqrstuvwxyz01234567", + { + 0xFA, 0x75, 0xCD, 0x23, 0x99, 0x2C, 0x82, 0xCF, 0x11, 0x0B, 0x4C, 0xA1, 0xEE, 0x6A, 0x11, 0x86, + 0x15, 0x48, 0xE9, 0xBD, 0x9C, 0xCB, 0x63, 0x2C, 0x7B, 0x60, 0x1F, 0xC3, 0xCB, 0x10, 0x65, 0x9F, + } }, + { "abcdefghijklmnopqrstuvwxyz012345678", + { + 0x2F, 0x96, 0xFC, 0xD5, 0x47, 0x6D, 0x14, 0x65, 0xB0, 0xA9, 0x9B, 0x37, 0x31, 0xCA, 0xF2, 0x41, + 0x4B, 0xD2, 0xF0, 0x90, 0x10, 0xEE, 0x09, 0x44, 0x48, 0xBD, 0xB5, 0x59, 0x8A, 0xEC, 0xFF, 0xD2, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789", + { + 0xB0, 0xB9, 0x2F, 0x78, 0x81, 0x54, 0x3E, 0xFB, 0x77, 0xF3, 0x18, 0x6D, 0x81, 0x86, 0x09, 0x44, + 0x20, 0xA9, 0x00, 0x63, 0xBB, 0x5A, 0x38, 0xC7, 0x55, 0x1D, 0xFB, 0x3D, 0xAC, 0x2F, 0xEB, 0xB1, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789a", + { + 0x0B, 0xE1, 0x2A, 0x0C, 0xFC, 0xE8, 0xCF, 0xAF, 0xB4, 0x66, 0x2D, 0xBC, 0xFD, 0xD4, 0x21, 0xAF, + 0x55, 0x98, 0xE2, 0x01, 0x5D, 0xC8, 0xA3, 0x80, 0x5A, 0x68, 0xA7, 0x6D, 0x9D, 0xB0, 0xFE, 0xC3, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789ab", + { + 0x82, 0x0F, 0x15, 0xAE, 0x80, 0x73, 0x5E, 0x1B, 0x0A, 0x67, 0x6C, 0xA6, 0x9D, 0x36, 0xA1, 0x8D, + 0x86, 0x93, 0x21, 0x39, 0x84, 0xD5, 0x5B, 0x5E, 0x3A, 0x7C, 0xC9, 0x60, 0x45, 0x08, 0x49, 0x79, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abc", + { + 0xD8, 0x42, 0xF4, 0x26, 0xC3, 0x02, 0xDD, 0x36, 0xE7, 0x26, 0xD8, 0x59, 0xF0, 0xD3, 0x54, 0x3B, + 0xA9, 0xF3, 0x31, 0xE2, 0xA4, 0xFC, 0x93, 0xF9, 0x22, 0xC0, 0xDD, 0xFA, 0x60, 0x2E, 0x36, 0x32, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcd", + { + 0x50, 0xA4, 0xDE, 0x4F, 0x65, 0x9A, 0x28, 0x2C, 0xD0, 0x99, 0x68, 0x60, 0x12, 0xDB, 0xD9, 0xAF, + 0x2C, 0x1F, 0xD9, 0x7B, 0x50, 0x8E, 0xE8, 0x7B, 0xF6, 0x5F, 0x6F, 0x3E, 0x7F, 0x67, 0xB5, 0xF9, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcde", + { + 0x05, 0xC5, 0x21, 0xE1, 0x77, 0x93, 0xF5, 0x5D, 0xAF, 0x1D, 0x3A, 0xDD, 0xD1, 0x3A, 0xC8, 0xF7, + 0x84, 0x51, 0xAF, 0xE4, 0x1C, 0x3D, 0xAC, 0x3D, 0xE5, 0x5D, 0x9F, 0x11, 0xE8, 0x31, 0xED, 0x2B, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdef", + { + 0x1D, 0x7D, 0x82, 0xB2, 0xD2, 0x17, 0xD9, 0xDF, 0xCB, 0xC6, 0xD9, 0x72, 0x47, 0x22, 0x0C, 0xC5, + 0x2D, 0xF1, 0x0F, 0xAF, 0xD4, 0x51, 0x61, 0xD2, 0x6A, 0x36, 0x36, 0x5D, 0x0E, 0xB8, 0xDD, 0x65, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefg", + { + 0xC7, 0x82, 0x41, 0x6B, 0xF3, 0xE7, 0x40, 0x6B, 0x1A, 0xFD, 0xC8, 0x2A, 0xFB, 0x6D, 0xE6, 0xB9, + 0x15, 0xFF, 0x83, 0x48, 0x2D, 0x61, 0x11, 0x8E, 0xE1, 0xC0, 0x35, 0xE6, 0x48, 0x39, 0x9E, 0xE6, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefgh", + { + 0x0C, 0xB5, 0x2C, 0xD0, 0x1B, 0x97, 0x54, 0x0F, 0x87, 0x3E, 0xD6, 0x71, 0x9D, 0x5F, 0xF6, 0xFE, + 0xB1, 0xE1, 0x46, 0x91, 0x35, 0x50, 0x0E, 0x6E, 0xD5, 0x9D, 0x21, 0x41, 0x43, 0xD9, 0x50, 0xC2, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghi", + { + 0x6F, 0xE1, 0xD4, 0x04, 0x47, 0x58, 0x8C, 0x8D, 0xD9, 0x7B, 0x63, 0x72, 0xE4, 0x85, 0xD9, 0x33, + 0x63, 0x36, 0x2A, 0x5B, 0xF6, 0x4E, 0x4C, 0x1B, 0x34, 0x1B, 0xD7, 0xF7, 0xFF, 0x86, 0x81, 0xFC, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghij", + { + 0xB4, 0x75, 0xF1, 0x63, 0xEF, 0x54, 0x19, 0x19, 0x01, 0x9D, 0x5B, 0xF2, 0x87, 0xC5, 0x6E, 0xD6, + 0x47, 0x24, 0xFD, 0x54, 0x86, 0x5A, 0x6A, 0xC1, 0xF0, 0x1D, 0x20, 0x06, 0x23, 0x29, 0x85, 0x01, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijk", + { + 0x57, 0xA5, 0xD1, 0x5A, 0xAB, 0x13, 0x3A, 0x41, 0x25, 0xBA, 0x8E, 0xFC, 0x97, 0x90, 0x48, 0x16, + 0x6A, 0x21, 0x58, 0x5F, 0x47, 0xDA, 0xC9, 0x64, 0xA6, 0x4C, 0xCA, 0xD0, 0x49, 0xF9, 0x5B, 0xC1, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijkl", + { + 0x40, 0x9F, 0x20, 0x6E, 0xBE, 0x1D, 0x31, 0x1C, 0x2E, 0x97, 0x16, 0xC6, 0x8F, 0x81, 0xBF, 0x7D, + 0xA2, 0x2A, 0xC3, 0x27, 0x10, 0x07, 0xF6, 0x15, 0x54, 0x0D, 0xF8, 0xA3, 0x22, 0x54, 0x08, 0xA0, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklm", + { + 0xC1, 0x1A, 0x7C, 0x91, 0xAC, 0xC9, 0x02, 0xA6, 0xC5, 0x41, 0xFC, 0x0C, 0x79, 0x49, 0xDC, 0x86, + 0xF5, 0xBE, 0xCD, 0x3E, 0xFD, 0x21, 0x89, 0x64, 0xD2, 0x36, 0x1A, 0x9D, 0xEB, 0xC9, 0xD6, 0x24, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmn", + { + 0xC4, 0xBB, 0x86, 0x95, 0x20, 0x61, 0xEC, 0xB5, 0x97, 0x16, 0x3E, 0xB3, 0xAD, 0xD6, 0xAB, 0x55, + 0xEB, 0x76, 0x25, 0xCD, 0xA7, 0x43, 0x89, 0x39, 0xF0, 0x58, 0xFD, 0x37, 0x43, 0xF7, 0x50, 0xE6, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmno", + { + 0x52, 0x37, 0x53, 0x3B, 0x57, 0x4F, 0x97, 0xAE, 0x1F, 0x93, 0xEE, 0x00, 0x56, 0x59, 0xCD, 0xCB, + 0x2D, 0x93, 0xF5, 0x28, 0x2D, 0x88, 0x12, 0xCD, 0xCD, 0xF1, 0xB2, 0x3C, 0xE6, 0xC0, 0x5D, 0xE1, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnop", + { + 0x12, 0x31, 0x05, 0x55, 0x14, 0x80, 0x59, 0xFD, 0x7D, 0x68, 0x56, 0xD8, 0x66, 0x5D, 0xBB, 0xCF, + 0xC8, 0x27, 0x88, 0x7F, 0x4F, 0xE3, 0x3E, 0x60, 0x5B, 0x3F, 0xF8, 0x3D, 0x5F, 0x42, 0xCB, 0x4B, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopq", + { + 0xF1, 0xEF, 0x42, 0xBD, 0x61, 0x26, 0x88, 0x75, 0x92, 0x98, 0x37, 0x2B, 0x04, 0x3C, 0xBB, 0x22, + 0x71, 0xA6, 0x51, 0x12, 0x0D, 0x99, 0xA4, 0x02, 0x52, 0xC0, 0x75, 0xC8, 0x32, 0x57, 0x61, 0xA1, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqr", + { + 0x39, 0xC9, 0x89, 0x0B, 0x86, 0xAC, 0xDF, 0xD8, 0xB8, 0x76, 0x4C, 0x78, 0x34, 0x62, 0x25, 0xF9, + 0xD0, 0x69, 0xCC, 0x53, 0xB8, 0xD8, 0xC3, 0xB9, 0xD5, 0xD9, 0x99, 0x22, 0xBA, 0x4E, 0x2C, 0x43, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrs", + { + 0x94, 0x84, 0xD7, 0x8C, 0x2C, 0x64, 0x9C, 0x38, 0x41, 0xE5, 0x95, 0xCD, 0x20, 0xA4, 0xD0, 0x87, + 0xBF, 0x52, 0xCE, 0x14, 0x69, 0xE2, 0x57, 0x08, 0xA4, 0x18, 0x32, 0x58, 0xC6, 0x1E, 0xD2, 0xEF, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrst", + { + 0x38, 0x5C, 0xC1, 0x1A, 0x36, 0x61, 0x12, 0xBE, 0x5B, 0xF3, 0x36, 0x32, 0xB3, 0x63, 0xD4, 0x95, + 0x5D, 0x29, 0x5F, 0x1F, 0x2B, 0x4C, 0xF0, 0x08, 0xBB, 0x0E, 0x67, 0x90, 0xB1, 0x17, 0xD3, 0xE6, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstu", + { + 0x52, 0x35, 0x52, 0x89, 0x00, 0xF4, 0xBC, 0x82, 0xF5, 0x47, 0x46, 0x33, 0x05, 0x87, 0xD1, 0x1B, + 0x8F, 0x20, 0x3E, 0x66, 0x35, 0xD8, 0x3A, 0xB7, 0x08, 0xC6, 0x9A, 0x95, 0xBC, 0x6E, 0xC7, 0xAD, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuv", + { + 0xD1, 0x40, 0x7E, 0x7D, 0x6B, 0x47, 0x49, 0xF9, 0x9F, 0xEB, 0x9C, 0xAE, 0x77, 0xFF, 0x4B, 0x3B, + 0x32, 0xA6, 0xD0, 0xD3, 0x6E, 0xB1, 0xA2, 0x79, 0x28, 0xBD, 0xAB, 0x1A, 0x98, 0x21, 0xF0, 0xD7, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvw", + { + 0x4F, 0xE7, 0xCC, 0x9F, 0x96, 0x3F, 0x77, 0x03, 0xB4, 0x48, 0x26, 0xEC, 0x47, 0x6E, 0x63, 0x3F, + 0x22, 0xCA, 0x25, 0x97, 0xAE, 0x1A, 0x5B, 0x75, 0xF8, 0x4A, 0xFE, 0x6C, 0x8A, 0x04, 0xAD, 0x56, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwx", + { + 0xD0, 0x50, 0x10, 0x40, 0xA4, 0xCE, 0x8E, 0xB2, 0x73, 0x55, 0x19, 0xC3, 0xFB, 0xED, 0x76, 0x5E, + 0x9D, 0x80, 0x42, 0xDD, 0x3B, 0xD4, 0x3F, 0xF9, 0x07, 0xCC, 0xD9, 0x5D, 0xCC, 0x17, 0xC6, 0xCC, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxy", + { + 0xE8, 0x8C, 0xAF, 0x20, 0x5D, 0x3C, 0x9F, 0x8F, 0x82, 0x2F, 0x65, 0x3A, 0xD5, 0x80, 0x9F, 0x43, + 0xD1, 0xF9, 0xD4, 0x6A, 0x3E, 0x45, 0xA9, 0xEB, 0xCF, 0xF2, 0xE6, 0xC0, 0x64, 0x38, 0xF8, 0x7D, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz", + { + 0x23, 0xCE, 0xAA, 0xFD, 0xAC, 0x74, 0xFB, 0xB0, 0x87, 0x33, 0xC0, 0x03, 0x25, 0xA6, 0x96, 0x40, + 0xEE, 0x85, 0xC9, 0xB3, 0x32, 0x68, 0x2C, 0x5A, 0xE2, 0x68, 0xB4, 0x53, 0x90, 0x48, 0x7C, 0x6A, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0", + { + 0xD4, 0x91, 0x25, 0x0A, 0x64, 0xC0, 0xA6, 0xB6, 0xDB, 0x2D, 0xDE, 0x1A, 0xEA, 0x38, 0x92, 0xEE, + 0x56, 0x47, 0x8D, 0x2B, 0x26, 0xC4, 0x26, 0xE2, 0xA2, 0x52, 0xE5, 0x39, 0x37, 0x5F, 0xFB, 0x59, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01", + { + 0xF7, 0xF8, 0x54, 0x5A, 0x00, 0x36, 0x5D, 0xE0, 0x08, 0x90, 0xAF, 0x80, 0x89, 0x96, 0xED, 0x71, + 0x87, 0x8A, 0xDA, 0x34, 0x9A, 0x98, 0xD2, 0xCB, 0x5B, 0x91, 0x06, 0xC1, 0x95, 0x60, 0x71, 0x37, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz012", + { + 0xF5, 0x2B, 0x8E, 0x5E, 0x75, 0xC5, 0x6B, 0x8E, 0xAD, 0x21, 0xE5, 0xEF, 0x19, 0x19, 0xBD, 0xA7, + 0x30, 0x70, 0x8B, 0xA3, 0x3F, 0x9F, 0x24, 0x6A, 0x73, 0xC4, 0x03, 0xE1, 0x41, 0xCE, 0xED, 0x0F, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123", + { + 0x8D, 0x07, 0x4A, 0xB3, 0xB4, 0x1A, 0xF8, 0xCF, 0x10, 0xCB, 0x52, 0x60, 0x6A, 0xED, 0xEC, 0x0B, + 0xFB, 0x8D, 0xD9, 0xF0, 0xD5, 0x22, 0xA8, 0xAA, 0xD4, 0x5C, 0x4C, 0x50, 0x01, 0x60, 0xF3, 0x07, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01234", + { + 0x35, 0xFF, 0xDE, 0x6F, 0x4A, 0xF5, 0xE6, 0x5F, 0x5E, 0xCF, 0x17, 0x46, 0x4A, 0xE6, 0xDE, 0x27, + 0x56, 0x06, 0x5F, 0xAF, 0x72, 0xF6, 0x3A, 0xD9, 0x02, 0xBD, 0x0E, 0x56, 0x2B, 0xB7, 0x18, 0x94, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz012345", + { + 0xFE, 0x4F, 0x12, 0xD6, 0x2E, 0xC1, 0x73, 0x6E, 0x99, 0x4A, 0x78, 0xD8, 0xEF, 0x66, 0x7E, 0x5B, + 0x35, 0xB3, 0x03, 0x74, 0x85, 0x76, 0x0E, 0x8F, 0xFA, 0xDD, 0xE2, 0x41, 0xA6, 0x19, 0x34, 0x66, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456", + { + 0xA3, 0x27, 0xA8, 0xF0, 0xCE, 0xF2, 0x24, 0x4E, 0x39, 0x3F, 0xE9, 0x8B, 0xA7, 0xE5, 0x59, 0x5C, + 0x5E, 0x40, 0xE4, 0x35, 0x93, 0xE5, 0x87, 0xCE, 0x55, 0x43, 0x02, 0x1C, 0xD5, 0xF9, 0x4C, 0xAD, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01234567", + { + 0xD2, 0x17, 0xD0, 0xA3, 0xEC, 0x35, 0x28, 0x99, 0xDD, 0xB1, 0xD0, 0x38, 0xE5, 0x33, 0x6A, 0xE7, + 0x15, 0x56, 0xC0, 0xEA, 0x61, 0x2A, 0xF9, 0x70, 0xCB, 0x75, 0xD4, 0x9B, 0x1E, 0x25, 0x36, 0x6E, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz012345678", + { + 0x05, 0x5E, 0x47, 0x72, 0x8E, 0x3C, 0xE5, 0xD4, 0x83, 0xBD, 0xB4, 0x8F, 0x47, 0x14, 0x5C, 0xF6, + 0xF7, 0x31, 0xF5, 0x0F, 0xC9, 0x34, 0xA1, 0xF6, 0x4B, 0x58, 0xBD, 0xE6, 0x41, 0x38, 0x38, 0x07, + } }, + { "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789", + { + 0xAA, 0x60, 0x21, 0x6D, 0xE8, 0x21, 0x17, 0x5F, 0x97, 0x3E, 0x38, 0x26, 0xED, 0x7A, 0x0B, 0x74, + 0x31, 0xEC, 0x87, 0xE8, 0xE2, 0x19, 0x2E, 0x80, 0x24, 0x12, 0x53, 0xB2, 0xA9, 0x4D, 0xB0, 0x11, + } }, +}; + +std::vector test_input(size_t input_len) +{ + std::vector input; + for (size_t i = 0; i < input_len; ++i) { + input.push_back(uint8_t(i % 251)); + } + return input; +} + +TEST(misc_blake3s_full, test_vectors) +{ + for (auto v : test_vectors) { + std::vector input(v.input.begin(), v.input.end()); + EXPECT_EQ(blake3_full::blake3s(input), v.output); + } +} + +TEST(misc_blake3s_full, test_full_vectors) +{ + std::string key_str = "whats the Elvish word for friend"; + std::vector key(key_str.begin(), key_str.end()); + + char context[] = "BLAKE3 2019-12-27 16:29:52 test vectors context"; + + for (auto v : full_test_vector) { + std::vector input = test_input(v.input_len); + + // Test normal hash + EXPECT_EQ(blake3_full::blake3s(input), v.hash); + + // Test keyed hash + EXPECT_EQ(blake3_full::blake3s(input, blake3_full::KEYED_HASH_MODE, &key[0]), v.keyed_hash); + + // Test derive key hash + EXPECT_EQ(blake3_full::blake3s(input, blake3_full::DERIVE_KEY_MODE, nullptr, context), v.derive_key); + } +} diff --git a/cpp/src/aztec/crypto/pedersen/convert_buffer_to_field.hpp b/cpp/src/aztec/crypto/pedersen/convert_buffer_to_field.hpp new file mode 100644 index 0000000000..e6dc4d3212 --- /dev/null +++ b/cpp/src/aztec/crypto/pedersen/convert_buffer_to_field.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +namespace crypto { +namespace pedersen { + +inline std::vector convert_buffer_to_field(const std::vector& input) +{ + const size_t num_bytes = input.size(); + const size_t bytes_per_element = 31; + size_t num_elements = (num_bytes % bytes_per_element != 0) + (num_bytes / bytes_per_element); + + const auto slice = [](const std::vector& data, const size_t start, const size_t slice_size) { + uint256_t result(0); + for (size_t i = 0; i < slice_size; ++i) { + result = (result << uint256_t(8)); + result += uint256_t(data[i + start]); + } + return grumpkin::fq(result); + }; + + std::vector elements; + for (size_t i = 0; i < num_elements; ++i) { + size_t bytes_to_slice = 0; + if (i == num_elements - 1) { + bytes_to_slice = num_bytes - (i * bytes_per_element); + } else { + bytes_to_slice = bytes_per_element; + } + grumpkin::fq element = slice(input, i * bytes_per_element, bytes_to_slice); + elements.emplace_back(element); + } + return elements; +} +} // namespace pedersen +} // namespace crypto \ No newline at end of file diff --git a/cpp/src/aztec/crypto/pedersen/pedersen.cpp b/cpp/src/aztec/crypto/pedersen/pedersen.cpp index 7e9367825c..92aa4f6c13 100644 --- a/cpp/src/aztec/crypto/pedersen/pedersen.cpp +++ b/cpp/src/aztec/crypto/pedersen/pedersen.cpp @@ -1,4 +1,5 @@ #include "./pedersen.hpp" +#include "./convert_buffer_to_field.hpp" #include #include #ifndef NO_MULTITHREADING @@ -78,31 +79,7 @@ grumpkin::fq compress_native(const std::vector& inputs, const size */ grumpkin::fq compress_native_buffer_to_field(const std::vector& input) { - const size_t num_bytes = input.size(); - const size_t bytes_per_element = 31; - size_t num_elements = (num_bytes % bytes_per_element != 0) + (num_bytes / bytes_per_element); - - const auto slice = [](const std::vector& data, const size_t start, const size_t slice_size) { - uint256_t result(0); - for (size_t i = 0; i < slice_size; ++i) { - result = (result << uint256_t(8)); - result += uint256_t(data[i + start]); - } - return grumpkin::fq(result); - }; - - std::vector elements; - for (size_t i = 0; i < num_elements; ++i) { - size_t bytes_to_slice = 0; - if (i == num_elements - 1) { - bytes_to_slice = num_bytes - (i * bytes_per_element); - } else { - bytes_to_slice = bytes_per_element; - } - grumpkin::fq element = slice(input, i * bytes_per_element, bytes_to_slice); - elements.emplace_back(element); - } - + const auto elements = convert_buffer_to_field(input); grumpkin::fq result_fq = compress_native(elements); return result_fq; } diff --git a/cpp/src/aztec/crypto/pedersen/pedersen_lookup.cpp b/cpp/src/aztec/crypto/pedersen/pedersen_lookup.cpp new file mode 100644 index 0000000000..5fd8dbff62 --- /dev/null +++ b/cpp/src/aztec/crypto/pedersen/pedersen_lookup.cpp @@ -0,0 +1,211 @@ +#include "./pedersen_lookup.hpp" +#include "./convert_buffer_to_field.hpp" + +#include + +namespace crypto { +namespace pedersen { +namespace lookup { +namespace { + +static std::array, NUM_PEDERSEN_TABLES> pedersen_tables; +static std::vector pedersen_iv_table; +static std::array generators; +static bool inited = false; + +void init_single_lookup_table(const size_t index) +{ + std::vector temp; + temp.reserve(PEDERSEN_TABLE_SIZE); + pedersen_tables[index].reserve(PEDERSEN_TABLE_SIZE); + + const auto& generator = generators[index]; + for (size_t i = 0; i < PEDERSEN_TABLE_SIZE; ++i) { + temp.emplace_back(generator * grumpkin::fr(i + 1)); + } + grumpkin::g1::element::batch_normalize(&temp[0], PEDERSEN_TABLE_SIZE); + + for (const auto& element : temp) { + pedersen_tables[index].emplace_back(element); + } +} + +void init_small_lookup_table(const size_t index) +{ + std::vector temp; + temp.reserve(PEDERSEN_SMALL_TABLE_SIZE); + pedersen_tables[index].reserve(PEDERSEN_SMALL_TABLE_SIZE); + + const auto& generator = generators[index]; + for (size_t i = 0; i < PEDERSEN_SMALL_TABLE_SIZE; ++i) { + temp.emplace_back(generator * grumpkin::fr(i + 1)); + } + grumpkin::g1::element::batch_normalize(&temp[0], PEDERSEN_SMALL_TABLE_SIZE); + + for (const auto& element : temp) { + pedersen_tables[index].emplace_back(element); + } +} + +void init_iv_lookup_table() +{ + std::vector temp; + temp.reserve(PEDERSEN_IV_TABLE_SIZE); + pedersen_iv_table.reserve(PEDERSEN_IV_TABLE_SIZE); + + for (size_t i = 0; i < PEDERSEN_IV_TABLE_SIZE; ++i) { + temp.emplace_back(grumpkin::g1::affine_one * grumpkin::fr(i + 1)); + } + grumpkin::g1::element::batch_normalize(&temp[0], PEDERSEN_IV_TABLE_SIZE); + + for (const auto& element : temp) { + pedersen_iv_table.emplace_back(element); + } +} + +void init() +{ + ASSERT(BITS_PER_TABLE < BITS_OF_BETA); + ASSERT(BITS_PER_TABLE + BITS_OF_BETA < BITS_ON_CURVE); + if (inited) { + return; + } + generators = grumpkin::g1::derive_generators(); + const size_t first_half = (NUM_PEDERSEN_TABLES >> 1) - 1; + for (size_t i = 0; i < first_half; ++i) { + init_single_lookup_table(i); + } + init_small_lookup_table(first_half); + for (size_t i = 0; i < first_half; ++i) { + init_single_lookup_table(i + first_half + 1); + } + init_small_lookup_table(2 * first_half + 1); + init_iv_lookup_table(); + inited = true; +} +} // namespace + +grumpkin::g1::affine_element get_table_generator(const size_t table_index) +{ + ASSERT(table_index < NUM_PEDERSEN_TABLES); + init(); + return generators[table_index]; +} + +const std::vector& get_table(const size_t table_index) +{ + init(); + return pedersen_tables[table_index]; +} + +const std::vector& get_iv_table() +{ + init(); + return pedersen_iv_table; +} + +grumpkin::g1::element hash_single(const grumpkin::fq& input, const bool parity) +{ + init(); + uint256_t bits(input); + + // N.B. NUM_PEDERSEN_TABLES must be divisible by 2 for this to work as-is. + constexpr size_t num_rounds = NUM_PEDERSEN_TABLES / 2; + constexpr uint64_t table_mask = PEDERSEN_TABLE_SIZE - 1; + size_t table_index_offset = parity ? (NUM_PEDERSEN_TABLES / 2) : 0; + + std::array accumulators; + for (size_t i = 0; i < num_rounds; ++i) { + const uint64_t slice_a = (bits.data[0] & table_mask); + bits >>= BITS_PER_TABLE; + const uint64_t slice_b = (bits.data[0] & table_mask); + + // P = g * (b) + g * (a * lambda) + const size_t index = table_index_offset + i; + if (i == 0) { + accumulators = { + pedersen_tables[index][static_cast(slice_a)], + pedersen_tables[index][static_cast(slice_b)], + }; + } else { + accumulators[0] += pedersen_tables[index][static_cast(slice_a)]; + if (i < (num_rounds - 1)) { + accumulators[1] += pedersen_tables[index][static_cast(slice_b)]; + } + } + bits >>= (BITS_PER_TABLE); + } + + grumpkin::fq beta = grumpkin::fq::cube_root_of_unity(); + accumulators[0].x *= beta; + + return accumulators[0] + accumulators[1]; +} + +grumpkin::fq hash_pair(const grumpkin::fq& left, const grumpkin::fq& right) +{ + grumpkin::g1::affine_element result = + grumpkin::g1::affine_element(hash_single(left, false) + hash_single(right, true)); + return result.x; +} + +grumpkin::g1::element merkle_damgard_compress(const std::vector& inputs, const size_t iv) +{ + if (inputs.size() == 0) { + auto result = grumpkin::g1::affine_one; + result.self_set_infinity(); + return result; + } + init(); + const size_t num_inputs = inputs.size(); + + grumpkin::fq result = (pedersen_iv_table[iv]).x; + for (size_t i = 0; i < num_inputs; i++) { + result = hash_pair(result, inputs[i]); + } + + return (hash_single(result, false) + hash_single(grumpkin::fq(num_inputs), true)); +} + +grumpkin::g1::affine_element commit_native(const std::vector& inputs, const size_t hash_index) +{ + return grumpkin::g1::affine_element(merkle_damgard_compress(inputs, hash_index)); +} + +grumpkin::fq compress_native(const std::vector& inputs, const size_t hash_index) +{ + return commit_native(inputs, hash_index).x; +} + +grumpkin::fq compress_native_buffer_to_field(const std::vector& input) +{ + const auto elements = convert_buffer_to_field(input); + grumpkin::fq result_fq = compress_native(elements); + return result_fq; +} + +std::vector compress_native(const std::vector& input) +{ + const auto result_fq = compress_native_buffer_to_field(input); + uint256_t result_u256(result_fq); + const size_t num_bytes = input.size(); + + bool is_zero = true; + for (const auto byte : input) { + is_zero = is_zero && (byte == static_cast(0)); + } + if (is_zero) { + result_u256 = num_bytes; + } + std::vector result_buffer; + result_buffer.reserve(32); + for (size_t i = 0; i < 32; ++i) { + const uint64_t shift = (31 - i) * 8; + uint256_t shifted = result_u256 >> uint256_t(shift); + result_buffer.push_back(static_cast(shifted.data[0])); + } + return result_buffer; +} +} // namespace lookup +} // namespace pedersen +} // namespace crypto \ No newline at end of file diff --git a/cpp/src/aztec/crypto/pedersen/pedersen_lookup.hpp b/cpp/src/aztec/crypto/pedersen/pedersen_lookup.hpp new file mode 100644 index 0000000000..ac63e9b19e --- /dev/null +++ b/cpp/src/aztec/crypto/pedersen/pedersen_lookup.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include + +namespace crypto { +namespace pedersen { +namespace lookup { + +constexpr size_t BITS_PER_HASH = 512; +constexpr size_t BITS_PER_TABLE = 9; +constexpr size_t BITS_OF_BETA = 192; +constexpr size_t BITS_ON_CURVE = 254; +constexpr size_t BITS_PER_LAST_TABLE = 2; +constexpr size_t PEDERSEN_TABLE_SIZE = (1UL) << BITS_PER_TABLE; +constexpr size_t PEDERSEN_SMALL_TABLE_SIZE = (1UL) << BITS_PER_LAST_TABLE; +constexpr size_t TABLE_MULTIPLICITY = 2; // using group automorphism, we can read from the same table twice +constexpr size_t NUM_PEDERSEN_TABLES_RAW = (BITS_PER_HASH / (BITS_PER_TABLE * TABLE_MULTIPLICITY)) + 1; +constexpr size_t NUM_PEDERSEN_TABLES = NUM_PEDERSEN_TABLES_RAW + (NUM_PEDERSEN_TABLES_RAW & 1); +constexpr size_t PEDERSEN_IV_TABLE_SIZE = (1UL) << 10; +constexpr size_t NUM_PEDERSEN_IV_TABLES = 4; + +grumpkin::g1::affine_element get_table_generator(const size_t table_index); + +const std::array& get_endomorphism_scalars(); + +const std::vector& get_table(const size_t table_index); +const std::vector& get_iv_table(); + +grumpkin::g1::element hash_single(const grumpkin::fq& input, const bool parity); + +grumpkin::fq hash_pair(const grumpkin::fq& left, const grumpkin::fq& right); +grumpkin::g1::element merkle_damgard_compress(const std::vector& inputs, const size_t iv); + +grumpkin::fq compress_native(const std::vector& inputs, const size_t hash_index = 0); +std::vector compress_native(const std::vector& input); + +grumpkin::fq compress_native_buffer_to_field(const std::vector& input); + +template grumpkin::fq compress_native(const std::array& inputs) +{ + std::vector in(inputs.begin(), inputs.end()); + return compress_native(in); +} + +grumpkin::g1::affine_element commit_native(const std::vector& inputs, const size_t hash_index = 0); + +} // namespace lookup +} // namespace pedersen +} // namespace crypto \ No newline at end of file diff --git a/cpp/src/aztec/crypto/pedersen/pedersen_lookup.test.cpp b/cpp/src/aztec/crypto/pedersen/pedersen_lookup.test.cpp new file mode 100644 index 0000000000..974dd1ba29 --- /dev/null +++ b/cpp/src/aztec/crypto/pedersen/pedersen_lookup.test.cpp @@ -0,0 +1,143 @@ +#include +#include +#include + +#include "./pedersen_lookup.hpp" + +namespace { +auto& engine = numeric::random::get_debug_engine(); +} + +auto compute_expected(const grumpkin::fq exponent, size_t generator_offset) +{ + uint256_t bits(exponent); + std::array accumulators; + const auto lambda = grumpkin::fr::cube_root_of_unity(); + const auto mask = crypto::pedersen::lookup::PEDERSEN_TABLE_SIZE - 1; + + for (size_t i = 0; i < 15; ++i) { + const auto slice_a = static_cast(bits.data[0] & mask) + 1; + bits >>= crypto::pedersen::lookup::BITS_PER_TABLE; + const auto slice_b = static_cast(bits.data[0] & mask) + 1; + + const auto generator = crypto::pedersen::lookup::get_table_generator(generator_offset + i); + + if (i == 0) { + accumulators[0] = generator * (lambda * slice_a); + accumulators[1] = generator * grumpkin::fr(slice_b); + } else { + accumulators[0] += (generator * (lambda * slice_a)); + if (i < 14) { + accumulators[1] += (generator * grumpkin::fr(slice_b)); + } + } + bits >>= crypto::pedersen::lookup::BITS_PER_TABLE; + } + return (accumulators[0] + accumulators[1]); +} + +TEST(pedersen_lookup, endomorphism_test) +{ + typedef grumpkin::fq fq; + typedef grumpkin::fr fr; + + typedef grumpkin::g1::affine_element affine_element; + typedef grumpkin::g1::element element; + + fr exponent = engine.get_random_uint256(); + + const auto beta = fq::cube_root_of_unity(); + + const auto lambda = fr::cube_root_of_unity(); + + const element P = grumpkin::g1::one; + + affine_element base(P * exponent); + affine_element first(P * (exponent * lambda)); + affine_element second(P * (exponent * (lambda + 1))); + EXPECT_EQ(base.x * beta, first.x); + EXPECT_EQ(base.x * beta.sqr(), second.x); + EXPECT_EQ(base.y, first.y); + EXPECT_EQ(-base.y, second.y); +} + +TEST(pedersen_lookup, hash_single) +{ + typedef grumpkin::fq fq; + typedef grumpkin::fr fr; + typedef grumpkin::g1::affine_element affine_element; + typedef grumpkin::g1::element element; + + const fq exponent = engine.get_random_uint256(); + + const affine_element result(crypto::pedersen::lookup::hash_single(exponent, false)); + + const auto mask = crypto::pedersen::lookup::PEDERSEN_TABLE_SIZE - 1; + + uint256_t bits(exponent); + + const fr lambda = grumpkin::fr::cube_root_of_unity(); + + std::array accumulators; + + for (size_t i = 0; i < 15; ++i) { + const auto slice_a = static_cast(bits.data[0] & mask) + 1; + bits >>= crypto::pedersen::lookup::BITS_PER_TABLE; + const auto slice_b = static_cast(bits.data[0] & mask) + 1; + + const element generator = crypto::pedersen::lookup::get_table_generator(i); + + if (i == 0) { + accumulators[0] = generator * (lambda * slice_a); + accumulators[1] = generator * (slice_b); + } else { + accumulators[0] += (generator * (lambda * slice_a)); + if (i < 14) { + accumulators[1] += (generator * (slice_b)); + } + } + bits >>= crypto::pedersen::lookup::BITS_PER_TABLE; + } + + const affine_element expected(accumulators[0] + accumulators[1]); + + EXPECT_EQ(result, expected); +} + +TEST(pedersen_lookup, hash_pair) +{ + typedef grumpkin::fq fq; + typedef grumpkin::g1::affine_element affine_element; + + const fq left = engine.get_random_uint256(); + const fq right = engine.get_random_uint256(); + + const fq result(crypto::pedersen::lookup::hash_pair(left, right)); + + const affine_element expected(compute_expected(left, 0) + compute_expected(right, 15)); + + EXPECT_EQ(result, expected.x); +} + +TEST(pedersen_lookup, merkle_damgard_compress) +{ + typedef grumpkin::fq fq; + typedef grumpkin::fr fr; + typedef grumpkin::g1::affine_element affine_element; + + const size_t m = 3, iv = 10; + std::vector inputs; + for (size_t i = 0; i < m; i++) { + inputs.push_back(engine.get_random_uint256()); + } + + const auto result = crypto::pedersen::lookup::merkle_damgard_compress(inputs, iv); + + fq intermediate = (grumpkin::g1::affine_one * fr(iv + 1)).x; + for (size_t i = 0; i < m; i++) { + intermediate = affine_element(compute_expected(intermediate, 0) + compute_expected(inputs[i], 15)).x; + } + + EXPECT_EQ(affine_element(result).x, + affine_element(compute_expected(intermediate, 0) + compute_expected(fq(m), 15)).x); +} diff --git a/cpp/src/aztec/crypto/pedersen/sidon_pedersen.cpp b/cpp/src/aztec/crypto/pedersen/sidon_pedersen.cpp deleted file mode 100644 index 68ef37979a..0000000000 --- a/cpp/src/aztec/crypto/pedersen/sidon_pedersen.cpp +++ /dev/null @@ -1,171 +0,0 @@ -#include "./sidon_pedersen.hpp" - -#include "./sidon_set/sidon_set.hpp" - -#include - -namespace crypto { -namespace pedersen { -namespace sidon { -namespace { - -static std::vector sidon_set; -static std::array, NUM_PEDERSEN_TABLES> sidon_pedersen_tables; -static std::array generators; -static bool inited = false; - -void init_single_sidon_lookup_table(const size_t index) -{ - std::vector temp; - temp.reserve(PEDERSEN_TABLE_SIZE); - sidon_pedersen_tables[index].reserve(PEDERSEN_TABLE_SIZE); - - const auto& generator = generators[index]; - for (size_t i = 0; i < PEDERSEN_TABLE_SIZE; ++i) { - temp.emplace_back(generator * grumpkin::fr(sidon_set[i])); - } - grumpkin::g1::element::batch_normalize(&temp[0], PEDERSEN_TABLE_SIZE); - - for (const auto& element : temp) { - sidon_pedersen_tables[index].emplace_back(element); - } -} - -void init() -{ - if (inited) { - return; - } - sidon_set = compute_sidon_set(); - generators = grumpkin::g1::derive_generators(); - for (size_t i = 0; i < NUM_PEDERSEN_TABLES; ++i) { - init_single_sidon_lookup_table(i); - } - inited = true; -} -} // namespace - -grumpkin::g1::affine_element get_table_generator(const size_t table_index) -{ - ASSERT(table_index < NUM_PEDERSEN_TABLES); - init(); - return generators[table_index]; -} - -const std::vector& get_sidon_set() -{ - init(); - return sidon_set; -} - -const std::vector& get_table(const size_t table_index) -{ - init(); - return sidon_pedersen_tables[table_index]; -} - -grumpkin::g1::element compress_single(const grumpkin::fq& input, const bool parity) -{ - init(); - uint256_t bits(input); - - // N.B. NUM_PEDERSEN_TABLES must be divisible by 2 for this to work as-is. - constexpr size_t num_rounds = NUM_PEDERSEN_TABLES / 2; - - constexpr uint64_t table_mask = PEDERSEN_TABLE_SIZE - 1; - - size_t table_index_offset = parity ? (NUM_PEDERSEN_TABLES / 2) : 0; - - std::array accumulators; - for (size_t i = 0; i < num_rounds; ++i) { - const uint64_t slice_a = (bits.data[0] & table_mask); - bits >>= BITS_PER_TABLE; - const uint64_t slice_b = (bits.data[0] & table_mask); - bits >>= BITS_PER_TABLE; - const uint64_t slice_c = (bits.data[0] & table_mask); - - // P = g * a + g * (b * lambda) + g * (c * (lambda + 1)) - - const size_t index = table_index_offset + i; - if (i == 0) { - accumulators = { - sidon_pedersen_tables[index][static_cast(slice_a)], - sidon_pedersen_tables[index][static_cast(slice_b)], - sidon_pedersen_tables[index][static_cast(slice_c)], - }; - } else { - accumulators[0] += sidon_pedersen_tables[index][static_cast(slice_a)]; - accumulators[1] += sidon_pedersen_tables[index][static_cast(slice_b)]; - if (i < (num_rounds - 1)) { - accumulators[2] += sidon_pedersen_tables[index][static_cast(slice_c)]; - } - } - bits >>= (BITS_PER_TABLE); - } - - accumulators[0].x *= grumpkin::fq::beta(); - accumulators[2].x *= grumpkin::fq::beta().sqr(); - accumulators[2].y = -accumulators[2].y; - - return accumulators[0] + accumulators[1] + accumulators[2]; -} - -grumpkin::fq compress_native(const grumpkin::fq& left, const grumpkin::fq& right) -{ - grumpkin::g1::affine_element result = - grumpkin::g1::affine_element(compress_single(left, false) + compress_single(right, true)); - return result.x; -} - -grumpkin::g1::element tree_compress(const std::vector& inputs) -{ - const size_t num_inputs = inputs.size(); - - size_t num_tree_levels = numeric::get_msb(num_inputs) + 1; - if (1UL << num_tree_levels < num_inputs) { - ++num_tree_levels; - } - - std::vector previous_leaves(inputs.begin(), inputs.end()); - - for (size_t i = 0; i < num_tree_levels - 1; ++i) { - const size_t num_leaves = 1UL << (num_tree_levels - i); - std::vector current_leaves; - for (size_t j = 0; j < num_leaves; j += 2) { - grumpkin::fq left; - grumpkin::fq right; - if (j < previous_leaves.size()) { - left = previous_leaves[j]; - } else { - left = 0; - } - - if ((j + 1) < previous_leaves.size()) { - right = previous_leaves[j + 1]; - } else { - right = 0; - } - - current_leaves.push_back(compress_native(left, right)); - } - - previous_leaves.resize(current_leaves.size()); - std::copy(current_leaves.begin(), current_leaves.end(), previous_leaves.begin()); - } - - return (compress_single(previous_leaves[0], false) + compress_single(previous_leaves[1], true)); -} - -grumpkin::g1::affine_element commit_native(const std::vector& inputs) -{ - return grumpkin::g1::affine_element(tree_compress(inputs)); -} - -grumpkin::fq compress_native(const std::vector& inputs) -{ - return commit_native(inputs).x; -} - -} // namespace sidon -} // namespace pedersen -} // namespace crypto \ No newline at end of file diff --git a/cpp/src/aztec/crypto/pedersen/sidon_pedersen.hpp b/cpp/src/aztec/crypto/pedersen/sidon_pedersen.hpp deleted file mode 100644 index 6d5a092ff0..0000000000 --- a/cpp/src/aztec/crypto/pedersen/sidon_pedersen.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include - -namespace crypto { -namespace pedersen { -namespace sidon { - -constexpr size_t BITS_PER_HASH = 512; -constexpr size_t BITS_PER_TABLE = 10; -constexpr size_t PEDERSEN_TABLE_SIZE = (1UL) << BITS_PER_TABLE; -constexpr size_t TABLE_MULTIPLICITY = 3; // using our sidon sequences, we can read from the same table three times -constexpr size_t NUM_PEDERSEN_TABLES = - (BITS_PER_HASH + (BITS_PER_TABLE * TABLE_MULTIPLICITY)) / (BITS_PER_TABLE * TABLE_MULTIPLICITY); - -grumpkin::g1::affine_element get_table_generator(const size_t table_index); - -const std::array& get_endomorphism_scalars(); - -const std::vector& get_sidon_set(); - -const std::vector& get_table(const size_t table_index); - -grumpkin::g1::element compress_single(const grumpkin::fq& input, const bool parity); - -grumpkin::fq compress_native(const grumpkin::fq& left, const grumpkin::fq& right); -grumpkin::fq compress_native(const std::vector& inputs); -template grumpkin::fq compress_native(const std::array& inputs) -{ - std::vector in(inputs.begin(), inputs.end()); - return compress_native(in); -} - -grumpkin::g1::affine_element commit_native(const std::vector& inputs); - -} // namespace sidon -} // namespace pedersen -} // namespace crypto \ No newline at end of file diff --git a/cpp/src/aztec/crypto/pedersen/sidon_pedersen.test.cpp b/cpp/src/aztec/crypto/pedersen/sidon_pedersen.test.cpp deleted file mode 100644 index d5547b4173..0000000000 --- a/cpp/src/aztec/crypto/pedersen/sidon_pedersen.test.cpp +++ /dev/null @@ -1,133 +0,0 @@ -#include -#include -#include - -#include "./sidon_pedersen.hpp" - -namespace { -auto& engine = numeric::random::get_debug_engine(); -} - -TEST(sidon_pedersen, endomorphism_test) -{ - typedef grumpkin::fq fq; - typedef grumpkin::fr fr; - - typedef grumpkin::g1::affine_element affine_element; - typedef grumpkin::g1::element element; - - fr exponent = engine.get_random_uint256(); - - const auto beta = fq::beta(); - - const auto lambda = fr::beta(); - - const element P = grumpkin::g1::one; - - affine_element base(P * exponent); - affine_element first(P * (exponent * lambda)); - affine_element second(P * (exponent * (lambda + 1))); - EXPECT_EQ(base.x * beta, first.x); - EXPECT_EQ(base.x * beta.sqr(), second.x); - EXPECT_EQ(base.y, first.y); - EXPECT_EQ(-base.y, second.y); -} - -TEST(sidon_pedersen, compress_single) -{ - typedef grumpkin::fq fq; - typedef grumpkin::fr fr; - typedef grumpkin::g1::affine_element affine_element; - typedef grumpkin::g1::element element; - - const fq exponent = engine.get_random_uint256(); - - const affine_element result(crypto::pedersen::sidon::compress_single(exponent, false)); - - const auto& sidon_set = crypto::pedersen::sidon::get_sidon_set(); - - const auto mask = crypto::pedersen::sidon::PEDERSEN_TABLE_SIZE - 1; - - uint256_t bits(exponent); - - const fr lambda = grumpkin::fr::beta(); - - std::array accumulators; - - for (size_t i = 0; i < 9; ++i) { - uint64_t slice_a = sidon_set[static_cast(bits.data[0] & mask)]; - bits >>= crypto::pedersen::sidon::BITS_PER_TABLE; - uint64_t slice_b = sidon_set[static_cast(bits.data[0] & mask)]; - bits >>= crypto::pedersen::sidon::BITS_PER_TABLE; - uint64_t slice_c = sidon_set[static_cast(bits.data[0] & mask)]; - - const element generator = crypto::pedersen::sidon::get_table_generator(i); - - if (i == 0) { - accumulators[0] = generator * (lambda * slice_a); - accumulators[1] = generator * (slice_b); - accumulators[2] = generator * ((lambda + 1) * slice_c); - } else { - accumulators[0] += (generator * (lambda * slice_a)); - accumulators[1] += (generator * (slice_b)); - if (i < 8) { - accumulators[2] += (generator * ((lambda + 1) * slice_c)); - } - } - bits >>= crypto::pedersen::sidon::BITS_PER_TABLE; - } - - const affine_element expected(accumulators[0] + accumulators[1] + accumulators[2]); - - EXPECT_EQ(result, expected); -} - -TEST(sidon_pedersen, compress_native) -{ - typedef grumpkin::fq fq; - typedef grumpkin::fr fr; - typedef grumpkin::g1::affine_element affine_element; - typedef grumpkin::g1::element element; - - const fq left = engine.get_random_uint256(); - const fq right = engine.get_random_uint256(); - - const fq result(crypto::pedersen::sidon::compress_native(left, right)); - - const auto& sidon_set = crypto::pedersen::sidon::get_sidon_set(); - - const auto compute_expected = [&sidon_set](fq exponent, size_t generator_offset) { - uint256_t bits(exponent); - std::array accumulators; - const fr lambda = grumpkin::fr::beta(); - const auto mask = crypto::pedersen::sidon::PEDERSEN_TABLE_SIZE - 1; - - for (size_t i = 0; i < 9; ++i) { - uint64_t slice_a = sidon_set[static_cast(bits.data[0] & mask)]; - bits >>= crypto::pedersen::sidon::BITS_PER_TABLE; - uint64_t slice_b = sidon_set[static_cast(bits.data[0] & mask)]; - bits >>= crypto::pedersen::sidon::BITS_PER_TABLE; - uint64_t slice_c = sidon_set[static_cast(bits.data[0] & mask)]; - - const element generator = crypto::pedersen::sidon::get_table_generator(generator_offset + i); - - if (i == 0) { - accumulators[0] = generator * (lambda * slice_a); - accumulators[1] = generator * (slice_b); - accumulators[2] = generator * ((lambda + 1) * slice_c); - } else { - accumulators[0] += (generator * (lambda * slice_a)); - accumulators[1] += (generator * (slice_b)); - if (i < 8) { - accumulators[2] += (generator * ((lambda + 1) * slice_c)); - } - } - bits >>= crypto::pedersen::sidon::BITS_PER_TABLE; - } - return (accumulators[0] + accumulators[1] + accumulators[2]); - }; - - const affine_element expected(compute_expected(left, 0) + compute_expected(right, 9)); - - EXPECT_EQ(result, expected.x); -} diff --git a/cpp/src/aztec/ecc/curves/bn254/fq.test.cpp b/cpp/src/aztec/ecc/curves/bn254/fq.test.cpp index ead55d8d1f..79e7bdea70 100644 --- a/cpp/src/aztec/ecc/curves/bn254/fq.test.cpp +++ b/cpp/src/aztec/ecc/curves/bn254/fq.test.cpp @@ -267,7 +267,8 @@ TEST(fq, beta) fq x = fq::random_element(); fq beta_x = { x.data[0], x.data[1], x.data[2], x.data[3] }; - beta_x = beta_x * fq::beta(); + fq beta = fq::cube_root_of_unity(); + beta_x = beta_x * beta; // compute x^3 fq x_cubed; @@ -356,7 +357,7 @@ TEST(fq, split_into_endomorphism_scalars) k1.self_to_montgomery_form(); k2.self_to_montgomery_form(); - result = k2 * fq::beta(); + result = k2 * fq::cube_root_of_unity(); result = k1 - result; result.self_from_montgomery_form(); @@ -378,7 +379,8 @@ TEST(fq, split_into_endomorphism_scalars_simple) k1.self_to_montgomery_form(); k2.self_to_montgomery_form(); - result = k2 * fq::beta(); + fq beta = fq::cube_root_of_unity(); + result = k2 * beta; result = k1 - result; result.self_from_montgomery_form(); diff --git a/cpp/src/aztec/ecc/curves/bn254/fr.hpp b/cpp/src/aztec/ecc/curves/bn254/fr.hpp index 36d64c6772..0c2f11910b 100644 --- a/cpp/src/aztec/ecc/curves/bn254/fr.hpp +++ b/cpp/src/aztec/ecc/curves/bn254/fr.hpp @@ -9,6 +9,10 @@ namespace barretenberg { class Bn254FrParams { public: + // Note: limbs here are combined as concat(_3, _2, _1, _0) + // E.g. this modulus forms the value: + // 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000001 + // = 21888242871839275222246405745257275088548364400416034343698204186575808495617 static constexpr uint64_t modulus_0 = 0x43E1F593F0000001UL; static constexpr uint64_t modulus_1 = 0x2833E84879B97091UL; static constexpr uint64_t modulus_2 = 0xB85045B68181585DUL; diff --git a/cpp/src/aztec/ecc/curves/bn254/fr.test.cpp b/cpp/src/aztec/ecc/curves/bn254/fr.test.cpp index 4fab9a1d06..2f7a3a5f9f 100644 --- a/cpp/src/aztec/ecc/curves/bn254/fr.test.cpp +++ b/cpp/src/aztec/ecc/curves/bn254/fr.test.cpp @@ -177,7 +177,8 @@ TEST(fr, lambda) fr x = fr::random_element(); fr lambda_x = { x.data[0], x.data[1], x.data[2], x.data[3] }; - lambda_x = lambda_x * fr::beta(); + fr lambda = fr::cube_root_of_unity(); + lambda_x = lambda_x * lambda; // compute x^3 fr x_cubed; @@ -265,7 +266,8 @@ TEST(fr, split_into_endomorphism_scalars) k1.self_to_montgomery_form(); k2.self_to_montgomery_form(); - result = k2 * fr::beta(); + fr lambda = fr::cube_root_of_unity(); + result = k2 * lambda; result = k1 - result; result.self_from_montgomery_form(); @@ -287,7 +289,8 @@ TEST(fr, split_into_endomorphism_scalars_simple) k1.self_to_montgomery_form(); k2.self_to_montgomery_form(); - result = k2 * fr::beta(); + fr lambda = fr::cube_root_of_unity(); + result = k2 * lambda; result = k1 - result; result.self_from_montgomery_form(); diff --git a/cpp/src/aztec/ecc/curves/bn254/g2.test.cpp b/cpp/src/aztec/ecc/curves/bn254/g2.test.cpp index 1002e9c01e..c855bcb32b 100644 --- a/cpp/src/aztec/ecc/curves/bn254/g2.test.cpp +++ b/cpp/src/aztec/ecc/curves/bn254/g2.test.cpp @@ -344,16 +344,35 @@ TEST(g2, group_exponentiation_consistency_check) TEST(g2, serialize) { - g2::affine_element expected = g2::affine_element(g2::element::random_element()); + // test serializing random points + size_t num_repetitions(1); + for (size_t i = 0; i < num_repetitions; i++) { + g2::affine_element expected = g2::affine_element(g2::element::random_element()); - uint8_t buffer[sizeof(g2::affine_element)]; + uint8_t buffer[sizeof(g2::affine_element)]; - g2::affine_element::serialize_to_buffer(expected, buffer); + g2::affine_element::serialize_to_buffer(expected, buffer); - g2::affine_element result = g2::affine_element::serialize_from_buffer(buffer); + g2::affine_element result = g2::affine_element::serialize_from_buffer(buffer); - EXPECT_EQ(result == expected, true); + EXPECT_EQ(result == expected, true); + } + + // test serializing the point at infinity + { + g2::affine_element expected = g2::affine_element(g2::element::random_element()); + expected.self_set_infinity(); + uint8_t buffer[sizeof(g2::affine_element)]; + + g2::affine_element::serialize_to_buffer(expected, buffer); + + g2::affine_element result = g2::affine_element::serialize_from_buffer(buffer); + + ASSERT_TRUE(result.is_point_at_infinity()); + EXPECT_EQ(result == expected, true); + } } + template void write(const T t) { FILE* fp = fopen("/dev/null", "wb"); diff --git a/cpp/src/aztec/ecc/curves/bn254/scalar_multiplication/pippenger.cpp b/cpp/src/aztec/ecc/curves/bn254/scalar_multiplication/pippenger.cpp index 367f09ace5..4b7d0f2a48 100644 --- a/cpp/src/aztec/ecc/curves/bn254/scalar_multiplication/pippenger.cpp +++ b/cpp/src/aztec/ecc/curves/bn254/scalar_multiplication/pippenger.cpp @@ -1,6 +1,5 @@ #include "pippenger.hpp" #include - namespace barretenberg { namespace scalar_multiplication { @@ -14,23 +13,27 @@ Pippenger::Pippenger(g1::affine_element* points, size_t num_points) scalar_multiplication::generate_pippenger_point_table(monomials_, monomials_, num_points); } -Pippenger::Pippenger(uint8_t const* points, size_t num_points) +Pippenger::Pippenger(uint8_t const* points, size_t num_points, bool is_lagrange) : num_points_(num_points) { monomials_ = point_table_alloc(num_points); - monomials_[0] = barretenberg::g1::affine_one; + size_t index = 0; + if (!is_lagrange) { + monomials_[0] = barretenberg::g1::affine_one; + index = 1; + } - barretenberg::io::read_g1_elements_from_buffer(&monomials_[1], (char*)points, (num_points - 1) * 64); + barretenberg::io::read_g1_elements_from_buffer(&monomials_[index], (char*)points, (num_points - index) * 64); barretenberg::scalar_multiplication::generate_pippenger_point_table(monomials_, monomials_, num_points); } -Pippenger::Pippenger(std::string const& path, size_t num_points) +Pippenger::Pippenger(std::string const& path, size_t num_points, bool is_lagrange) : num_points_(num_points) { monomials_ = point_table_alloc(num_points); - barretenberg::io::read_transcript_g1(monomials_, num_points, path); + barretenberg::io::read_transcript_g1(monomials_, num_points, path, is_lagrange); barretenberg::scalar_multiplication::generate_pippenger_point_table(monomials_, monomials_, num_points); } diff --git a/cpp/src/aztec/ecc/curves/bn254/scalar_multiplication/pippenger.hpp b/cpp/src/aztec/ecc/curves/bn254/scalar_multiplication/pippenger.hpp index 6c2fc0044f..fca62ae2f9 100644 --- a/cpp/src/aztec/ecc/curves/bn254/scalar_multiplication/pippenger.hpp +++ b/cpp/src/aztec/ecc/curves/bn254/scalar_multiplication/pippenger.hpp @@ -41,9 +41,9 @@ class Pippenger { */ Pippenger(g1::affine_element* points, size_t num_points); - Pippenger(uint8_t const* points, size_t num_points); + Pippenger(uint8_t const* points, size_t num_points, bool is_lagrange = false); - Pippenger(std::string const& path, size_t num_points); + Pippenger(std::string const& path, size_t num_points, bool is_lagrange = false); ~Pippenger(); diff --git a/cpp/src/aztec/ecc/curves/bn254/scalar_multiplication/scalar_multiplication.cpp b/cpp/src/aztec/ecc/curves/bn254/scalar_multiplication/scalar_multiplication.cpp index 7b97a47170..cc2b74ec19 100644 --- a/cpp/src/aztec/ecc/curves/bn254/scalar_multiplication/scalar_multiplication.cpp +++ b/cpp/src/aztec/ecc/curves/bn254/scalar_multiplication/scalar_multiplication.cpp @@ -104,9 +104,10 @@ namespace scalar_multiplication { void generate_pippenger_point_table(g1::affine_element* points, g1::affine_element* table, size_t num_points) { // iterate backwards, so that `points` and `table` can point to the same memory location + fq beta = fq::cube_root_of_unity(); for (size_t i = num_points - 1; i < num_points; --i) { table[i * 2] = points[i]; - table[i * 2 + 1].x = fq::beta() * points[i].x; + table[i * 2 + 1].x = beta * points[i].x; table[i * 2 + 1].y = -points[i].y; } } @@ -552,7 +553,6 @@ uint32_t construct_addition_chains(affine_product_runtime_state& state, bool emp state.bit_offsets[i] = 0; } - // TODO: measure whether this is useful. `count_bits` has a nasty nested loop that, // theoretically, can be unrolled using templated methods. // However, explicitly unrolling the loop by using recursive template calls was slower! // Inner loop is currently bounded by a constexpr variable, need to see what the compiler does with that... @@ -880,7 +880,7 @@ g1::element pippenger(fr* scalars, if (num_initial_points <= threshold) { std::vector exponentiation_results(num_initial_points); // might as well multithread this... - // TODO: implement Strauss algorithm for small numbers of points. + // Possible optimization: use group::batch_mul_with_endomorphism here. #ifndef NO_MULTITHREADING #pragma omp parallel for #endif diff --git a/cpp/src/aztec/ecc/curves/bn254/scalar_multiplication/scalar_multiplication.test.cpp b/cpp/src/aztec/ecc/curves/bn254/scalar_multiplication/scalar_multiplication.test.cpp index 8ae1e7671e..37f807e5fd 100644 --- a/cpp/src/aztec/ecc/curves/bn254/scalar_multiplication/scalar_multiplication.test.cpp +++ b/cpp/src/aztec/ecc/curves/bn254/scalar_multiplication/scalar_multiplication.test.cpp @@ -18,7 +18,6 @@ namespace { auto& engine = numeric::random::get_debug_engine(); } -// TODO: refactor pippenger tests. These are a hot mess... TEST(scalar_multiplication, reduce_buckets_simple) { constexpr size_t num_points = 128; @@ -517,10 +516,11 @@ TEST(scalar_multiplication, endomorphism_split) g1::element result; g1::element t1 = g1::affine_one * k1; - g1::affine_element beta = g1::affine_one; - beta.x = beta.x * fq::beta(); - beta.y = -beta.y; - g1::element t2 = beta * k2; + g1::affine_element generator = g1::affine_one; + fq beta = fq::cube_root_of_unity(); + generator.x = generator.x * beta; + generator.y = -generator.y; + g1::element t2 = generator * k2; result = t1 + t2; EXPECT_EQ(result == expected, true); diff --git a/cpp/src/aztec/ecc/curves/grumpkin/grumpkin.test.cpp b/cpp/src/aztec/ecc/curves/grumpkin/grumpkin.test.cpp index 794a2d4555..0cf2d7e1e6 100644 --- a/cpp/src/aztec/ecc/curves/grumpkin/grumpkin.test.cpp +++ b/cpp/src/aztec/ecc/curves/grumpkin/grumpkin.test.cpp @@ -400,7 +400,7 @@ TEST(grumpkin, batch_mul) // https://github.com/AztecProtocol/aztec2-internal/issues/437 TEST(grumpkin, bad_points) { - auto beta = grumpkin::fr::beta(); + auto beta = grumpkin::fr::cube_root_of_unity(); auto beta_sqr = beta * beta; bool res = true; grumpkin::fr c(1); diff --git a/cpp/src/aztec/ecc/curves/secp256k1/secp256k1.cpp b/cpp/src/aztec/ecc/curves/secp256k1/secp256k1.cpp index 1b5d1c89e3..ed7bd89172 100644 --- a/cpp/src/aztec/ecc/curves/secp256k1/secp256k1.cpp +++ b/cpp/src/aztec/ecc/curves/secp256k1/secp256k1.cpp @@ -5,9 +5,12 @@ namespace { constexpr size_t max_num_generators = 1 << 10; static std::array generators; -// static bool init_generators = false; +static bool init_generators = false; + } // namespace -/** TODO: CHANGE (this can't be used due to compressed representation) + +/* In case where prime bit length is 256, the method produces a generator, but only with one less bit of randomness than +the maximum possible, as the y coordinate in that case is determined by the x-coordinate. */ g1::affine_element get_generator(const size_t generator_index) { if (!init_generators) { @@ -17,5 +20,4 @@ g1::affine_element get_generator(const size_t generator_index) ASSERT(generator_index < max_num_generators); return generators[generator_index]; } -**/ } // namespace secp256k1 \ No newline at end of file diff --git a/cpp/src/aztec/ecc/curves/secp256k1/secp256k1.hpp b/cpp/src/aztec/ecc/curves/secp256k1/secp256k1.hpp index 6e9c51d5bb..1d9051faf0 100644 --- a/cpp/src/aztec/ecc/curves/secp256k1/secp256k1.hpp +++ b/cpp/src/aztec/ecc/curves/secp256k1/secp256k1.hpp @@ -7,6 +7,7 @@ #include "../../groups/group.hpp" #include "../bn254/fq.hpp" #include "../bn254/fr.hpp" +#include "../types.hpp" namespace secp256k1 { @@ -57,10 +58,10 @@ struct Secp256k1FqParams { static constexpr uint64_t r_inv = 15580212934572586289ULL; - static constexpr uint64_t cube_root_0 = 0UL; - static constexpr uint64_t cube_root_1 = 0UL; - static constexpr uint64_t cube_root_2 = 0UL; - static constexpr uint64_t cube_root_3 = 0UL; + static constexpr uint64_t cube_root_0 = 0x58a4361c8e81894eULL; + static constexpr uint64_t cube_root_1 = 0x03fde1631c4b80afULL; + static constexpr uint64_t cube_root_2 = 0xf8e98978d02e3905ULL; + static constexpr uint64_t cube_root_3 = 0x7a4a36aebcbb3d53ULL; static constexpr uint64_t primitive_root_0 = 0UL; static constexpr uint64_t primitive_root_1 = 0UL; @@ -96,10 +97,26 @@ struct Secp256k1FrParams { 0, 0, 0, 0, 0, 0, 0, 0, }; - static constexpr uint64_t cube_root_0 = 0UL; - static constexpr uint64_t cube_root_1 = 0UL; - static constexpr uint64_t cube_root_2 = 0UL; - static constexpr uint64_t cube_root_3 = 0UL; + static constexpr uint64_t cube_root_0 = 0xf07deb3dc9926c9eULL; + static constexpr uint64_t cube_root_1 = 0x2c93e7ad83c6944cULL; + static constexpr uint64_t cube_root_2 = 0x73a9660652697d91ULL; + static constexpr uint64_t cube_root_3 = 0x532840178558d639ULL; + + static constexpr uint64_t endo_minus_b1_lo = 0x6F547FA90ABFE4C3ULL; + static constexpr uint64_t endo_minus_b1_mid = 0xE4437ED6010E8828ULL; + + static constexpr uint64_t endo_b2_lo = 0xe86c90e49284eb15ULL; + static constexpr uint64_t endo_b2_mid = 0x3086d221a7d46bcdULL; + + static constexpr uint64_t endo_g1_lo = 0xE893209A45DBB031ULL; + static constexpr uint64_t endo_g1_mid = 0x3DAA8A1471E8CA7FULL; + static constexpr uint64_t endo_g1_hi = 0xE86C90E49284EB15ULL; + static constexpr uint64_t endo_g1_hihi = 0x3086D221A7D46BCDULL; + + static constexpr uint64_t endo_g2_lo = 0x1571B4AE8AC47F71ULL; + static constexpr uint64_t endo_g2_mid = 0x221208AC9DF506C6ULL; + static constexpr uint64_t endo_g2_hi = 0x6F547FA90ABFE4C4ULL; + static constexpr uint64_t endo_g2_hihi = 0xE4437ED6010E8828ULL; static constexpr uint64_t primitive_root_0 = 0UL; static constexpr uint64_t primitive_root_1 = 0UL; @@ -128,7 +145,5 @@ struct Secp256k1G1Params { typedef barretenberg:: group, barretenberg::field, Secp256k1G1Params> g1; -/* TODO (#LARGE_MODULUS_AFFINE_POINT_COMPRESSION): Rewrite this test after designing point compression for p>2^255 g1::affine_element get_generator(const size_t generator_index); -*/ } // namespace secp256k1 \ No newline at end of file diff --git a/cpp/src/aztec/ecc/curves/secp256k1/secp256k1.test.cpp b/cpp/src/aztec/ecc/curves/secp256k1/secp256k1.test.cpp index 98b8c39196..5b5186019f 100644 --- a/cpp/src/aztec/ecc/curves/secp256k1/secp256k1.test.cpp +++ b/cpp/src/aztec/ecc/curves/secp256k1/secp256k1.test.cpp @@ -473,7 +473,6 @@ TEST(secp256k1, group_exponentiation_consistency_check) EXPECT_EQ(result == expected, true); } -/** TODO (#LARGE_MODULUS_AFFINE_POINT_COMPRESSION): Rewrite this test after designing point compression for p>2^255 TEST(secp256k1, derive_generators) { constexpr size_t num_generators = 128; @@ -493,7 +492,83 @@ TEST(secp256k1, derive_generators) EXPECT_EQ(result[k].on_curve(), true); } } -*/ + +TEST(secp256k1, get_endomorphism_scalars) +{ + for (size_t i = 0; i < 2048; i++) { + secp256k1::fr k = secp256k1::fr::random_element(); + secp256k1::fr k1 = 0; + secp256k1::fr k2 = 0; + + secp256k1::fr::split_into_endomorphism_scalars(k, k1, k2); + bool k1_neg = false; + bool k2_neg = false; + + if (k2.uint256_t_no_montgomery_conversion().get_msb() > 200) { + k2 = -k2; + k2_neg = true; + } + + EXPECT_LT(k1.uint256_t_no_montgomery_conversion().get_msb(), 129ULL); + EXPECT_LT(k2.uint256_t_no_montgomery_conversion().get_msb(), 129ULL); + + if (k1_neg) { + k1 = -k1; + } + if (k2_neg) { + k2 = -k2; + } + + k1.self_to_montgomery_form(); + k2.self_to_montgomery_form(); + + secp256k1::fr beta = secp256k1::fr::cube_root_of_unity(); + secp256k1::fr expected = k1 - k2 * beta; + + expected.self_from_montgomery_form(); + EXPECT_EQ(k, expected); + } +} + +TEST(secp256k1, test_endomorphism_scalars) +{ + secp256k1::fr k = secp256k1::fr::random_element(); + secp256k1::fr k1 = 0; + secp256k1::fr k2 = 0; + + secp256k1::fr::split_into_endomorphism_scalars(k, k1, k2); + bool k1_neg = false; + bool k2_neg = false; + + if (k1.uint256_t_no_montgomery_conversion().get_msb() > 200) { + k1 = -k1; + k1_neg = true; + } + if (k2.uint256_t_no_montgomery_conversion().get_msb() > 200) { + k2 = -k2; + k2_neg = true; + } + + EXPECT_LT(k1.uint256_t_no_montgomery_conversion().get_msb(), 129ULL); + EXPECT_LT(k2.uint256_t_no_montgomery_conversion().get_msb(), 129ULL); + + if (k1_neg) { + k1 = -k1; + } + if (k2_neg) { + k2 = -k2; + } + k1.self_to_montgomery_form(); + k2.self_to_montgomery_form(); + static const uint256_t secp256k1_const_lambda{ + 0xDF02967C1B23BD72ULL, 0x122E22EA20816678UL, 0xA5261C028812645AULL, 0x5363AD4CC05C30E0ULL + }; + + secp256k1::fr expected = k1 - k2 * secp256k1_const_lambda; + + expected.self_from_montgomery_form(); + EXPECT_EQ(k, expected); +} TEST(secp256k1, neg_and_self_neg_0_cmp_regression) { diff --git a/cpp/src/aztec/ecc/curves/secp256k1/secp256k1_endo_notes.hpp b/cpp/src/aztec/ecc/curves/secp256k1/secp256k1_endo_notes.hpp new file mode 100644 index 0000000000..61a2a6ab91 --- /dev/null +++ b/cpp/src/aztec/ecc/curves/secp256k1/secp256k1_endo_notes.hpp @@ -0,0 +1,164 @@ +#pragma once + +#include +#include "secp256k1.hpp" + +namespace secp256k1_params { +struct basis_vectors { + uint64_t endo_g1_lo = 0; + uint64_t endo_g1_mid = 0; + uint64_t endo_g1_hi = 0; + uint64_t endo_g1_hihi = 0; + uint64_t endo_g2_lo = 0; + uint64_t endo_g2_mid = 0; + uint64_t endo_g2_hi = 0; + uint64_t endo_g2_hihi = 0; + uint64_t endo_minus_b1_lo = 0; + uint64_t endo_minus_b1_mid = 0; + uint64_t endo_b2_lo = 0; + uint64_t endo_b2_mid = 0; + uint64_t endo_a1_lo = 0; + uint64_t endo_a1_mid = 0; + uint64_t endo_a1_hi = 0; + uint64_t endo_a2_lo = 0; + uint64_t endo_a2_mid = 0; + uint64_t endo_a2_hi = 0; + + bool real = false; +}; +static basis_vectors get_endomorphism_basis_vectors(const secp256k1::fr& lambda) +{ + uint512_t approximate_square_root; + uint512_t z = (uint512_t(secp256k1::fr::modulus) + uint512_t(2)) >> 1; + uint512_t y = uint512_t(secp256k1::fr::modulus); + while (z < y) { + y = z; + z = (uint512_t(secp256k1::fr::modulus) / z + z) >> 1; + } + approximate_square_root = y; + // Run the extended greatest common divisor algorithm until out * (\lambda + 1) < approximate_square_root + + uint512_t u(lambda); + uint512_t v(secp256k1::fr::modulus); + uint512_t x1 = 1; + uint512_t y1 = 0; + uint512_t x2 = 0; + uint512_t y2 = 1; + + uint512_t a0 = 0; + uint512_t b0 = 0; + + uint512_t a1 = 0; + uint512_t b1 = 0; + + uint512_t a2 = 0; + uint512_t b2 = 0; + + uint512_t prevOut = 0; + uint512_t i = 0; + uint512_t out = 0; + uint512_t x = 0; + + while (u != 0) { + uint512_t q = v / u; + out = v - uint512_t(uint512_t(q) * uint512_t(u)); + x = x2 - (q * x1); + uint512_t y = y2 - (q * y1); + if ((a1 == 0) && (out < approximate_square_root)) { + a0 = -prevOut; + b0 = x1; + a1 = -out; + b1 = x; + } else if ((a1 > 0) && (++i == 2)) { + break; + } + prevOut = out; + + v = u; + u = out; + x2 = x1; + x1 = x; + y2 = y1; + y1 = y; + } + + a2 = -out; + b2 = x; + + uint512_t len1 = (a1 * a1) + (b1 * b1); + uint512_t len2 = (a2 * a2) + (b2 * b2); + if (len2 >= len1) { + a2 = a0; + b2 = b0; + } + + if (a1.get_msb() >= 128) { + a1 = -a1; + b1 = -b1; + } + if (a2.get_msb() >= 128) { + a2 = -a2; + b2 = -b2; + } + + uint512_t minus_b1 = -b1; + uint512_t shift256 = uint512_t(1) << 384; + uint512_t g1 = (-b1 * shift256) / uint512_t(secp256k1::fr::modulus); + uint512_t g2 = (b2 * shift256) / uint512_t(secp256k1::fr::modulus); + + basis_vectors result; + result.endo_g1_lo = g1.lo.data[0]; + result.endo_g1_mid = g1.lo.data[1]; + result.endo_g1_hi = g1.lo.data[2]; + result.endo_g1_hihi = g1.lo.data[3]; + result.endo_g2_lo = g2.lo.data[0]; + result.endo_g2_mid = g2.lo.data[1]; + result.endo_g2_hi = g2.lo.data[2]; + result.endo_g2_hihi = g2.lo.data[3]; + result.endo_minus_b1_lo = minus_b1.lo.data[0]; + result.endo_minus_b1_mid = minus_b1.lo.data[1]; + result.endo_b2_lo = b2.lo.data[0]; + result.endo_b2_mid = b2.lo.data[1]; + result.endo_a1_lo = a1.lo.data[0]; + result.endo_a1_mid = a1.lo.data[1]; + result.endo_a1_hi = a1.lo.data[2]; + result.endo_a2_lo = a2.lo.data[0]; + result.endo_a2_mid = a2.lo.data[1]; + result.endo_a2_hi = a2.lo.data[2]; + return result; +} + +static std::pair get_endomorphism_scalars() +{ + // find beta \in secp256k1::fq and lambda \in secp256k1::fr such that: + + // 1. beta^3 = 1 mod q + // 2. lambda^3 = 1 mod r + // 3. for [P] \in G with coordinates (P.x, P.y) \in secp256k1::fq: + // \lambda.[P] = (\beta . P.x, P.y) + const secp256k1::fq beta = secp256k1::fq::cube_root_of_unity(); + const secp256k1::fr lambda = secp256k1::fr::cube_root_of_unity(); + + if (beta * beta * beta != secp256k1::fq(1)) { + std::cerr << "beta is not a cube root of unity" << std::endl; + } + if (lambda * lambda * lambda != secp256k1::fr(1)) { + std::cerr << "lambda is not a cube root of unity" << std::endl; + } + + secp256k1::g1::element P = secp256k1::g1::one; + secp256k1::g1::element endoP = P; + endoP.x *= beta; + + if (P * lambda == endoP) { + return { beta, lambda }; + } + endoP.x *= beta; + if ((P * lambda) == endoP) { + return { beta * beta, lambda }; + } + endoP.y = -endoP.y; + std::cerr << "could not find endomorphism scalars???" << std::endl; + return { secp256k1::fq(0), secp256k1::fr(0) }; +} +}; // namespace secp256k1_params \ No newline at end of file diff --git a/cpp/src/aztec/ecc/curves/secp256r1/secp256r1.cpp b/cpp/src/aztec/ecc/curves/secp256r1/secp256r1.cpp index 447e21bb3f..676ec6c3db 100644 --- a/cpp/src/aztec/ecc/curves/secp256r1/secp256r1.cpp +++ b/cpp/src/aztec/ecc/curves/secp256r1/secp256r1.cpp @@ -5,11 +5,12 @@ namespace { constexpr size_t max_num_generators = 1 << 10; static std::array generators; -// TODO (#LARGE_MODULUS_AFFINE_POINT_COMPRESSION): Rewrite this test after designing point compression for p>2^255 -// static bool init_generators = false; +static bool init_generators = false; } // namespace -/** TODO (#LARGE_MODULUS_AFFINE_POINT_COMPRESSION): Rewrite this test after designing point compression for p>2^255 + +/* In case where prime bit length is 256, the method produces a generator, but only with one less bit of randomness than +the maximum possible, as the y coordinate in that case is determined by the x-coordinate. */ g1::affine_element get_generator(const size_t generator_index) { if (!init_generators) { @@ -18,5 +19,5 @@ g1::affine_element get_generator(const size_t generator_index) } ASSERT(generator_index < max_num_generators); return generators[generator_index]; -}**/ +} } // namespace secp256r1 \ No newline at end of file diff --git a/cpp/src/aztec/ecc/curves/secp256r1/secp256r1.hpp b/cpp/src/aztec/ecc/curves/secp256r1/secp256r1.hpp index 173f27ccca..461aecb029 100644 --- a/cpp/src/aztec/ecc/curves/secp256r1/secp256r1.hpp +++ b/cpp/src/aztec/ecc/curves/secp256r1/secp256r1.hpp @@ -132,8 +132,5 @@ struct Secp256r1G1Params { typedef barretenberg:: group, barretenberg::field, Secp256r1G1Params> g1; - -/** TODO (#LARGE_MODULUS_AFFINE_POINT_COMPRESSION): Rewrite this test after designing point compression for p>2^255 g1::affine_element get_generator(const size_t generator_index); -**/ } // namespace secp256r1 \ No newline at end of file diff --git a/cpp/src/aztec/ecc/curves/types.hpp b/cpp/src/aztec/ecc/curves/types.hpp new file mode 100644 index 0000000000..9bb3026ebf --- /dev/null +++ b/cpp/src/aztec/ecc/curves/types.hpp @@ -0,0 +1,5 @@ +#pragma once + +namespace waffle { +enum CurveType { BN254, SECP256K1, SECP256R1, GRUMPKIN }; +} diff --git a/cpp/src/aztec/ecc/fields/field.hpp b/cpp/src/aztec/ecc/fields/field.hpp index 973ace6af0..41c2abe3e1 100644 --- a/cpp/src/aztec/ecc/fields/field.hpp +++ b/cpp/src/aztec/ecc/fields/field.hpp @@ -99,6 +99,11 @@ template struct alignas(32) field { return uint256_t(out.data[0], out.data[1], out.data[2], out.data[3]); } + constexpr uint256_t uint256_t_no_montgomery_conversion() const noexcept + { + return uint256_t(data[0], data[1], data[2], data[3]); + } + constexpr field(const field& other) = default; constexpr field& operator=(const field& other) = default; @@ -107,14 +112,20 @@ template struct alignas(32) field { static constexpr uint256_t modulus = uint256_t{ Params::modulus_0, Params::modulus_1, Params::modulus_2, Params::modulus_3 }; - static constexpr field beta() + static constexpr field cube_root_of_unity() { - // TODO: move this into group, so that we can pick cube roots over both Fq and Fr that align with the curve - // endomorphism i.e. lambda * [P] = (beta * x, y) constexpr field two_inv = field(2).invert(); constexpr field - // numerator = (-field(3)).sqrt() - field(1); constexpr field result = two_inv * numerator; - constexpr field result = - field(Params::cube_root_0, Params::cube_root_1, Params::cube_root_2, Params::cube_root_3); - return result; + // endomorphism i.e. lambda * [P] = (beta * x, y) + if constexpr (Params::cube_root_0 != 0) { + constexpr field result{ + Params::cube_root_0, Params::cube_root_1, Params::cube_root_2, Params::cube_root_3 + }; + return result; + } else { + constexpr field two_inv = field(2).invert(); + constexpr field numerator = (-field(3)).sqrt() - field(1); + constexpr field result = two_inv * numerator; + return result; + } } static constexpr field zero() { return field(0, 0, 0, 0); } @@ -145,7 +156,7 @@ template struct alignas(32) field { static constexpr field coset_generator(const size_t idx) { - ASSERT(idx < 7); // TODO: well-named constants for enforcing PI elements disjointess instead of this + ASSERT(idx < 7); const field result{ Params::coset_generators_0[idx], Params::coset_generators_1[idx], @@ -231,6 +242,7 @@ template struct alignas(32) field { return *this; } } + /** * For short Weierstrass curves y^2 = x^3 + b mod r, if there exists a cube root of unity mod r, * we can take advantage of an enodmorphism to decompose a 254 bit scalar into 2 128 bit scalars. @@ -250,19 +262,22 @@ template struct alignas(32) field { * * To find k1, k2, We use the extended euclidean algorithm to find 4 short scalars [a1, a2], [b1, b2] such that * modulus = (a1 * b2) - (b1 * a2) - * We then compube scalars c1 = round(b2 * k / r), c2 = round(b1 * k / r), where + * We then compute scalars c1 = round(b2 * k / r), c2 = round(b1 * k / r), where * k1 = (c1 * a1) + (c2 * a2), k2 = -((c1 * b1) + (c2 * b2)) * We pre-compute scalars g1 = (2^256 * b1) / n, g2 = (2^256 * b2) / n, to avoid having to perform long division * on 512-bit scalars **/ static void split_into_endomorphism_scalars(const field& k, field& k1, field& k2) { + // if the modulus is a 256-bit integer, we need to use a basis where g1, g2 have been shifted by 2^384 + if constexpr (Params::modulus_3 >= 0x4000000000000000ULL) { + split_into_endomorphism_scalars_384(k, k1, k2); + return; + } field input = k.reduce_once(); // uint64_t lambda_reduction[4] = { 0 }; // __to_montgomery_form(lambda, lambda_reduction); - // TODO: these parameters only work for the bn254 coordinate field. - // Need to shift into Params and calculate correct constants for the subgroup field constexpr field endo_g1 = { Params::endo_g1_lo, Params::endo_g1_mid, Params::endo_g1_hi, 0 }; constexpr field endo_g2 = { Params::endo_g2_lo, Params::endo_g2_mid, 0, 0 }; @@ -278,7 +293,6 @@ template struct alignas(32) field { // (the bit shifts are implicit, as we only utilize the high limbs of c1, c2 - // TODO remove data duplication field c1_hi = { c1.data[4], c1.data[5], c1.data[6], c1.data[7] }; // *(field*)((uintptr_t)(&c1) + (4 * sizeof(uint64_t))); @@ -291,18 +305,69 @@ template struct alignas(32) field { // compute q2 = c2 * b2 wide_array q2 = c2_hi.mul_512(endo_b2); - // TODO: this doesn't have to be a 512-bit multiply... + // FIX: Avoid using 512-bit multiplication as its not necessary. + // c1_hi, c2_hi can be uint256_t's and the final result (without montgomery reduction) + // could be casted to a field. field q1_lo{ q1.data[0], q1.data[1], q1.data[2], q1.data[3] }; field q2_lo{ q2.data[0], q2.data[1], q2.data[2], q2.data[3] }; field t1 = (q2_lo - q1_lo).reduce_once(); - field t2 = (t1 * beta() + input).reduce_once(); + field beta = cube_root_of_unity(); + field t2 = (t1 * beta + input).reduce_once(); k2.data[0] = t1.data[0]; k2.data[1] = t1.data[1]; k1.data[0] = t2.data[0]; k1.data[1] = t2.data[1]; } + static void split_into_endomorphism_scalars_384(const field& input, field& k1_out, field& k2_out) + { + + constexpr field minus_b1f{ + Params::endo_minus_b1_lo, + Params::endo_minus_b1_mid, + 0, + 0, + }; + constexpr field b2f{ + Params::endo_b2_lo, + Params::endo_b2_mid, + 0, + 0, + }; + constexpr uint256_t g1{ + Params::endo_g1_lo, + Params::endo_g1_mid, + Params::endo_g1_hi, + Params::endo_g1_hihi, + }; + constexpr uint256_t g2{ + Params::endo_g2_lo, + Params::endo_g2_mid, + Params::endo_g2_hi, + Params::endo_g2_hihi, + }; + + field kf = input.reduce_once(); + uint256_t k{ kf.data[0], kf.data[1], kf.data[2], kf.data[3] }; + + uint512_t c1 = (uint512_t(k) * uint512_t(g1)) >> 384; + uint512_t c2 = (uint512_t(k) * uint512_t(g2)) >> 384; + + field c1f{ c1.lo.data[0], c1.lo.data[1], c1.lo.data[2], c1.lo.data[3] }; + field c2f{ c2.lo.data[0], c2.lo.data[1], c2.lo.data[2], c2.lo.data[3] }; + + c1f.self_to_montgomery_form(); + c2f.self_to_montgomery_form(); + c1f = c1f * minus_b1f; + c2f = c2f * b2f; + field r2f = c1f - c2f; + field beta = cube_root_of_unity(); + field r1f = input.reduce_once() - r2f * beta; + k1_out = r1f; + k2_out = -r2f; + } + // static constexpr auto coset_generators = compute_coset_generators(); // static constexpr std::array coset_generators = compute_coset_generators((1 << 30U)); diff --git a/cpp/src/aztec/ecc/fields/field12.hpp b/cpp/src/aztec/ecc/fields/field12.hpp index f626f302ac..5b3b2ff2f6 100644 --- a/cpp/src/aztec/ecc/fields/field12.hpp +++ b/cpp/src/aztec/ecc/fields/field12.hpp @@ -220,7 +220,8 @@ template cl constexpr field12 cyclotomic_squared() const { - // TODO: write more efficient version... + // Possible Optimization: The cyclotomic squaring can be implemented more than efficiently + // than the generic squaring. return sqr(); } diff --git a/cpp/src/aztec/ecc/fields/field2.hpp b/cpp/src/aztec/ecc/fields/field2.hpp index a8a70a0447..dc5a8ead27 100644 --- a/cpp/src/aztec/ecc/fields/field2.hpp +++ b/cpp/src/aztec/ecc/fields/field2.hpp @@ -48,7 +48,10 @@ template struct alignas(32) field2 { { return field2{ Params::twist_mul_by_q_y_0, Params::twist_mul_by_q_y_1 }; } - static constexpr field2 beta() { return field2{ Params::twist_cube_root_0, Params::twist_cube_root_1 }; } + static constexpr field2 cube_root_of_unity() + { + return field2{ Params::twist_cube_root_0, Params::twist_cube_root_1 }; + } constexpr field2 operator*(const field2& other) const noexcept; constexpr field2 operator+(const field2& other) const noexcept; diff --git a/cpp/src/aztec/ecc/fields/field_impl.hpp b/cpp/src/aztec/ecc/fields/field_impl.hpp index e8ddb4e7b2..e6e4728393 100644 --- a/cpp/src/aztec/ecc/fields/field_impl.hpp +++ b/cpp/src/aztec/ecc/fields/field_impl.hpp @@ -35,6 +35,7 @@ template constexpr field field::operator*(const field& other) co { if constexpr (BBERG_NO_ASM || (T::modulus_3 >= 0x4000000000000000ULL) || (T::modulus_1 == 0 && T::modulus_2 == 0 && T::modulus_3 == 0)) { + // >= 255-bits or <= 64-bits. return montgomery_mul(other); } else { if (std::is_constant_evaluated()) { @@ -48,6 +49,7 @@ template constexpr field field::operator*=(const field& other) n { if constexpr (BBERG_NO_ASM || (T::modulus_3 >= 0x4000000000000000ULL) || (T::modulus_1 == 0 && T::modulus_2 == 0 && T::modulus_3 == 0)) { + // >= 255-bits or <= 64-bits. *this = operator*(other); } else { if (std::is_constant_evaluated()) { diff --git a/cpp/src/aztec/ecc/fields/field_impl_generic.hpp b/cpp/src/aztec/ecc/fields/field_impl_generic.hpp index 859dfdae1d..064dc7d71c 100644 --- a/cpp/src/aztec/ecc/fields/field_impl_generic.hpp +++ b/cpp/src/aztec/ecc/fields/field_impl_generic.hpp @@ -686,8 +686,8 @@ template constexpr field field::montgomery_square() const noexce t3 = carry_lo + round_carry; return { t0, t1, t2, t3 }; #else - // TODO: apparently the plain old 'mul' operation is faster than the squaring code. - // `square_accumulate` has too many additions and comparisons, to justify the saved multiplications + // We use ‘montgomery_mul' instead of 'square_accumulate'. The number of additions and comparisons in + // 'square_accumulate' makes it slower in this particular case. return montgomery_mul(*this); #endif } diff --git a/cpp/src/aztec/ecc/groups/affine_element.hpp b/cpp/src/aztec/ecc/groups/affine_element.hpp index 0d3e4e5097..766949a518 100644 --- a/cpp/src/aztec/ecc/groups/affine_element.hpp +++ b/cpp/src/aztec/ecc/groups/affine_element.hpp @@ -1,6 +1,8 @@ #pragma once #include #include +#include +#include namespace barretenberg { namespace group_elements { @@ -16,15 +18,16 @@ template class alignas(64) affine_el /** * @brief Reconstruct a point in affine coordinates from compressed form. + * @details #LARGE_MODULUS_AFFINE_POINT_COMPRESSION Point compression is only implemented for curves of a prime + * field F_p with p using < 256 bits. One possiblity for extending to a 256-bit prime field: + * https://patents.google.com/patent/US6252960B1/en. * - * @tparam BaseField Coordinate field class - * @tparam CompileTimeEnabled Checks that the modulus of BaseField is < 2**255, otherwise disables the function - * - * @param compressed Compressed point + * @param compressed compressed point + * @return constexpr affine_element */ template > 255) == uint256_t(0), void>> - static constexpr std::pair deserialize(const uint256_t& compressed) noexcept; + static constexpr affine_element from_compressed(const uint256_t& compressed) noexcept; constexpr affine_element& operator=(const affine_element& other) noexcept; @@ -32,7 +35,7 @@ template class alignas(64) affine_el template > 255) == uint256_t(0), void>> - explicit constexpr operator uint256_t() const noexcept; + constexpr uint256_t compress() const noexcept; constexpr affine_element set_infinity() const noexcept; constexpr void self_set_infinity() noexcept; @@ -44,15 +47,9 @@ template class alignas(64) affine_el /** * @brief Hash a seed value to curve. * - * @tparam BaseField Coordinate field - * @tparam CompileTimeEnabled Checks that the modulus of BaseField is < 2**255, otherwise disables the function - * - * @return if the seed lands on a point if not. + * @return A point on the curve corresponding to the given seed */ - template > 255) == uint256_t(0), void>> - static std::pair hash_to_curve(const uint64_t seed) noexcept; + static affine_element hash_to_curve(const uint64_t seed) noexcept; constexpr bool operator==(const affine_element& other) const noexcept; @@ -64,52 +61,82 @@ template class alignas(64) affine_el /** * @brief Serialize the point to the given buffer * - * @tparam BaseField Coordinate field - * @tparam CompileTimeEnabled Checks that the modulus of BaseField is < 2**255, otherwise disables the function + * @details We support serializing the point at infinity for curves defined over a barretenberg::field (i.e., a + * native field of prime order) and for points of barretenberg::g2. + * + * @warning This will need to be updated if we serialize points over composite-order fields other than fq2! * */ - template > 255) == uint256_t(0), void>> static void serialize_to_buffer(const affine_element& value, uint8_t* buffer) { - Fq::serialize_to_buffer(value.y, buffer); - Fq::serialize_to_buffer(value.x, buffer + sizeof(Fq)); if (value.is_point_at_infinity()) { - buffer[0] = buffer[0] | (1 << 7); + if constexpr (Fq::modulus.get_msb() == 255) { + write(buffer, uint256_t(0)); + write(buffer, Fq::modulus); + } else { + write(buffer, uint256_t(0)); + write(buffer, uint256_t(1) << 255); + } + } else { + Fq::serialize_to_buffer(value.y, buffer); + Fq::serialize_to_buffer(value.x, buffer + sizeof(Fq)); } } + /** * @brief Restore point from a buffer * - * @tparam BaseField Coordinate field - * @tparam CompileTimeEnabled Checks that the modulus of BaseField is < 2**255, otherwise disables the function - * * @param buffer Buffer from which we deserialize the point * * @return Deserialized point + * + * @details We support serializing the point at infinity for curves defined over a barretenberg::field (i.e., a + * native field of prime order) and for points of barretenberg::g2. + * + * @warning This will need to be updated if we serialize points over composite-order fields other than fq2! */ - template > 255) == uint256_t(0), void>> static affine_element serialize_from_buffer(uint8_t* buffer) { affine_element result; - result.y = Fq::serialize_from_buffer(buffer); - result.x = Fq::serialize_from_buffer(buffer + sizeof(Fq)); - if (((buffer[0] >> 7) & 1) == 1) { - result.self_set_infinity(); + + // need to read a raw uint256_t to avoid reductions so we can check whether the point is the point at infinity + uint256_t raw_x = from_buffer(buffer + sizeof(Fq)); + + if constexpr (Fq::modulus.get_msb() == 255) { + if (raw_x == Fq::modulus) { + result.y = Fq::zero(); + result.x.data[0] = raw_x.data[0]; + result.x.data[1] = raw_x.data[1]; + result.x.data[2] = raw_x.data[2]; + result.x.data[3] = raw_x.data[3]; + } else { + result.y = Fq::serialize_from_buffer(buffer); + result.x = Fq(raw_x); + } + } else { + if (raw_x.get_msb() == 255) { + result.y = Fq::zero(); + result.x = Fq::zero(); + result.self_set_infinity(); + } else { + // conditional here to avoid reading the same data twice in case of a field of prime order + if constexpr (std::is_same::value) { + result.y = Fq::serialize_from_buffer(buffer); + result.x = Fq::serialize_from_buffer(buffer + sizeof(Fq)); + } else { + result.y = Fq::serialize_from_buffer(buffer); + result.x = Fq(raw_x); + } + } } return result; } + /** * @brief Serialize the point to a byte vector * - * @tparam BaseField Coordinate field - * @tparam CompileTimeEnabled Checks that the modulus of BaseField is < 2**255, otherwise disables the function - * * @return Vector with serialized representation of the point */ - template > 255) == uint256_t(0), void>> inline std::vector to_buffer() const { std::vector buffer(sizeof(affine_element)); diff --git a/cpp/src/aztec/ecc/groups/affine_element.test.cpp b/cpp/src/aztec/ecc/groups/affine_element.test.cpp index a7ad49abc1..fbeb22a85b 100644 --- a/cpp/src/aztec/ecc/groups/affine_element.test.cpp +++ b/cpp/src/aztec/ecc/groups/affine_element.test.cpp @@ -1,40 +1,94 @@ #include +#include #include +#include #include #include #include namespace test_affine_element { +template class test_affine_element : public testing::Test { + using element = typename G1::element; + using affine_element = typename G1::affine_element; -using namespace barretenberg; + public: + static void test_read_write_buffer() + { + // a generic point + { + affine_element P = affine_element(element::random_element()); + affine_element Q; + affine_element R; -TEST(affine_element, read_write_buffer) -{ - g1::affine_element P = g1::affine_element(g1::element::random_element()); - g1::affine_element Q; - g1::affine_element R; + std::vector v(65); // extra byte to allow a bad read + uint8_t* ptr = v.data(); + affine_element::serialize_to_buffer(P, ptr); + + // bad read + Q = affine_element::serialize_from_buffer(ptr + 1); + ASSERT_FALSE(Q.on_curve() && !Q.is_point_at_infinity()); + ASSERT_FALSE(P == Q); + + // good read + R = affine_element::serialize_from_buffer(ptr); + ASSERT_TRUE(R.on_curve()); + ASSERT_TRUE(P == R); + } + + // point at infinity + { + affine_element P = affine_element(element::random_element()); + P.self_set_infinity(); + affine_element R; + + std::vector v(64); + uint8_t* ptr = v.data(); + affine_element::serialize_to_buffer(P, ptr); + + R = affine_element::serialize_from_buffer(ptr); + ASSERT_TRUE(R.is_point_at_infinity()); + ASSERT_TRUE(P == R); + } + } - std::vector v(64); - uint8_t* ptr = v.data(); - g1::affine_element::serialize_to_buffer(P, ptr); + static void test_point_compression() + { + for (size_t i = 0; i < 10; i++) { + affine_element P = affine_element(element::random_element()); + uint256_t compressed = P.compress(); + affine_element Q = affine_element::from_compressed(compressed); + EXPECT_EQ(P, Q); + } + } - Q = g1::affine_element::serialize_from_buffer(ptr + 1); - ASSERT_FALSE(Q.on_curve() && !Q.is_point_at_infinity()); - R = g1::affine_element::serialize_from_buffer(ptr); - ASSERT_TRUE(R.on_curve()); + // Regression test to ensure that the point at infinity is not equal to its coordinate-wise reduction, which may lie + // on the curve, depending on the y-coordinate. + // TODO: add corresponding typed test class + static void test_infinity_regression() + { + affine_element P; + P.self_set_infinity(); + affine_element R(0, P.y); + ASSERT_FALSE(P == R); + } +}; - ASSERT_FALSE(P == Q); - ASSERT_TRUE(P == R); +typedef testing::Types TestTypes; + +TYPED_TEST_SUITE(test_affine_element, TestTypes); + +TYPED_TEST(test_affine_element, read_write_buffer) +{ + TestFixture::test_read_write_buffer(); } -// Regression test to ensure that the point at infinity is not equal to its coordinate-wise reduction, which may lie -// on the curve, depending on the y-coordinate. -TEST(affine_element, infinity_equality_regression) +TYPED_TEST(test_affine_element, point_compression) { - g1::affine_element P; - P.self_set_infinity(); - g1::affine_element R(0, P.y); - ASSERT_FALSE(P == R); + if constexpr (TypeParam::Fq::modulus.data[3] >= 0x4000000000000000ULL) { + GTEST_SKIP(); + } else { + TestFixture::test_point_compression(); + } } // Regression test to ensure that the point at infinity is not equal to its coordinate-wise reduction, which may lie diff --git a/cpp/src/aztec/ecc/groups/affine_element_impl.hpp b/cpp/src/aztec/ecc/groups/affine_element_impl.hpp index 3bbd47e10a..bfde853c22 100644 --- a/cpp/src/aztec/ecc/groups/affine_element_impl.hpp +++ b/cpp/src/aztec/ecc/groups/affine_element_impl.hpp @@ -22,10 +22,8 @@ constexpr affine_element::affine_element(affine_element&& other) noex {} template - template -constexpr std::pair> affine_element::deserialize( - const uint256_t& compressed) noexcept +constexpr affine_element affine_element::from_compressed(const uint256_t& compressed) noexcept { uint256_t x_coordinate = compressed; x_coordinate.data[3] = x_coordinate.data[3] & (~0x8000000000000000ULL); @@ -38,12 +36,13 @@ constexpr std::pair> affine_element:: } auto [is_quadratic_remainder, y] = y2.sqrt(); if (!is_quadratic_remainder) { - return std::make_pair(false, affine_element(Fq::zero(), Fq::zero())); + return affine_element(Fq::zero(), Fq::zero()); } if (uint256_t(y).get_bit(0) != y_bit) { y = -y; } - return std::make_pair(true, affine_element(x, y)); + + return affine_element(x, y); } template @@ -61,13 +60,14 @@ constexpr affine_element& affine_element::operator=(affine y = other.y; return *this; } + template template -constexpr affine_element::operator uint256_t() const noexcept +constexpr uint256_t affine_element::compress() const noexcept { uint256_t out(x); - if (y.from_montgomery_form().get_bit(0)) { + if (uint256_t(y).get_bit(0)) { out.data[3] = out.data[3] | 0x8000000000000000ULL; } return out; @@ -154,15 +154,38 @@ constexpr bool affine_element::operator>(const affine_element& other) } template -template -std::pair> affine_element::hash_to_curve(const uint64_t seed) noexcept +affine_element affine_element::hash_to_curve(const uint64_t seed) noexcept { static_assert(T::can_hash_to_curve == true); Fq input(seed, 0, 0, 0); keccak256 c = hash_field_element((uint64_t*)&input.data[0]); - uint256_t compressed{ c.word64s[0], c.word64s[1], c.word64s[2], c.word64s[3] }; - return deserialize(compressed); + uint256_t hash{ c.word64s[0], c.word64s[1], c.word64s[2], c.word64s[3] }; + + uint256_t x_coordinate = hash; + + if constexpr (Fq::modulus.data[3] < 0x8000000000000000ULL) { + x_coordinate.data[3] = x_coordinate.data[3] & (~0x8000000000000000ULL); + } + + bool y_bit = hash.get_bit(255); + + Fq x_out = Fq(x_coordinate); + Fq y_out = (x_out.sqr() * x_out + T::b); + if constexpr (T::has_a) { + y_out += (x_out * T::a); + } + + // When the sqrt of y_out doesn't exist, return 0. + auto [is_quadratic_remainder, y_out_] = y_out.sqrt(); + if (!is_quadratic_remainder) { + return affine_element(Fq::zero(), Fq::zero()); + } + if (uint256_t(y_out_).get_bit(0) != y_bit) { + y_out_ = -y_out_; + } + + return affine_element(x_out, y_out_); } } // namespace group_elements -} // namespace barretenberg \ No newline at end of file +} // namespace barretenberg diff --git a/cpp/src/aztec/ecc/groups/element.hpp b/cpp/src/aztec/ecc/groups/element.hpp index 188e54ce9e..9c83a399c4 100644 --- a/cpp/src/aztec/ecc/groups/element.hpp +++ b/cpp/src/aztec/ecc/groups/element.hpp @@ -72,7 +72,9 @@ template class alignas(32) element { element operator*=(const Fr& other) noexcept; - // constexpr Fr operator/(const element& other) noexcept {} TODO: this one seems harder than the others... + // If you end up implementing this, congrats, you've solved the DL problem! + // P.S. This is a joke, don't even attempt! 😂 + // constexpr Fr operator/(const element& other) noexcept {} constexpr element normalize() const noexcept; BBERG_INLINE constexpr element set_infinity() const noexcept; diff --git a/cpp/src/aztec/ecc/groups/element_impl.hpp b/cpp/src/aztec/ecc/groups/element_impl.hpp index 13ec5a73b6..6877446433 100644 --- a/cpp/src/aztec/ecc/groups/element_impl.hpp +++ b/cpp/src/aztec/ecc/groups/element_impl.hpp @@ -643,6 +643,7 @@ element element::mul_with_endomorphism(const Fr& exponent) uint64_t wnaf_entry; uint64_t index; bool sign; + Fq beta = Fq::cube_root_of_unity(); for (size_t i = 0; i < num_rounds * 2; ++i) { wnaf_entry = wnaf_table[i]; @@ -652,7 +653,7 @@ element element::mul_with_endomorphism(const Fr& exponent) auto to_add = lookup_table[static_cast(index)]; to_add.y.self_conditional_negate(sign ^ is_odd); if (is_odd) { - to_add.x *= Fq::beta(); + to_add.x *= beta; } work_element += to_add; @@ -668,7 +669,7 @@ element element::mul_with_endomorphism(const Fr& exponent) work_element += temporary; } - temporary = { lookup_table[0].x * Fq::beta(), lookup_table[0].y, lookup_table[0].z }; + temporary = { lookup_table[0].x * beta, lookup_table[0].y, lookup_table[0].z }; if (endo_skew) { work_element += temporary; @@ -787,6 +788,8 @@ std::vector> element::batch_mul_with_endomo uint64_t wnaf_entry; uint64_t index; bool sign; + Fq beta = Fq::cube_root_of_unity(); + for (size_t i = 0; i < 2; ++i) { for (size_t j = 0; j < num_points; ++j) { wnaf_entry = wnaf_table[i]; @@ -796,7 +799,7 @@ std::vector> element::batch_mul_with_endomo auto to_add = lookup_table[static_cast(index)][j]; to_add.y.self_conditional_negate(sign ^ is_odd); if (is_odd) { - to_add.x *= Fq::beta(); + to_add.x *= beta; } if (i == 0) { work_elements[j] = to_add; @@ -821,7 +824,7 @@ std::vector> element::batch_mul_with_endomo auto to_add = lookup_table[static_cast(index)][j]; to_add.y.self_conditional_negate(sign ^ is_odd); if (is_odd) { - to_add.x *= Fq::beta(); + to_add.x *= beta; } temp_point_vector[j] = to_add; } @@ -838,7 +841,7 @@ std::vector> element::batch_mul_with_endomo if (endo_skew) { for (size_t j = 0; j < num_points; ++j) { temp_point_vector[j] = lookup_table[0][j]; - temp_point_vector[j].x *= Fq::beta(); + temp_point_vector[j].x *= beta; } batch_affine_add(&temp_point_vector[0], &work_elements[0]); } diff --git a/cpp/src/aztec/ecc/groups/group.hpp b/cpp/src/aztec/ecc/groups/group.hpp index cd1cff013f..6faf886dae 100644 --- a/cpp/src/aztec/ecc/groups/group.hpp +++ b/cpp/src/aztec/ecc/groups/group.hpp @@ -32,7 +32,8 @@ template element; typedef group_elements::affine_element affine_element; - + typedef coordinate_field Fq; + typedef subgroup_field Fr; static constexpr bool USE_ENDOMORPHISM = GroupParams::USE_ENDOMORPHISM; static constexpr bool has_a = GroupParams::has_a; @@ -51,8 +52,8 @@ template (scalar, wnaf, point_index, slice + predicate); } else { - constexpr size_t final_bits = scalar_bits - (scalar_bits / wnaf_bits) * wnaf_bits; + constexpr size_t final_bits = ((scalar_bits / wnaf_bits) * wnaf_bits == scalar_bits) + ? wnaf_bits + : scalar_bits - (scalar_bits / wnaf_bits) * wnaf_bits; uint64_t slice = get_wnaf_bits_const(scalar); uint64_t predicate = ((slice & 1UL) == 0UL); wnaf[num_points] = diff --git a/cpp/src/aztec/ecc/groups/wnaf.test.cpp b/cpp/src/aztec/ecc/groups/wnaf.test.cpp index 0bcaf32fb2..a58583845b 100644 --- a/cpp/src/aztec/ecc/groups/wnaf.test.cpp +++ b/cpp/src/aztec/ecc/groups/wnaf.test.cpp @@ -163,7 +163,8 @@ TEST(wnaf, wnaf_fixed_with_endo_split) recover_fixed_wnaf(endo_wnaf, endo_skew, k2_recovered.data[1], k2_recovered.data[0], 5); fr result; - result = k2_recovered * fr::beta(); + fr lambda = fr::cube_root_of_unity(); + result = k2_recovered * lambda; result = k1_recovered - result; EXPECT_EQ(result, k); diff --git a/cpp/src/aztec/lagrange_base_gen/CMakeLists.txt b/cpp/src/aztec/lagrange_base_gen/CMakeLists.txt new file mode 100644 index 0000000000..ae9ed602e1 --- /dev/null +++ b/cpp/src/aztec/lagrange_base_gen/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable( + lagrange_base_gen + lagrange_base_processor.cpp +) + +target_link_libraries( + lagrange_base_gen + PRIVATE + srs +) \ No newline at end of file diff --git a/cpp/src/aztec/lagrange_base_gen/lagrange_base_gen.sh b/cpp/src/aztec/lagrange_base_gen/lagrange_base_gen.sh new file mode 100755 index 0000000000..3034cc64a9 --- /dev/null +++ b/cpp/src/aztec/lagrange_base_gen/lagrange_base_gen.sh @@ -0,0 +1,28 @@ +#!/bin/bash +set -e +cd ../../../build && make -j4 srs lagrange_base_gen + +# Take user inputs and set the default values +num_files=${1:-20} +src=${2:-../srs_db/ignition} +dest=${3:-../srs_db/lagrange} + +echo -e "\n--------------------------------------------" +echo "Generate lagrange SRS for $num_files files." +echo "Monomial SRS path: $src" +echo "Store lagrange SRS at: $dest" +echo -e "--------------------------------------------\n" + +# Iterate the loop for generating lagrange transcripts for degree <= 2^{20} +degree=1 +while [ $degree -le $num_files ] +do + printf "Generating lagrange transcript for subgroup size " + echo $((1 << $degree)) + + # call the lagrange base processor + ./bin/lagrange_base_gen $((1 << $degree)) $src $dest + + # increment the value + degree=`expr $degree + 1` +done \ No newline at end of file diff --git a/cpp/src/aztec/lagrange_base_gen/lagrange_base_processor.cpp b/cpp/src/aztec/lagrange_base_gen/lagrange_base_processor.cpp new file mode 100644 index 0000000000..c2a7e7e1b9 --- /dev/null +++ b/cpp/src/aztec/lagrange_base_gen/lagrange_base_processor.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + /* + This script generates lagrange base transcript from a monomial base srs transcript and + for a given subgroup size. The subgroup size must be greater than 1 because for subgroup size + equals 1, the corresponding monomial srs has only one term (g1::affine_one) and thus + we encounter a `No input file found` error in io::read_transcript_g1(). + + Sample usage: ./bin/lagrange_base_gen 8 + The bash script lagrange_base_gen.sh runs this script for a given set of subgroup sizes (only + powers of two). To run the srs_tests successfully, you need to run the bash script once to + generate relevant lagrange base transcripts. + */ + std::vector args(argv, argv + argc); + if (args.size() <= 1) { + info("usage: ", args[0], " [srs_path] [lagrange_srs_path]"); + return 1; + } + + const size_t subgroup_size = (size_t)atoi(args[1].c_str()); + const std::string srs_path = (args.size() > 2) ? args[2] : "../srs_db/ignition"; + const std::string lagrange_srs_path = (args.size() > 3) ? args[3] : "../srs_db/lagrange"; + + auto reference_string = std::make_shared(subgroup_size, srs_path); + std::vector monomial_srs(subgroup_size); + for (size_t i = 0; i < subgroup_size; ++i) { + monomial_srs[i] = reference_string->get_monomials()[2 * i]; + } + + auto verifier_ref_string = std::make_shared(srs_path); + + std::vector lagrange_base_srs(subgroup_size); + barretenberg::lagrange_base::transform_srs(&monomial_srs[0], &lagrange_base_srs[0], subgroup_size); + + std::vector g2_elements; + g2_elements.push_back(verifier_ref_string->get_g2x()); + g2_elements.push_back(barretenberg::g2::affine_one); + + barretenberg::io::Manifest manifest{ + 0, 1, static_cast(subgroup_size), 2, static_cast(subgroup_size), 2, 0 + }; + barretenberg::io::write_transcript(&lagrange_base_srs[0], &g2_elements[0], manifest, lagrange_srs_path, true); + + return 0; +} \ No newline at end of file diff --git a/cpp/src/aztec/numeric/bitop/sparse_form.hpp b/cpp/src/aztec/numeric/bitop/sparse_form.hpp index e49ca86cf4..f026ea4275 100644 --- a/cpp/src/aztec/numeric/bitop/sparse_form.hpp +++ b/cpp/src/aztec/numeric/bitop/sparse_form.hpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include "../uint256/uint256.hpp" @@ -25,12 +27,14 @@ inline std::vector slice_input(const uint256_t input, const uint64_t b return slices; } - inline std::vector slice_input_using_variable_bases(const uint256_t input, const std::vector bases) { uint256_t target = input; std::vector slices; for (size_t i = 0; i < bases.size(); ++i) { + if (target >= bases[i] && i == bases.size() - 1) { + throw_or_abort(format("Last key slice greater than ", bases[i])); + } slices.push_back((target % bases[i]).data[0]); target /= bases[i]; } diff --git a/cpp/src/aztec/numeric/uint256/uint256.hpp b/cpp/src/aztec/numeric/uint256/uint256.hpp index d446eac47b..9c144ec422 100644 --- a/cpp/src/aztec/numeric/uint256/uint256.hpp +++ b/cpp/src/aztec/numeric/uint256/uint256.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "../uint128/uint128.hpp" @@ -33,6 +34,15 @@ class alignas(32) uint256_t { : data{ other.data[0], other.data[1], other.data[2], other.data[3] } {} + explicit uint256_t(std::string const& str) + { + for (int i = 0; i < 4; ++i) { + std::stringstream ss; + ss << std::hex << str.substr(size_t(i) * 16, 16); + ss >> data[3 - i]; + } + } + static constexpr uint256_t from_uint128(const uint128_t a) { return uint256_t(static_cast(a), static_cast(a >> 64), 0, 0); diff --git a/cpp/src/aztec/plonk/CMakeLists.txt b/cpp/src/aztec/plonk/CMakeLists.txt index 32bf87ad28..26d67ea5f8 100644 --- a/cpp/src/aztec/plonk/CMakeLists.txt +++ b/cpp/src/aztec/plonk/CMakeLists.txt @@ -1 +1 @@ -barretenberg_module(plonk crypto_pedersen polynomials crypto_sha256 ecc crypto_blake2s) \ No newline at end of file +barretenberg_module(plonk crypto_pedersen polynomials crypto_sha256 ecc crypto_blake3s) \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/composer_base.cpp b/cpp/src/aztec/plonk/composer/composer_base.cpp index 5a253daa36..d03b605c46 100644 --- a/cpp/src/aztec/plonk/composer/composer_base.cpp +++ b/cpp/src/aztec/plonk/composer/composer_base.cpp @@ -14,34 +14,30 @@ namespace waffle { * */ void ComposerBase::assert_equal(const uint32_t a_variable_idx, const uint32_t b_variable_idx, std::string const& msg) { - assert_valid_variables({ a_variable_idx, b_variable_idx }); - bool values_equal = (get_variable(a_variable_idx) == get_variable(b_variable_idx)); - if (!values_equal && !failed) { - failed = true; - err = msg; + if (!values_equal && !failed()) { + failure(msg); } uint32_t a_real_idx = real_variable_index[a_variable_idx]; uint32_t b_real_idx = real_variable_index[b_variable_idx]; - // If a==b is already enforced exit method + // If a==b is already enforced, exit method if (a_real_idx == b_real_idx) return; - // otherwise update the real_idx of b-chain members to that of a + // Otherwise update the real_idx of b-chain members to that of a auto b_start_idx = get_first_variable_in_class(b_variable_idx); update_real_variable_indices(b_start_idx, a_real_idx); - // now merge equivalence classes of a and b by tying last (= real) element of b-chain to first element of a-chain + // Now merge equivalence classes of a and b by tying last (= real) element of b-chain to first element of a-chain auto a_start_idx = get_first_variable_in_class(a_variable_idx); next_var_index[b_real_idx] = a_start_idx; prev_var_index[a_start_idx] = b_real_idx; - bool no_tag_clash = (variable_tags[a_real_idx] == DUMMY_TAG || variable_tags[b_real_idx] == DUMMY_TAG || - variable_tags[a_real_idx] == variable_tags[b_real_idx]); - if (!no_tag_clash && !failed) { - failed = true; - err = msg; + bool no_tag_clash = (real_variable_tags[a_real_idx] == DUMMY_TAG || real_variable_tags[b_real_idx] == DUMMY_TAG || + real_variable_tags[a_real_idx] == real_variable_tags[b_real_idx]); + if (!no_tag_clash && !failed()) { + failure(msg); } - if (variable_tags[a_real_idx] == DUMMY_TAG) - variable_tags[a_real_idx] = variable_tags[b_real_idx]; + if (real_variable_tags[a_real_idx] == DUMMY_TAG) + real_variable_tags[a_real_idx] = real_variable_tags[b_real_idx]; } /** @@ -55,8 +51,6 @@ void ComposerBase::assert_equal(const uint32_t a_variable_idx, const uint32_t b_ * */ template void ComposerBase::compute_wire_copy_cycles() { - const uint32_t num_public_inputs = static_cast(public_inputs.size()); - // Initialize wire_copy_cycles of public input variables to point to themselves for (size_t i = 0; i < public_inputs.size(); ++i) { cycle_node left{ static_cast(i), WireType::LEFT }; @@ -68,23 +62,26 @@ template void ComposerBase::compute_wire_copy_cycles() cycle.emplace_back(left); cycle.emplace_back(right); } + + const uint32_t num_public_inputs = static_cast(public_inputs.size()); + // Go through all witnesses and add them to the wire_copy_cycles for (size_t i = 0; i < n; ++i) { const auto w_1_index = real_variable_index[w_l[i]]; const auto w_2_index = real_variable_index[w_r[i]]; const auto w_3_index = real_variable_index[w_o[i]]; - cycle_node left{ static_cast(i + num_public_inputs), WireType::LEFT }; - cycle_node right{ static_cast(i + num_public_inputs), WireType::RIGHT }; - cycle_node out{ static_cast(i + num_public_inputs), WireType::OUTPUT }; - wire_copy_cycles[static_cast(w_1_index)].emplace_back(left); - wire_copy_cycles[static_cast(w_2_index)].emplace_back(right); - wire_copy_cycles[static_cast(w_3_index)].emplace_back(out); + wire_copy_cycles[static_cast(w_1_index)].emplace_back(static_cast(i + num_public_inputs), + WireType::LEFT); + wire_copy_cycles[static_cast(w_2_index)].emplace_back(static_cast(i + num_public_inputs), + WireType::RIGHT); + wire_copy_cycles[static_cast(w_3_index)].emplace_back(static_cast(i + num_public_inputs), + WireType::OUTPUT); if constexpr (program_width > 3) { const auto w_4_index = real_variable_index[w_4[i]]; - cycle_node fourth{ static_cast(i + num_public_inputs), WireType::FOURTH }; - wire_copy_cycles[static_cast(w_4_index)].emplace_back(fourth); + wire_copy_cycles[static_cast(w_4_index)].emplace_back(static_cast(i + num_public_inputs), + WireType::FOURTH); } } } @@ -95,18 +92,23 @@ template void ComposerBase::compute_wire_copy_cycles() * @param key Proving key. * * @tparam program_width Program width. - * @tparam with_tags Whether to construct id permutation polynomial or not. - * */ + * @tparam with_tags means that we are modifying the Plonk permutation to include "generalised subset permutations". You + * can assign tags to wires, and then add an equivalence check between two tags. e.g. I could assign wires 'w1, w5, w3' + * tag1, and 'w2, w4, w6' to tag2. Then assert that tag1 === tag2. The permutation polynomials are modified to assert + * these two wire sets are equivalent (unlike normal copy constraints, the ordering of the wires within the sets is not + * enforced). `with_tags` is closely linked with `id_poly`: id_poly is a flag that describes whether we're using + * Vitalik's trick of using trivial identity permutation polynomials (id_poly = false). OR whether the identity + * permutation polynomials are circuit-specific and stored in the proving/verification key (id_poly = true). + */ template void ComposerBase::compute_sigma_permutations(proving_key* key) { // Compute wire copy cycles for public and private variables compute_wire_copy_cycles(); std::array, program_width> sigma_mappings; std::array, program_width> id_mappings; - // std::array wire_offsets{ 0U, 0x40000000, 0x80000000, 0xc0000000 }; - const uint32_t num_public_inputs = static_cast(public_inputs.size()); - // Prepare the sigma and id mappings by reserving enough space - // and saving perumation subgroup elements that point to themselves + + // Instantiate the sigma and id mappings by reserving enough space and pushing 'default' permutation subgroup + // elements that point to themselves. for (size_t i = 0; i < program_width; ++i) { sigma_mappings[i].reserve(key->n); if (with_tags) @@ -114,11 +116,16 @@ template void ComposerBase::compute_sigma } for (size_t i = 0; i < program_width; ++i) { for (size_t j = 0; j < key->n; ++j) { - sigma_mappings[i].emplace_back(permutation_subgroup_element{ (uint32_t)j, (uint8_t)i, false, false }); + sigma_mappings[i].emplace_back(permutation_subgroup_element{ + .subgroup_index = (uint32_t)j, .column_index = (uint8_t)i, .is_public_input = false, .is_tag = false }); if (with_tags) - id_mappings[i].emplace_back(permutation_subgroup_element{ (uint32_t)j, (uint8_t)i, false, false }); + id_mappings[i].emplace_back(permutation_subgroup_element{ .subgroup_index = (uint32_t)j, + .column_index = (uint8_t)i, + .is_public_input = false, + .is_tag = false }); } } + // Go through all wire cycles and update sigma and id mappings to point to the next element // within each cycle as well as set the appropriate tags for (size_t i = 0; i < wire_copy_cycles.size(); ++i) { @@ -132,7 +139,10 @@ template void ComposerBase::compute_sigma const uint32_t current_column = static_cast(current_cycle_node.wire_type) >> 30U; const uint32_t next_column = static_cast(next_cycle_node.wire_type) >> 30U; - sigma_mappings[current_column][current_row] = { next_row, (uint8_t)next_column, false, false }; + sigma_mappings[current_column][current_row] = { .subgroup_index = next_row, + .column_index = (uint8_t)next_column, + .is_public_input = false, + .is_tag = false }; bool first_node, last_node; if (with_tags) { @@ -141,15 +151,18 @@ template void ComposerBase::compute_sigma last_node = next_cycle_node_index == 0; if (first_node) { id_mappings[current_column][current_row].is_tag = true; - id_mappings[current_column][current_row].subgroup_index = (variable_tags[i]); + id_mappings[current_column][current_row].subgroup_index = (real_variable_tags[i]); } if (last_node) { sigma_mappings[current_column][current_row].is_tag = true; - sigma_mappings[current_column][current_row].subgroup_index = tau.at(variable_tags[i]); + sigma_mappings[current_column][current_row].subgroup_index = tau.at(real_variable_tags[i]); } } } } + + const uint32_t num_public_inputs = static_cast(public_inputs.size()); + // This corresponds in the paper to modifying sigma to sigma' with the zeta_i values; this enforces public input // consistency for (size_t i = 0; i < num_public_inputs; ++i) { @@ -160,6 +173,7 @@ template void ComposerBase::compute_sigma std::cerr << "MAPPING IS BOTH A TAG AND A PUBLIC INPUT" << std::endl; } } + for (size_t i = 0; i < program_width; ++i) { // Construct permutation polynomials in lagrange base @@ -206,30 +220,31 @@ template void ComposerBase::compute_sigma * Compute proving key base. * * 1. Load crs. - * 2. Initialize circuit proving key. - * 3. Create polynomial constraint selector from each of coefficient selectors in the circuit proving key. + * 2. Initialize this.circuit_proving_key. + * 3. Create constraint selector polynomials from each of this composer's `selectors` vectors and add them to the + * proving key. * * N.B. Need to add the fix for coefficients * * @param minimum_circuit_size Used as the total number of gates when larger than n + count of public inputs. * @param num_reserved_gates The number of reserved gates. - * @return Pointer to the initialized proving key updated with selectors. + * @return Pointer to the initialized proving key updated with selector polynomials. * */ std::shared_ptr ComposerBase::compute_proving_key_base(const waffle::ComposerType composer_type, const size_t minimum_circuit_size, const size_t num_reserved_gates) { const size_t num_filled_gates = n + public_inputs.size(); - const size_t total_num_gates = std::max(minimum_circuit_size, n + public_inputs.size()); - const size_t subgroup_size = get_circuit_subgroup_size(total_num_gates + num_reserved_gates); + const size_t total_num_gates = std::max(minimum_circuit_size, num_filled_gates); + const size_t subgroup_size = get_circuit_subgroup_size(total_num_gates + num_reserved_gates); // next power of 2 - // In case of standard plonk, if 4 roots are cut out of the vanishing polynomial, + // In the case of standard plonk, if 4 roots are cut out of the vanishing polynomial, // then the degree of the quotient polynomial t(X) is 3n. This implies that the degree // of the constituent t_{high} of t(X) must be n (as against (n - 1) for other composer types). // Thus, to commit to t_{high}, we need the crs size to be (n + 1) for standard plonk. // // For more explanation about the degree of t(X), see - // ./src/aztec/plonk/proof_system/prover/prover.cpp/ProverBase::compute_quotient_pre_commitment + // ./src/aztec/plonk/proof_system/prover/prover.cpp/ProverBase::compute_quotient_commitments // auto crs = crs_factory_->get_prover_crs(subgroup_size + 1); @@ -237,26 +252,30 @@ std::shared_ptr ComposerBase::compute_proving_key_base(const waffle // Initialize circuit_proving_key circuit_proving_key = std::make_shared(subgroup_size, public_inputs.size(), crs, composer_type); - for (size_t i = 0; i < selector_num; ++i) { - std::vector& coeffs = selectors[i]; + for (size_t i = 0; i < num_selectors; ++i) { + std::vector& selector_values = selectors[i]; const auto& properties = selector_properties[i]; - ASSERT(n == coeffs.size()); + ASSERT(n == selector_values.size()); - // Fill unfilled gates coefficients with zeroes - for (size_t j = num_filled_gates; j < subgroup_size - 1 /*- public_inputs.size()*/; ++j) { - coeffs.emplace_back(fr::zero()); + // Fill unfilled gates' selector values with zeroes (stopping 1 short; the last value will be nonzero). + for (size_t j = num_filled_gates; j < subgroup_size - 1; ++j) { + selector_values.emplace_back(fr::zero()); } - // Add `1` to ensure the selectors have at least one non-zero element - // This in turn ensures that when we commit to a selector, we will never get the - // point at infinity. We avoid the point at infinity in the native verifier because this is an edge case in the - // recursive verifier circuit, and this ensures that the logic is consistent between both verifiers. + + // Add a nonzero value at the end of each selector vector. This ensures that, if the selector would otherwise + // have been 'empty': + // 1) that its commitment won't be the point at infinity. We avoid the point at + // infinity in the native verifier because this is an edge case in the recursive verifier circuit, and we + // want the logic to be consistent between both verifiers. + // 2) that its commitment won't be equal to any other selectors' commitments (which would break biggroup + // operations when verifying snarks within a circuit, since doubling is not directly supported). This in turn + // ensures that when we commit to a selector, we will never get the point at infinity. // - // Note: Setting the selector to 1, would ordinarily make the proof fail if we did not have a satisfying + // Note: Setting the selector to nonzero would ordinarily make the proof fail if we did not have a satisfying // constraint. This is not the case for the last selector position, as it is never checked in the proving // system; observe that we cut out 4 roots and only use 3 for zero knowledge. The last root, corresponds to this // position. - - coeffs.emplace_back(1); + selector_values.emplace_back(i + 1); // Compute lagrange form of selector polynomial polynomial selector_poly_lagrange(subgroup_size); @@ -264,7 +283,7 @@ std::shared_ptr ComposerBase::compute_proving_key_base(const waffle selector_poly_lagrange[k] = fr::zero(); } for (size_t k = public_inputs.size(); k < subgroup_size; ++k) { - selector_poly_lagrange[k] = coeffs[k - public_inputs.size()]; + selector_poly_lagrange[k] = selector_values[k - public_inputs.size()]; } // Compute monomial form of selector polynomial @@ -285,22 +304,81 @@ std::shared_ptr ComposerBase::compute_proving_key_base(const waffle return circuit_proving_key; } +/** + * @brief Computes the verification key by computing the: + * (1) commitments to the selector polynomials, + * (2) sets the polynomial manifest using the data from proving key. + */ +std::shared_ptr ComposerBase::compute_verification_key_base( + std::shared_ptr const& proving_key, std::shared_ptr const& vrs) +{ + auto circuit_verification_key = std::make_shared( + proving_key->n, proving_key->num_public_inputs, vrs, proving_key->composer_type); + + for (size_t i = 0; i < proving_key->polynomial_manifest.size(); ++i) { + const auto& selector_poly_info = proving_key->polynomial_manifest[i]; + + const std::string selector_poly_label(selector_poly_info.polynomial_label); + const std::string selector_commitment_label(selector_poly_info.commitment_label); + + if (selector_poly_info.source == PolynomialSource::SELECTOR) { + // Fetch the constraint selector polynomial in its coefficient form. + fr* selector_poly_coefficients; + selector_poly_coefficients = proving_key->polynomial_cache.get(selector_poly_label).get_coefficients(); + + // Commit to the constraint selector polynomial and insert the commitment in the verification key. + auto selector_poly_commitment = + g1::affine_element(scalar_multiplication::pippenger(selector_poly_coefficients, + proving_key->reference_string->get_monomials(), + proving_key->n, + proving_key->pippenger_runtime_state)); + + circuit_verification_key->constraint_selectors.insert( + { selector_commitment_label, selector_poly_commitment }); + + } else if (selector_poly_info.source == PolynomialSource::PERMUTATION) { + // Fetch the permutation selector polynomial in its coefficient form. + fr* selector_poly_coefficients; + selector_poly_coefficients = proving_key->polynomial_cache.get(selector_poly_label).get_coefficients(); + + // Commit to the permutation selector polynomial insert the commitment in the verification key. + auto selector_poly_commitment = + g1::affine_element(scalar_multiplication::pippenger(selector_poly_coefficients, + proving_key->reference_string->get_monomials(), + proving_key->n, + proving_key->pippenger_runtime_state)); + + circuit_verification_key->permutation_selectors.insert( + { selector_commitment_label, selector_poly_commitment }); + } + } + + // Set the polynomial manifest in verification key. + circuit_verification_key->polynomial_manifest = PolynomialManifest(proving_key->composer_type); + + return circuit_verification_key; +} + /** * Compute witness polynomials (w_1, w_2, w_3, w_4). * + * @details Fills 3 or 4 witness polynomials w_1, w_2, w_3, w_4 with the values of in-circuit variables. The beginning + * of w_1, w_2 polynomials is filled with public_input values. * @return Witness with computed witness polynomials. * * @tparam Program settings needed to establish if w_4 is being used. * */ -template void ComposerBase::compute_witness_base() +template void ComposerBase::compute_witness_base(const size_t minimum_circuit_size) { if (computed_witness) { return; } - const size_t total_num_gates = n + public_inputs.size(); + const size_t total_num_gates = std::max(minimum_circuit_size, n + public_inputs.size()); const size_t subgroup_size = get_circuit_subgroup_size(total_num_gates + NUM_RESERVED_GATES); + // Note: randomness is added to 3 of the last 4 positions in plonk/proof_system/prover/prover.cpp + // ProverBase::execute_preamble_round(). for (size_t i = total_num_gates; i < subgroup_size; ++i) { w_l.emplace_back(zero_idx); w_r.emplace_back(zero_idx); @@ -318,6 +396,10 @@ template void ComposerBase::compute_witness_base() if (program_settings::program_width > 3) w_4_lagrange = polynomial(subgroup_size); + + // Push the public inputs' values to the beginning of the wire witness polynomials. + // Note: each public input variable is assigned to both w_1 and w_2. See + // plonk/proof_system/public_inputs/public_inputs_impl.hpp for a giant comment explaining why. for (size_t i = 0; i < public_inputs.size(); ++i) { fr::__copy(get_variable(public_inputs[i]), w_1_lagrange[i]); fr::__copy(get_variable(public_inputs[i]), w_2_lagrange[i]); @@ -325,6 +407,9 @@ template void ComposerBase::compute_witness_base() if (program_settings::program_width > 3) fr::__copy(fr::zero(), w_4_lagrange[i]); } + + // Assign the variable values (which are pointed-to by the `w_` wires) to the wire witness polynomials `poly_w_`, + // shifted to make room for the public inputs at the beginning. for (size_t i = public_inputs.size(); i < subgroup_size; ++i) { fr::__copy(get_variable(w_l[i - public_inputs.size()]), w_1_lagrange.at(i)); fr::__copy(get_variable(w_r[i - public_inputs.size()]), w_2_lagrange.at(i)); @@ -346,8 +431,9 @@ template void ComposerBase::compute_witness_base() template void ComposerBase::compute_sigma_permutations<3, false>(proving_key* key); template void ComposerBase::compute_sigma_permutations<4, false>(proving_key* key); template void ComposerBase::compute_sigma_permutations<4, true>(proving_key* key); -template void ComposerBase::compute_witness_base(); -template void ComposerBase::compute_witness_base(); +template void ComposerBase::compute_witness_base(const size_t); +template void ComposerBase::compute_witness_base(const size_t); +template void ComposerBase::compute_witness_base(const size_t); template void ComposerBase::compute_wire_copy_cycles<3>(); template void ComposerBase::compute_wire_copy_cycles<4>(); diff --git a/cpp/src/aztec/plonk/composer/composer_base.hpp b/cpp/src/aztec/plonk/composer/composer_base.hpp index 3134837d68..843ee0dc1d 100644 --- a/cpp/src/aztec/plonk/composer/composer_base.hpp +++ b/cpp/src/aztec/plonk/composer/composer_base.hpp @@ -2,8 +2,8 @@ #include #include #include -#include #include +#include namespace waffle { static constexpr uint32_t DUMMY_TAG = 0; @@ -112,6 +112,7 @@ class ComposerBase { static constexpr uint32_t FIRST_VARIABLE_IN_CLASS = UINT32_MAX - 2; static constexpr size_t NUM_RESERVED_GATES = 4; // this must be >= num_roots_cut_out_of_vanishing_polynomial + // Enum values spaced in increments of 30-bits (multiples of 2 ** 30). enum WireType { LEFT = 0U, RIGHT = (1U << 30U), OUTPUT = (1U << 31U), FOURTH = 0xc0000000 }; struct cycle_node { @@ -146,13 +147,13 @@ class ComposerBase { : ComposerBase(std::shared_ptr(new FileReferenceStringFactory("../srs_db/ignition"))) {} ComposerBase(std::shared_ptr const& crs_factory, - size_t selector_num = 0, + size_t num_selectors = 0, size_t size_hint = 0, std::vector selector_properties = {}) : n(0) , crs_factory_(crs_factory) - , selector_num(selector_num) - , selectors(selector_num) + , num_selectors(num_selectors) + , selectors(num_selectors) , selector_properties(selector_properties) , rand_engine(nullptr) { @@ -161,13 +162,13 @@ class ComposerBase { } } - ComposerBase(size_t selector_num = 0, + ComposerBase(size_t num_selectors = 0, size_t size_hint = 0, std::vector selector_properties = {}) : n(0) , crs_factory_(std::make_unique("../srs_db/ignition")) - , selector_num(selector_num) - , selectors(selector_num) + , num_selectors(num_selectors) + , selectors(num_selectors) , selector_properties(selector_properties) , rand_engine(nullptr) { @@ -178,14 +179,14 @@ class ComposerBase { ComposerBase(std::shared_ptr const& p_key, std::shared_ptr const& v_key, - size_t selector_num = 0, + size_t num_selectors = 0, size_t size_hint = 0, std::vector selector_properties = {}) : n(0) , circuit_proving_key(p_key) , circuit_verification_key(v_key) - , selector_num(selector_num) - , selectors(selector_num) + , num_selectors(num_selectors) + , selectors(num_selectors) , selector_properties(selector_properties) , rand_engine(nullptr) { @@ -199,14 +200,18 @@ class ComposerBase { virtual ~ComposerBase(){}; virtual size_t get_num_gates() const { return n; } + virtual void print_num_gates() const { std::cout << n << std::endl; } virtual size_t get_num_variables() const { return variables.size(); } virtual std::shared_ptr compute_proving_key_base(const waffle::ComposerType type = waffle::STANDARD, const size_t minimum_ciricut_size = 0, const size_t num_reserved_gates = NUM_RESERVED_GATES); + // This needs to be static as it may be used only to compute the selector commitments. + static std::shared_ptr compute_verification_key_base( + std::shared_ptr const& proving_key, std::shared_ptr const& vrs); virtual std::shared_ptr compute_proving_key() = 0; virtual std::shared_ptr compute_verification_key() = 0; virtual void compute_witness() = 0; - template void compute_witness_base(); + template void compute_witness_base(const size_t minimum_circuit_size = 0); uint32_t zero_idx = 0; virtual void create_add_gate(const add_triple& in) = 0; @@ -291,12 +296,22 @@ class ComposerBase { virtual uint32_t add_variable(const barretenberg::fr& in) { variables.emplace_back(in); + + // By default, we assume each new variable belongs in its own copy-cycle. These defaults can be modified later + // by `assert_equal`. const uint32_t index = static_cast(variables.size()) - 1U; real_variable_index.emplace_back(index); next_var_index.emplace_back(REAL_VARIABLE); prev_var_index.emplace_back(FIRST_VARIABLE_IN_CLASS); - variable_tags.emplace_back(DUMMY_TAG); - wire_copy_cycles.push_back(std::vector()); + real_variable_tags.emplace_back(DUMMY_TAG); + wire_copy_cycles.push_back( + std::vector()); // Note: this doesn't necessarily need to be initialised here. In fact, the + // number of wire_copy_cycles often won't match the number of variables; its + // non-zero entries will be a smaller vector of size equal to the number of + // "real variables" (i.e. unique indices in the `real_variable_index` vector). + // `wire_copy_cycles` could instead be instantiated during + // compute_wire_copy_cycles(), although that would require a loop to identify + // the number of unique "real variables". return index; } /** @@ -310,13 +325,7 @@ class ComposerBase { */ virtual uint32_t add_public_variable(const barretenberg::fr& in) { - variables.emplace_back(in); - const uint32_t index = static_cast(variables.size()) - 1U; - real_variable_index.emplace_back(index); - next_var_index.emplace_back(REAL_VARIABLE); - prev_var_index.emplace_back(FIRST_VARIABLE_IN_CLASS); - variable_tags.emplace_back(DUMMY_TAG); - wire_copy_cycles.push_back(std::vector()); + const uint32_t index = add_variable(in); public_inputs.emplace_back(index); return index; } @@ -336,9 +345,8 @@ class ComposerBase { public_inputs.emplace_back(witness_index); } ASSERT(does_not_exist); - if (!does_not_exist && !failed) { - failed = true; - err = "Attempted to set a public input that is already public!"; + if (!does_not_exist && !failed()) { + failure("Attempted to set a public input that is already public!"); } } @@ -372,6 +380,8 @@ class ComposerBase { } } bool is_valid_variable(uint32_t variable_index) { return static_cast(variables.size()) > variable_index; } + bool _failed = false; + std::string _err; public: size_t n; @@ -385,9 +395,12 @@ class ComposerBase { std::vector prev_var_index; // index of previous variable in equivalence class (=FIRST if you're in a cycle alone) std::vector real_variable_index; // indices of corresponding real variables - std::vector variable_tags; + std::vector real_variable_tags; uint32_t current_tag = DUMMY_TAG; - std::map tau; // the permutation on variable tags; + std::map + tau; // The permutation on variable tags. See + // https://github.com/AztecProtocol/plonk-with-lookups-private/blob/new-stuff/GenPermuations.pdf + // DOCTODO: replace with the relevant wiki link. std::vector> wire_copy_cycles; std::shared_ptr circuit_proving_key; @@ -396,20 +409,315 @@ class ComposerBase { bool computed_witness = false; std::shared_ptr crs_factory_; - size_t selector_num; + size_t num_selectors; std::vector> selectors; + /** + * @brief Contains the properties of each selector: + * + name + * + if the polynomial needs to be in lagrange form + * + * @details The actual values are always set during class construction. You can find them in: + * + Standard plonk: standard_composer.hpp, function standard_sel_props() + * + Turbo plonk: turbo_composer.cpp, function turbo_sel_props() + * + Ultra plonk: ultra_composer.cpp, fucntion plookup_sel_props() + */ std::vector selector_properties; - bool failed = false; - std::string err; numeric::random::Engine* rand_engine; + bool failed() const { return _failed; }; + const std::string& err() const { return _err; }; + + void set_err(std::string msg) { _err = msg; } + void failure(std::string msg) + { + _failed = true; + set_err(msg); + } }; extern template void ComposerBase::compute_wire_copy_cycles<3>(); extern template void ComposerBase::compute_wire_copy_cycles<4>(); extern template void ComposerBase::compute_sigma_permutations<3, false>(proving_key* key); extern template void ComposerBase::compute_sigma_permutations<4, false>(proving_key* key); -extern template void ComposerBase::compute_witness_base(); -extern template void ComposerBase::compute_witness_base(); +extern template void ComposerBase::compute_witness_base(const size_t); +extern template void ComposerBase::compute_witness_base(const size_t); +extern template void ComposerBase::compute_witness_base(const size_t); extern template void ComposerBase::compute_sigma_permutations<4, true>(proving_key* key); } // namespace waffle + +/** + * Composer Example: Pythagorean triples. + * + * (x_1 * x_1) + (x_2 * x_2) == (x_3 * x_3) + * + ************************************************************************************************************* + * + * Notation as per the 'Constraint Systems' section of the Plonk paper: + * ______________________ + * | | + * | a_1 = 1 | c_1 = 4 + * | w_1 = x_{a_1} = x_1 | w_9 = x_{c_1} = x_4 + * x_1 | * ---------------------- x_4 + * | b_1 = 1 | Gate 1 | + * | w_5 = x_{b_1} = x_1 | a_4 = 4 | c_4 = 7 + * |______________________| w_4 = x_{a_4} = x_4 | w_12 = x_{c_4} = x_7 + * + ------------------------ x_7 + * b_4 = 5 | Gate 4 = + * ______________________ w_8 = x_{b_4} = x_5 | = + * | | | = + * | a_2 = 2 | c_2 = 5 | = + * | w_2 = x_{a_2} = x_2 | w_10 = x_{c_2} = x_5 | = + * x_2 | * ---------------------- x_5 = + * | b_2 = 2 | Gate 2 = + * | w_6 = x_{b_2} = x_2 | These `=`s = + * |______________________| symbolise a = + * copy-constraint = + * = + * ______________________ = + * | | = + * | a_3 = 3 | c_3 = 6 = + * | w_3 = x_{a_3} = x_3 | w_11 = x_{c_3} = x_6 = + * x_3 | * --------------------------------------------------- x_6 + * | b_3 = 3 | Gate 3 ^ + * | w_7 = x_{b_3} = x_3 | Suppose x_6 is the only____| + * |______________________| public input. + * + * - 4 gates. + * - 7 "variables" or "witnesses", denoted by the x's, whose indices are pointed-to by the values of the a,b,c's. + * #gates <= #variables <= 2 * #gates, always (see plonk paper). + * - 12 "wires" (inputs / outputs to gates) (= 3 * #gates; for a fan-in-2 gate), denoted by the w's. + * Each wire takes the value of a variable (# wires >= # variables). + * + * a_1 = b_1 = 1 + * a_2 = b_2 = 2 + * a_3 = b_3 = 3 + * a_4 = c_1 = 4 + * b_4 = c_2 = 5 + * c_3 = 6 + * c_4 = 7 + * ^ ^ ^ + * | | |____ indices of the x's (variables (witnesses)) + * |_____|________ indices of the gates + * + * So x_{a_1} = x_1, etc. + * + * Then we have "wire values": + * w_1 = x_{a_1} = x_1 + * w_2 = x_{a_2} = x_2 + * w_3 = x_{a_3} = x_3 + * w_4 = x_{a_4} = x_4 + * + * w_5 = x_{b_1} = x_1 + * w_6 = x_{b_2} = x_2 + * w_7 = x_{b_3} = x_3 + * w_8 = x_{b_4} = x_5 + * + * w_9 = x_{c_1} = x_4 + * w_10 = x_{c_2} = x_5 + * w_11 = x_{c_3} = x_6 + * w_12 = x_{c_4} = x_7 + * + **************************************************************************************************************** + * + * Notation as per this codebase is different from the Plonk paper: + * This example is reproduced exactly in the stdlib field test `test_field_pythagorean`. + * + * variables[0] = 0 for all circuits <-- this gate is not shown in this diagram. + * ______________________ + * | | + * | | + * | w_l[1] = 1 | w_o[1] = 4 + * variables[1] | * ------------------- variables[4] + * | w_r[1] = 1 | Gate 1 | + * | | | + * |______________________| w_l[4] = 4 | w_o[4] = 7 + * + --------------------- variables[7] + * w_r[4] = 5 | Gate 4 = + * ______________________ | = + * | | | = + * | | | = + * | w_l[2] = 2 | w_o[2] = 5 | = + * variables[2] | * ------------------- variables[5] = + * | w_r[2] = 2 | Gate 2 = + * | | These `=`s = + * |______________________| symbolise a = + * copy-constraint = + * = + * ______________________ = + * | | = + * | | = + * | w_l[3] = 3 | w_o[3] = 6 = + * variables[3] | * ------------------------------------------------variables[6] + * | w_r[3] = 3 | Gate 3 ^ + * | | Suppose this is the only___| + * |______________________| public input. + * + * - 5 gates (4 gates plus the 'zero' gate). + * - 7 "variables" or "witnesses", stored in the `variables` vector. + * #gates <= #variables <= 2 * #gates, always (see plonk paper). + * - 12 "wires" (inputs / outputs to gates) (= 3 * #gates; for a fan-in-2 gate), denoted by the w's. + * Each wire takes the value of a variable (# wires >= # variables). + * + * ComposerBase naming conventions: + * - n = 5 gates (4 gates plus the 'zero' gate). + * - variables <-- A.k.a. "witnesses". Indices of this variables vector are referred to as `witness_indices`. + * Example of varibales in this example (a 3,4,5 triangle): + * - variables = [ 0, 3, 4, 5, 9, 16, 25, 25] + * - public_inputs = [6] <-- points to variables[6]. + * + * These `w`'s are called "wires". In fact, they're witness_indices; pointing to indices in the `variables` vector. + * - w_l = [ 0, 1, 2, 3, 4] + * - w_r = [ 0, 1, 2, 3, 5] + * - w_o = [ 0, 4, 5, 6, 7] + * ^ The 0th wires are 0, for the default gate which instantiates the first witness as equal to 0. + * - w_4 = [ 0, 0, 0, 0, 0] <-- not used in this example. + * - selectors = [ + * q_m: [ 0, 1, 1, 1, 0], + * q_c: [ 0, 0, 0, 0, 0], + * q_1: [ 1, 0, 0, 0, 1], + * q_2: [ 0, 0, 0, 0, 1], + * q_3: [ 0,-1,-1,-1,-1], + * q_4: [ 0, 0, 0, 0, 0], <-- not used in this example; doesn't exist in Standard PlonK. + * ] + * + * These vectors imply copy-cycles between variables. ("copy-cycle" meaning "a set of variables which must always be + * equal"). The indices of these vectors correspond to those of the `variables` vector. Each index contains + * information about the corresponding variable. + * - next_var_index = [ -1, -1, -1, -1, -1, -1, -1, 6] + * - prev_var_index = [ -2, -2, -2, -2, -2, -2, 7, -2] + * - real_var_index = [ 0, 1, 2, 3, 4, 5, 6, 6] <-- Notice this repeated 6. + * + * `-1` = "The variable at this index is considered the last in its cycle (no next variable exists)" + * Note: we (arbitrarily) consider the "last" variable in a cycle to be the true representative of its + * cycle, and so dub it the "real" variable of the cycle. + * `-2` = "The variable at this index is considered the first in its cycle (no previous variable exists)" + * Any other number denotes the index of another variable in the cycle = "The variable at this index is equal to + * the variable at this other index". + * + * By default, when a variable is added to the composer, we assume the variable is in a copy-cycle of its own. So + * we set `next_var_index = -1`, `prev_var_index = -2`, `real_var_index` = the index of the variable in `variables`. + * You can see in our example that all but the last two indices of each *_index vector contain the default values. + * In our example, we have `variables[6].assert_equal(variables[7])`. The `assert_equal` function modifies the above + * vectors' entries for variables 6 & 7 to imply a copy-cycle between them. Arbitrarily, variables[7] is deemed the + * "first" in the cycle and variables[6] is considered the last (and hence the "real" variable which represents the + * cycle). + * + * By the time we get to computing wire copy-cycles, we need to allow for public_inputs, which in the plonk protocol + * are positioned to be the first witness values. `variables` doesn't include these public inputs (they're stored + * separately). In our example, we only have one public input, equal to `variables[6]`. We create a new "gate" for + * this 'public inputs' version of `variables[6]`, and push it to the front of our gates. (i.e. The first + * gate_index-es become gates for the public inputs, and all our `variables` occupy gates with gate_index-es shifted + * up by the number of public inputs (by 1 in this example)): + * - wire_copy_cycles = [ + * // The i-th index of `wire_copy_cycles` details the set of wires which all equal + * // variables[real_var_index[i]]. (I.e. equal to the i-th "real" variable): + * [ + * { gate_index: 1, left }, // w_l[1-#pub] = w_l[0] -> variables[0] = 0 <-- tag = 1 (id_mapping) + * { gate_index: 1, right }, // w_r[1-#pub] = w_r[0] -> variables[0] = 0 + * { gate_index: 1, output }, // w_o[1-#pub] = w_o[0] -> variables[0] = 0 + * { gate_index: 1, 4th }, // w_4[1-#pub] = w_4[0] -> variables[0] = 0 + * { gate_index: 2, 4th }, // w_4[2-#pub] = w_4[1] -> variables[0] = 0 + * { gate_index: 3, 4th }, // w_4[3-#pub] = w_4[2] -> variables[0] = 0 + * { gate_index: 4, 4th }, // w_4[4-#pub] = w_4[3] -> variables[0] = 0 + * { gate_index: 5, 4th }, // w_4[5-#pub] = w_4[4] -> variables[0] = 0 + * ], + * [ + * { gate_index: 2, left }, // w_l[2-#pub] = w_l[1] -> variables[1] = 3 <-- tag = 1 + * { gate_index: 2, right }, // w_r[2-#pub] = w_r[1] -> variables[1] = 3 + * ], + * [ + * { gate_index: 3, left }, // w_l[3-#pub] = w_l[2] -> variables[2] = 4 <-- tag = 1 + * { gate_index: 3, right }, // w_r[3-#pub] = w_r[2] -> variables[2] = 4 + * ], + * [ + * { gate_index: 4, left }, // w_l[4-#pub] = w_l[3] -> variables[3] = 5 <-- tag = 1 + * { gate_index: 4, right }, // w_r[4-#pub] = w_r[3] -> variables[3] = 5 + * ], + * [ + * { gate_index: 2, output }, // w_o[2-#pub] = w_o[1] -> variables[4] = 9 <-- tag = 1 + * { gate_index: 5, left }, // w_l[5-#pub] = w_l[4] -> variables[4] = 9 + * ], + * [ + * { gate_index: 3, output }, // w_o[3-#pub] = w_o[2] -> variables[5] = 16 <-- tag = 1 + * { gate_index: 5, right }, // w_r[5-#pub] = w_r[4] -> variables[5] = 16 + * ], + * [ + * { gate_index: 0, left }, // public_inputs[0] -> w_l[0] -> variables[6] = 25 <-- tag = 1 + * { gate_index: 0, right }, // public_inputs[0] -> w_r[0] -> variables[6] = 25 + * { gate_index: 4, output }, // w_o[4-#pub] = w_o[3] -> variables[6] = 25 + * { gate_index: 5, output }, // w_o[5-#pub] = w_o[4] -> variables[7] == variables[6] = 25 + * ], + * ] + * + * + * Converting the wire_copy_cycles' objects into coordinates [row #, column #] (l=0, r=1, o=2, 4th = 3), and showing + * how the cycles permute with arrows: + * Note: the mappings (permutations) shown here (by arrows) are exactly those expressed by `sigma_mappings`. + * Note: `-*>` denotes when a sigma_mappings entry has `is_tag=true`. + * Note: [[ , ]] denotes an entry in sigma_mappings which has been modified due to is_tag=true or + * is_public_input=true. + * Note: `-pub->` denotes a sigma_mappings entry from a left-wire which is a public input. + * + * Eg: [i, j] -> [k, l] means sigma_mappings[j][i] + * has: subgroup_index = k, column_index = l, is_false = true and is_public_input = false + * Eg: [i, j] -*> [[k, l]] means sigma_mappings[j][i] + * has: subgroup_index = k, column_index = l, is_tag = true + * Eg: [i, j] -pub-> [[k, l]] means sigma_mappings[j][i] + * has: subgroup_index = k, column_index = l, is_public_input = true + * + * [ + * [1, 0] -> [1, 1] -> [1, 2] -> [1, 3] -> [2, 3] -> [3, 3] -> [4, 3] -> [5, 3] -*> [[0, 0]], + * [2, 0] -> [2, 1] -*> [[0, 0]], + * [3, 0] -> [3, 1] -*> [[0, 0]], + * [4, 0] -> [4, 1] -*> [[0, 0]], + * [2, 2] -> [5, 0] -*> [[0, 2]], <-- the column # (2) is ignored if is_tag=true. + * [3, 2] -> [5, 1] -*> [[0, 2]], <-- the column # (2) is ignored if is_tag=true. + * [0, 0] -----> [0, 1] -> [4, 2] -> [5, 2] -*> [[0, 0]], + * -pub->[[0, 0]] + * // self^ ^ignored when is_public_input=true + * ] ^^^^^^ + * These are tagged with is_tag=true in `id_mappings`... + * + * Notice: the 0th row (gate) is the one public input of our example circuit. Two wires point to this public input: + * w_l[0] and w_r[0]. The reason _two_ wires do (and not just w_l[0]) is (I think) because of what we see above in + * the sigma_mappings data. + * - The [0,0] (w_l[0]) entry of sigma_mappings maps to [0,0], with is_public_input=true set. This is used by + * permutation.hpp to ensure the correct zeta_0 term is created for the ∆_PI term of the separate + * "plonk public inputs" paper. + * - The [0,1] (w_r[0]) entry of sigma_mappings maps to the next wire in the cycle ([4,2]=w_o[4-1]=w_o[3]). This is + * used to create the correct value for the sigma polynomial at S_σ_2(0). + * + * `id_mappings` maps every [row #, column #] to itself, except where is_tag=true where: + * + * [1, 0] -*> [[0, 0]], + * [2, 0] -*> [[0, 0]], + * [3, 0] -*> [[0, 0]], + * [4, 0] -*> [[0, 0]], + * [2, 2] -*> [[0, 2]], + * [3, 2] -*> [[0, 2]], + * [0, 0] -*> [[0, 0]], + * ^this column data is ignored by permutation.hpp when is_tag=true + * + * + * + * The (subgroup.size() * program_width) elements of sigma_mappings are of the form: + * { + * subgroup_index: j, // iterates over all rows in the subgroup + * column_index: i, // l,r,o,4 + * is_public_input: false, + * is_tag: false, + * } + * - sigma_mappings = [ + * // The i-th index of sigma_mappings is the "column" index (l,r,o,4). + * [ + * // The j-th index of sigma_mappings[i] is the subgroup_index or "row" + * { + * subgroup_index: j, + * column_index: i, + * is_public_input: false, + * is_tag: false, + * }, + * ], + * ]; + * + */ \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/plookup/compute_verification_key.cpp b/cpp/src/aztec/plonk/composer/plookup/compute_verification_key.cpp deleted file mode 100644 index a324d1ef1a..0000000000 --- a/cpp/src/aztec/plonk/composer/plookup/compute_verification_key.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "compute_verification_key.hpp" -#include -#include -#include -#include -#include - -using namespace barretenberg; - -namespace waffle { -namespace plookup_composer { - -std::shared_ptr compute_verification_key(std::shared_ptr const& circuit_proving_key, - std::shared_ptr const& vrs) -{ - std::array poly_coefficients; - poly_coefficients[0] = circuit_proving_key->polynomial_cache.get("q_1").get_coefficients(); - poly_coefficients[1] = circuit_proving_key->polynomial_cache.get("q_2").get_coefficients(); - poly_coefficients[2] = circuit_proving_key->polynomial_cache.get("q_3").get_coefficients(); - poly_coefficients[3] = circuit_proving_key->polynomial_cache.get("q_4").get_coefficients(); - poly_coefficients[4] = circuit_proving_key->polynomial_cache.get("q_5").get_coefficients(); - poly_coefficients[5] = circuit_proving_key->polynomial_cache.get("q_m").get_coefficients(); - poly_coefficients[6] = circuit_proving_key->polynomial_cache.get("q_c").get_coefficients(); - poly_coefficients[7] = circuit_proving_key->polynomial_cache.get("q_arith").get_coefficients(); - poly_coefficients[8] = circuit_proving_key->polynomial_cache.get("q_ecc_1").get_coefficients(); - poly_coefficients[9] = circuit_proving_key->polynomial_cache.get("q_range").get_coefficients(); - poly_coefficients[10] = circuit_proving_key->polynomial_cache.get("q_sort").get_coefficients(); - poly_coefficients[11] = circuit_proving_key->polynomial_cache.get("q_logic").get_coefficients(); - poly_coefficients[12] = circuit_proving_key->polynomial_cache.get("q_elliptic").get_coefficients(); - - poly_coefficients[13] = circuit_proving_key->polynomial_cache.get("sigma_1").get_coefficients(); - poly_coefficients[14] = circuit_proving_key->polynomial_cache.get("sigma_2").get_coefficients(); - poly_coefficients[15] = circuit_proving_key->polynomial_cache.get("sigma_3").get_coefficients(); - poly_coefficients[16] = circuit_proving_key->polynomial_cache.get("sigma_4").get_coefficients(); - - poly_coefficients[17] = circuit_proving_key->polynomial_cache.get("table_value_1").get_coefficients(); - poly_coefficients[18] = circuit_proving_key->polynomial_cache.get("table_value_2").get_coefficients(); - poly_coefficients[19] = circuit_proving_key->polynomial_cache.get("table_value_3").get_coefficients(); - poly_coefficients[20] = circuit_proving_key->polynomial_cache.get("table_value_4").get_coefficients(); - poly_coefficients[21] = circuit_proving_key->polynomial_cache.get("table_index").get_coefficients(); - poly_coefficients[22] = circuit_proving_key->polynomial_cache.get("table_type").get_coefficients(); - - poly_coefficients[23] = circuit_proving_key->polynomial_cache.get("id_1").get_coefficients(); - poly_coefficients[24] = circuit_proving_key->polynomial_cache.get("id_2").get_coefficients(); - poly_coefficients[25] = circuit_proving_key->polynomial_cache.get("id_3").get_coefficients(); - poly_coefficients[26] = circuit_proving_key->polynomial_cache.get("id_4").get_coefficients(); - - std::vector commitments; - commitments.resize(27); - - for (size_t i = 0; i < 27; ++i) { - commitments[i] = - g1::affine_element(scalar_multiplication::pippenger(poly_coefficients[i], - circuit_proving_key->reference_string->get_monomials(), - circuit_proving_key->n, - circuit_proving_key->pippenger_runtime_state)); - } - - auto circuit_verification_key = std::make_shared( - circuit_proving_key->n, circuit_proving_key->num_public_inputs, vrs, circuit_proving_key->composer_type); - - circuit_verification_key->constraint_selectors.insert({ "Q_1", commitments[0] }); - circuit_verification_key->constraint_selectors.insert({ "Q_2", commitments[1] }); - circuit_verification_key->constraint_selectors.insert({ "Q_3", commitments[2] }); - circuit_verification_key->constraint_selectors.insert({ "Q_4", commitments[3] }); - circuit_verification_key->constraint_selectors.insert({ "Q_5", commitments[4] }); - circuit_verification_key->constraint_selectors.insert({ "Q_M", commitments[5] }); - circuit_verification_key->constraint_selectors.insert({ "Q_C", commitments[6] }); - circuit_verification_key->constraint_selectors.insert({ "Q_ARITHMETIC_SELECTOR", commitments[7] }); - circuit_verification_key->constraint_selectors.insert({ "Q_FIXED_BASE_SELECTOR", commitments[8] }); - circuit_verification_key->constraint_selectors.insert({ "Q_RANGE_SELECTOR", commitments[9] }); - circuit_verification_key->constraint_selectors.insert({ "Q_SORT_SELECTOR", commitments[10] }); - circuit_verification_key->constraint_selectors.insert({ "Q_LOGIC_SELECTOR", commitments[11] }); - circuit_verification_key->constraint_selectors.insert({ "Q_ELLIPTIC", commitments[12] }); - - circuit_verification_key->permutation_selectors.insert({ "SIGMA_1", commitments[13] }); - circuit_verification_key->permutation_selectors.insert({ "SIGMA_2", commitments[14] }); - circuit_verification_key->permutation_selectors.insert({ "SIGMA_3", commitments[15] }); - circuit_verification_key->permutation_selectors.insert({ "SIGMA_4", commitments[16] }); - - circuit_verification_key->constraint_selectors.insert({ "TABLE_1", commitments[17] }); - circuit_verification_key->constraint_selectors.insert({ "TABLE_2", commitments[18] }); - circuit_verification_key->constraint_selectors.insert({ "TABLE_3", commitments[19] }); - circuit_verification_key->constraint_selectors.insert({ "TABLE_4", commitments[20] }); - - circuit_verification_key->constraint_selectors.insert({ "TABLE_INDEX", commitments[21] }); - circuit_verification_key->constraint_selectors.insert({ "TABLE_TYPE", commitments[22] }); - - circuit_verification_key->permutation_selectors.insert({ "ID_1", commitments[23] }); - circuit_verification_key->permutation_selectors.insert({ "ID_2", commitments[24] }); - circuit_verification_key->permutation_selectors.insert({ "ID_3", commitments[25] }); - circuit_verification_key->permutation_selectors.insert({ "ID_4", commitments[26] }); - - return circuit_verification_key; -} - -} // namespace plookup_composer -} // namespace waffle \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/plookup/compute_verification_key.hpp b/cpp/src/aztec/plonk/composer/plookup/compute_verification_key.hpp deleted file mode 100644 index b65a2c31b6..0000000000 --- a/cpp/src/aztec/plonk/composer/plookup/compute_verification_key.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once -#include - -namespace waffle { -struct verification_key; -struct proving_key; -class VerifierReferenceString; - -namespace plookup_composer { - -std::shared_ptr compute_verification_key(std::shared_ptr const& circuit_proving_key, - std::shared_ptr const& vrs); - -} -} // namespace waffle \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/plookup_composer.cpp b/cpp/src/aztec/plonk/composer/plookup_composer.cpp deleted file mode 100644 index 6242186dfa..0000000000 --- a/cpp/src/aztec/plonk/composer/plookup_composer.cpp +++ /dev/null @@ -1,1659 +0,0 @@ -#include "plookup_composer.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "plookup_tables/plookup_tables.hpp" -#include "plookup_tables/aes128.hpp" -#include "plookup_tables/sha256.hpp" - -using namespace barretenberg; - -namespace waffle { - -#define PLOOKUP_SELECTOR_REFS \ - auto& q_m = selectors[PlookupSelectors::QM]; \ - auto& q_c = selectors[PlookupSelectors::QC]; \ - auto& q_1 = selectors[PlookupSelectors::Q1]; \ - auto& q_2 = selectors[PlookupSelectors::Q2]; \ - auto& q_3 = selectors[PlookupSelectors::Q3]; \ - auto& q_4 = selectors[PlookupSelectors::Q4]; \ - auto& q_5 = selectors[PlookupSelectors::Q5]; \ - auto& q_arith = selectors[PlookupSelectors::QARITH]; \ - auto& q_ecc_1 = selectors[PlookupSelectors::QECC_1]; \ - auto& q_range = selectors[PlookupSelectors::QRANGE]; \ - auto& q_sort = selectors[PlookupSelectors::QSORT]; \ - auto& q_logic = selectors[PlookupSelectors::QLOGIC]; \ - auto& q_elliptic = selectors[PlookupSelectors::QELLIPTIC]; \ - auto& q_lookup_index = selectors[PlookupSelectors::QLOOKUPINDEX]; \ - auto& q_lookup_type = selectors[PlookupSelectors::QLOOKUPTYPE]; -std::vector plookup_sel_props() -{ - std::vector result{ - { "q_m", true }, { "q_c", true }, { "q_1", false }, { "q_2", true }, - { "q_3", false }, { "q_4", false }, { "q_5", false }, { "q_arith", false }, - { "q_ecc_1", false }, { "q_range", false }, { "q_sort", false }, { "q_logic", false }, - { "q_elliptic", false }, { "table_index", true }, { "table_type", true }, - }; - return result; -} - -PlookupComposer::PlookupComposer() - : PlookupComposer("../srs_db/ignition", 0) -{} - -PlookupComposer::PlookupComposer(std::string const& crs_path, const size_t size_hint) - : PlookupComposer(std::unique_ptr(new FileReferenceStringFactory(crs_path)), size_hint){}; - -PlookupComposer::PlookupComposer(std::unique_ptr&& crs_factory, const size_t size_hint) - : ComposerBase(std::move(crs_factory), NUM_PLOOKUP_SELECTORS, size_hint, plookup_sel_props()) -{ - w_l.reserve(size_hint); - w_r.reserve(size_hint); - w_o.reserve(size_hint); - w_4.reserve(size_hint); - zero_idx = put_constant_variable(0); - tau.insert({ DUMMY_TAG, DUMMY_TAG }); -} - -PlookupComposer::PlookupComposer(std::shared_ptr const& p_key, - std::shared_ptr const& v_key, - size_t size_hint) - : ComposerBase(p_key, v_key, NUM_PLOOKUP_SELECTORS, size_hint, plookup_sel_props()) -{ - w_l.reserve(size_hint); - w_r.reserve(size_hint); - w_o.reserve(size_hint); - w_4.reserve(size_hint); - zero_idx = put_constant_variable(0); - tau.insert({ DUMMY_TAG, DUMMY_TAG }); -} - -void PlookupComposer::create_add_gate(const add_triple& in) -{ - PLOOKUP_SELECTOR_REFS - assert_valid_variables({ in.a, in.b, in.c }); - - w_l.emplace_back(in.a); - w_r.emplace_back(in.b); - w_o.emplace_back(in.c); - w_4.emplace_back(zero_idx); - q_m.emplace_back(0); - q_1.emplace_back(in.a_scaling); - q_2.emplace_back(in.b_scaling); - q_3.emplace_back(in.c_scaling); - q_c.emplace_back(in.const_scaling); - q_arith.emplace_back(1); - q_4.emplace_back(0); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(0); - q_logic.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - q_elliptic.emplace_back(0); - ++n; -} - -void PlookupComposer::create_big_add_gate(const add_quad& in) -{ - PLOOKUP_SELECTOR_REFS - assert_valid_variables({ in.a, in.b, in.c, in.d }); - - w_l.emplace_back(in.a); - w_r.emplace_back(in.b); - w_o.emplace_back(in.c); - w_4.emplace_back(in.d); - q_m.emplace_back(0); - q_1.emplace_back(in.a_scaling); - q_2.emplace_back(in.b_scaling); - q_3.emplace_back(in.c_scaling); - q_c.emplace_back(in.const_scaling); - q_arith.emplace_back(1); - q_4.emplace_back(in.d_scaling); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(0); - q_logic.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - q_elliptic.emplace_back(0); - ++n; -} - -void PlookupComposer::create_big_add_gate_with_bit_extraction(const add_quad& in) -{ - PLOOKUP_SELECTOR_REFS - assert_valid_variables({ in.a, in.b, in.c, in.d }); - - w_l.emplace_back(in.a); - w_r.emplace_back(in.b); - w_o.emplace_back(in.c); - w_4.emplace_back(in.d); - q_m.emplace_back(0); - q_1.emplace_back(in.a_scaling); - q_2.emplace_back(in.b_scaling); - q_3.emplace_back(in.c_scaling); - q_c.emplace_back(in.const_scaling); - q_arith.emplace_back(1 + 1); - q_4.emplace_back(in.d_scaling); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(0); - q_logic.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - q_elliptic.emplace_back(0); - ++n; -} - -void PlookupComposer::create_big_mul_gate(const mul_quad& in) -{ - - PLOOKUP_SELECTOR_REFS - assert_valid_variables({ in.a, in.b, in.c, in.d }); - - w_l.emplace_back(in.a); - w_r.emplace_back(in.b); - w_o.emplace_back(in.c); - w_4.emplace_back(in.d); - q_m.emplace_back(in.mul_scaling); - q_1.emplace_back(in.a_scaling); - q_2.emplace_back(in.b_scaling); - q_3.emplace_back(in.c_scaling); - q_c.emplace_back(in.const_scaling); - q_arith.emplace_back(1); - q_4.emplace_back(in.d_scaling); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(0); - q_logic.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - q_elliptic.emplace_back(0); - ++n; -} - -// Creates a width-4 addition gate, where the fourth witness must be a boolean. -// Can be used to normalize a 32-bit addition -void PlookupComposer::create_balanced_add_gate(const add_quad& in) -{ - PLOOKUP_SELECTOR_REFS - assert_valid_variables({ in.a, in.b, in.c, in.d }); - - w_l.emplace_back(in.a); - w_r.emplace_back(in.b); - w_o.emplace_back(in.c); - w_4.emplace_back(in.d); - q_m.emplace_back(0); - q_1.emplace_back(in.a_scaling); - q_2.emplace_back(in.b_scaling); - q_3.emplace_back(in.c_scaling); - q_c.emplace_back(in.const_scaling); - q_arith.emplace_back(1); - q_4.emplace_back(in.d_scaling); - q_5.emplace_back(1); - q_ecc_1.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(0); - q_logic.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - q_elliptic.emplace_back(0); - ++n; -} - -void PlookupComposer::create_mul_gate(const mul_triple& in) -{ - - PLOOKUP_SELECTOR_REFS - assert_valid_variables({ in.a, in.b, in.c }); - - w_l.emplace_back(in.a); - w_r.emplace_back(in.b); - w_o.emplace_back(in.c); - w_4.emplace_back(zero_idx); - q_m.emplace_back(in.mul_scaling); - q_1.emplace_back(0); - q_2.emplace_back(0); - q_3.emplace_back(in.c_scaling); - q_c.emplace_back(in.const_scaling); - q_arith.emplace_back(1); - q_4.emplace_back(0); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(0); - q_logic.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - q_elliptic.emplace_back(0); - ++n; -} - -void PlookupComposer::create_bool_gate(const uint32_t variable_index) -{ - PLOOKUP_SELECTOR_REFS - assert_valid_variables({ variable_index }); - - w_l.emplace_back(variable_index); - w_r.emplace_back(variable_index); - w_o.emplace_back(variable_index); - w_4.emplace_back(zero_idx); - q_arith.emplace_back(1); - q_4.emplace_back(0); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(0); - q_m.emplace_back(1); - q_1.emplace_back(0); - q_2.emplace_back(0); - q_3.emplace_back(fr::neg_one()); - q_c.emplace_back(0); - q_logic.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - q_elliptic.emplace_back(0); - ++n; -} - -void PlookupComposer::create_poly_gate(const poly_triple& in) -{ - PLOOKUP_SELECTOR_REFS - assert_valid_variables({ in.a, in.b, in.c }); - - w_l.emplace_back(in.a); - w_r.emplace_back(in.b); - w_o.emplace_back(in.c); - w_4.emplace_back(zero_idx); - q_m.emplace_back(in.q_m); - q_1.emplace_back(in.q_l); - q_2.emplace_back(in.q_r); - q_3.emplace_back(in.q_o); - q_c.emplace_back(in.q_c); - q_range.emplace_back(0); - q_sort.emplace_back(0); - q_logic.emplace_back(0); - - q_arith.emplace_back(1); - q_4.emplace_back(0); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - q_elliptic.emplace_back(0); - ++n; -} - -// adds a grumpkin point, from a 2-bit lookup table, into an accumulator point -void PlookupComposer::create_fixed_group_add_gate(const fixed_group_add_quad& in) -{ - PLOOKUP_SELECTOR_REFS - assert_valid_variables({ in.a, in.b, in.c, in.d }); - - w_l.emplace_back(in.a); - w_r.emplace_back(in.b); - w_o.emplace_back(in.c); - w_4.emplace_back(in.d); - - q_arith.emplace_back(0); - q_4.emplace_back(0); - q_5.emplace_back(0); - q_m.emplace_back(0); - q_c.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(0); - q_logic.emplace_back(0); - - q_1.emplace_back(in.q_x_1); - q_2.emplace_back(in.q_x_2); - q_3.emplace_back(in.q_y_1); - q_ecc_1.emplace_back(in.q_y_2); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - q_elliptic.emplace_back(0); - ++n; -} - -// adds a grumpkin point into an accumulator, while also initializing the accumulator -void PlookupComposer::create_fixed_group_add_gate_with_init(const fixed_group_add_quad& in, - const fixed_group_init_quad& init) -{ - PLOOKUP_SELECTOR_REFS - assert_valid_variables({ in.a, in.b, in.c, in.d }); - - w_l.emplace_back(in.a); - w_r.emplace_back(in.b); - w_o.emplace_back(in.c); - w_4.emplace_back(in.d); - - q_arith.emplace_back(0); - q_4.emplace_back(init.q_x_1); - q_5.emplace_back(init.q_x_2); - q_m.emplace_back(init.q_y_1); - q_c.emplace_back(init.q_y_2); - q_range.emplace_back(0); - q_sort.emplace_back(0); - q_logic.emplace_back(0); - - q_1.emplace_back(in.q_x_1); - q_2.emplace_back(in.q_x_2); - q_3.emplace_back(in.q_y_1); - q_ecc_1.emplace_back(in.q_y_2); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - q_elliptic.emplace_back(0); - ++n; -} - -void PlookupComposer::create_fixed_group_add_gate_final(const add_quad& in) -{ - create_big_add_gate(in); -} - -void PlookupComposer::create_ecc_add_gate(const ecc_add_gate& in) -{ - /** - * | 1 | 2 | 3 | 4 | - * | a1 | a2 | x1 | y1 | - * | x2 | y2 | x3 | y3 | - * | -- | -- | x4 | y4 | - * - **/ - - PLOOKUP_SELECTOR_REFS - - assert_valid_variables({ in.x1, in.x2, in.x3, in.y1, in.y2, in.y3 }); - - bool can_fuse_into_previous_gate = true; - can_fuse_into_previous_gate = can_fuse_into_previous_gate && (w_r[n - 1] == in.x1); - can_fuse_into_previous_gate = can_fuse_into_previous_gate && (w_o[n - 1] == in.y1); - can_fuse_into_previous_gate = can_fuse_into_previous_gate && (q_3[n - 1] == 0); - can_fuse_into_previous_gate = can_fuse_into_previous_gate && (q_4[n - 1] == 0); - can_fuse_into_previous_gate = can_fuse_into_previous_gate && (q_5[n - 1] == 0); - can_fuse_into_previous_gate = can_fuse_into_previous_gate && (q_arith[n - 1] == 0); - - if (can_fuse_into_previous_gate) { - ASSERT(w_r[n - 1] == in.x1); - ASSERT(w_o[n - 1] == in.y1); - - q_3[n - 1] = in.endomorphism_coefficient; - q_4[n - 1] = in.endomorphism_coefficient.sqr(); - q_5[n - 1] = in.sign_coefficient; - q_elliptic[n - 1] = 1; - } else { - w_l.emplace_back(zero_idx); - w_r.emplace_back(in.x1); - w_o.emplace_back(in.y1); - w_4.emplace_back(zero_idx); - q_3.emplace_back(in.endomorphism_coefficient); - q_4.emplace_back(in.endomorphism_coefficient.sqr()); - q_5.emplace_back(in.sign_coefficient); - - q_arith.emplace_back(0); - q_1.emplace_back(0); - q_2.emplace_back(0); - q_m.emplace_back(0); - q_c.emplace_back(0); - q_ecc_1.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(0); - q_logic.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - q_elliptic.emplace_back(1); - ++n; - } - - w_l.emplace_back(in.x2); - w_4.emplace_back(in.y2); - w_r.emplace_back(in.x3); - w_o.emplace_back(in.y3); - q_m.emplace_back(0); - q_1.emplace_back(0); - q_2.emplace_back(0); - q_3.emplace_back(0); - q_c.emplace_back(0); - q_arith.emplace_back(0); - q_4.emplace_back(0); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(0); - q_logic.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - q_elliptic.emplace_back(0); - ++n; -} - -void PlookupComposer::fix_witness(const uint32_t witness_index, const barretenberg::fr& witness_value) -{ - PLOOKUP_SELECTOR_REFS - assert_valid_variables({ witness_index }); - - w_l.emplace_back(witness_index); - w_r.emplace_back(zero_idx); - w_o.emplace_back(zero_idx); - w_4.emplace_back(zero_idx); - q_m.emplace_back(0); - q_1.emplace_back(1); - q_2.emplace_back(0); - q_3.emplace_back(0); - q_c.emplace_back(-witness_value); - q_arith.emplace_back(1); - q_4.emplace_back(0); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(0); - q_logic.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - q_elliptic.emplace_back(0); - ++n; -} - -std::vector PlookupComposer::decompose_into_base4_accumulators(const uint32_t witness_index, - const size_t num_bits, - std::string const& msg) -{ - PLOOKUP_SELECTOR_REFS - assert_valid_variables({ witness_index }); - - ASSERT(((num_bits >> 1U) << 1U) == num_bits); - - /* - * The range constraint accumulates base 4 values into a sum. - * We do this by evaluating a kind of 'raster scan', where we compare adjacent elements - * and validate that their differences map to a base for value * - * Let's say that we want to perform a 32-bit range constraint in 'x'. - * We can represent x via 16 constituent base-4 'quads' {q_0, ..., q_15}: - * - * 15 - * === - * \ i - * x = / q . 4 - * === i - * i = 0 - * - * In program memory, we place an accumulating base-4 sum of x {a_0, ..., a_15}, where - * - * i - * === - * \ j - * a = / q . 4 - * i === (15 - j) - * j = 0 - * - * - * From this, we can use our range transition constraint to validate that - * - * - * a - 4 . a ϵ [0, 1, 2, 3] - * i + 1 i - * - * - * We place our accumulating sums in program memory in the following sequence: - * - * +-----+-----+-----+-----+ - * | A | B | C | D | - * +-----+-----+-----+-----+ - * | a3 | a2 | a1 | 0 | - * | a7 | a6 | a5 | a4 | - * | a11 | a10 | a9 | a8 | - * | a15 | a14 | a13 | a12 | - * | --- | --- | --- | a16 | - * +-----+-----+-----+-----+ - * - * Our range transition constraint on row 'i' - * performs our base-4 range check on the follwing pairs: - * - * (D_{i}, C_{i}), (C_{i}, B_{i}), (B_{i}, A_{i}), (A_{i}, D_{i+1}) - * - * We need to start our raster scan at zero, so we simplify matters and just force the first value - * to be zero. - * - * The output will be in the 4th column of an otherwise unused row. Assuming this row can - * be used for a width-3 standard gate, the total number of gates for an n-bit range constraint - * is (n / 8) gates - * - **/ - - const uint256_t witness_value(get_variable(witness_index)); - - // one gate accmulates 4 quads, or 8 bits. - // # gates = (bits / 8) - size_t num_quad_gates = (num_bits >> 3); - - num_quad_gates = (num_quad_gates << 3 == num_bits) ? num_quad_gates : num_quad_gates + 1; - - // hmm - std::vector* wires[4]{ &w_4, &w_o, &w_r, &w_l }; - - const size_t num_quads = (num_quad_gates << 2); - const size_t forced_zero_threshold = 1 + (((num_quads << 1) - num_bits) >> 1); - std::vector accumulators; - fr accumulator = 0; - - for (size_t i = 0; i < num_quads + 1; ++i) { - uint32_t accumulator_index; - if (i < forced_zero_threshold) { - accumulator_index = zero_idx; - } else { - const size_t bit_index = (num_quads - i) << 1; - const uint64_t quad = static_cast(witness_value.get_bit(bit_index)) + - 2ULL * static_cast(witness_value.get_bit(bit_index + 1)); - const fr quad_element = fr{ quad, 0, 0, 0 }.to_montgomery_form(); - accumulator += accumulator; - accumulator += accumulator; - accumulator += quad_element; - - accumulator_index = add_variable(accumulator); - accumulators.emplace_back(accumulator_index); - } - - // hmmmm - (*(wires + (i & 3)))->emplace_back(accumulator_index); - } - size_t used_gates = (num_quads + 1) / 4; - - // TODO: handle partially used gates. For now just set them to be zero - if (used_gates * 4 != (num_quads + 1)) { - ++used_gates; - } - - for (size_t i = 0; i < used_gates; ++i) { - q_m.emplace_back(0); - q_1.emplace_back(0); - q_2.emplace_back(0); - q_3.emplace_back(0); - q_c.emplace_back(0); - q_arith.emplace_back(0); - q_4.emplace_back(0); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_logic.emplace_back(0); - q_range.emplace_back(1); - q_sort.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - q_elliptic.emplace_back(0); - } - - q_range[q_range.size() - 1] = 0; - - w_l.emplace_back(zero_idx); - w_r.emplace_back(zero_idx); - w_o.emplace_back(zero_idx); - - assert_equal(accumulators[accumulators.size() - 1], witness_index, msg); - accumulators[accumulators.size() - 1] = witness_index; - - n += used_gates; - return accumulators; -} - -waffle::accumulator_triple PlookupComposer::create_logic_constraint(const uint32_t a, - const uint32_t b, - const size_t num_bits, - const bool is_xor_gate) -{ - PLOOKUP_SELECTOR_REFS - assert_valid_variables({ a, b }); - - ASSERT(((num_bits >> 1U) << 1U) == num_bits); // no odd number of bits! bad! only quads! - - /* - * The LOGIC constraint accumulates 3 base-4 values (a, b, c) into a sum, where c = a & b OR c = a ^ b - * - * In program memory, we place an accumulating base-4 sum of a, b, c {a_0, ..., a_15}, where - * - * i - * === - * \ j - * a = / q . 4 - * i === (15 - j) - * j = 0 - * - * - * From this, we can use our logic transition constraint to validate that - * - * - * a - 4 . a ϵ [0, 1, 2, 3] - * i + 1 i - * - * - * - * - * b - 4 . b ϵ [0, 1, 2, 3] - * i + 1 i - * - * - * - * - * / \ / \ - * c - 4 . c = | a - 4 . a | (& OR ^) | b - b . a | - * i + 1 i \ i + 1 i / \ i + 1 i / - * - * - * We also need the following temporary, w, stored in program memory: - * - * / \ / \ - * w = | a - 4 . a | * | b - b . a | - * i \ i + 1 i / \ i + 1 i / - * - * - * w is needed to prevent the degree of our quotient polynomial from blowing up - * - * We place our accumulating sums in program memory in the following sequence: - * - * +-----+-----+-----+-----+ - * | A | B | C | D | - * +-----+-----+-----+-----+ - * | 0 | 0 | w1 | 0 | - * | a1 | b1 | w2 | c1 | - * | a2 | b2 | w3 | c2 | - * | : | : | : | : | - * | an | bn | --- | cn | - * +-----+-----+-----+-----+ - * - * Our transition constraint extracts quads by taking the difference between two accumulating sums, - * so we need to start the chain with a row of zeroes - * - * The total number of gates required to evaluate an AND operation is (n / 2) + 1, - * where n = max(num_bits(a), num_bits(b)) - * - * One additional benefit of this constraint, is that both our inputs and output are in 'native' uint32 form. - * This means we *never* have to decompose a uint32 into bits and back in order to chain together - * addition and logic operations. - * - **/ - - const uint256_t left_witness_value(get_variable(a)); - const uint256_t right_witness_value(get_variable(b)); - - // one gate accmulates 1 quads, or 2 bits. - // # gates = (bits / 2) - const size_t num_quads = (num_bits >> 1); - - waffle::accumulator_triple accumulators; - fr left_accumulator = 0; - fr right_accumulator = 0; - fr out_accumulator = 0; - - // Step 1: populate 1st row accumulators with zero - w_l.emplace_back(zero_idx); - w_r.emplace_back(zero_idx); - w_4.emplace_back(zero_idx); - - // w_l, w_r, w_4 should now point to 1 gate ahead of w_o - for (size_t i = 0; i < num_quads; ++i) { - uint32_t left_accumulator_index; - uint32_t right_accumulator_index; - uint32_t out_accumulator_index; - uint32_t product_index; - - const size_t bit_index = (num_quads - 1 - i) << 1; - const uint64_t left_quad = static_cast(left_witness_value.get_bit(bit_index)) + - 2ULL * static_cast(left_witness_value.get_bit(bit_index + 1)); - - const uint64_t right_quad = static_cast(right_witness_value.get_bit(bit_index)) + - 2ULL * static_cast(right_witness_value.get_bit(bit_index + 1)); - const fr left_quad_element = fr{ left_quad, 0, 0, 0 }.to_montgomery_form(); - const fr right_quad_element = fr{ right_quad, 0, 0, 0 }.to_montgomery_form(); - fr out_quad_element; - if (is_xor_gate) { - out_quad_element = fr{ left_quad ^ right_quad, 0, 0, 0 }.to_montgomery_form(); - } else { - out_quad_element = fr{ left_quad & right_quad, 0, 0, 0 }.to_montgomery_form(); - } - - const fr product_quad_element = fr{ left_quad * right_quad, 0, 0, 0 }.to_montgomery_form(); - - left_accumulator += left_accumulator; - left_accumulator += left_accumulator; - left_accumulator += left_quad_element; - - right_accumulator += right_accumulator; - right_accumulator += right_accumulator; - right_accumulator += right_quad_element; - - out_accumulator += out_accumulator; - out_accumulator += out_accumulator; - out_accumulator += out_quad_element; - - left_accumulator_index = add_variable(left_accumulator); - accumulators.left.emplace_back(left_accumulator_index); - - right_accumulator_index = add_variable(right_accumulator); - accumulators.right.emplace_back(right_accumulator_index); - - out_accumulator_index = add_variable(out_accumulator); - accumulators.out.emplace_back(out_accumulator_index); - - product_index = add_variable(product_quad_element); - - w_l.emplace_back(left_accumulator_index); - w_r.emplace_back(right_accumulator_index); - w_4.emplace_back(out_accumulator_index); - w_o.emplace_back(product_index); - } - w_o.emplace_back(zero_idx); - - for (size_t i = 0; i < num_quads + 1; ++i) { - q_m.emplace_back(0); - q_1.emplace_back(0); - q_2.emplace_back(0); - q_3.emplace_back(0); - q_arith.emplace_back(0); - q_4.emplace_back(0); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(0); - if (is_xor_gate) { - q_c.emplace_back(fr::neg_one()); - q_logic.emplace_back(fr::neg_one()); - } else { - q_c.emplace_back(1); - q_logic.emplace_back(1); - } - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - q_elliptic.emplace_back(0); - } - q_c[q_c.size() - 1] = 0; // last gate is a noop - q_logic[q_logic.size() - 1] = 0; // last gate is a noop - - assert_equal(accumulators.left[accumulators.left.size() - 1], a); - accumulators.left[accumulators.left.size() - 1] = a; - - assert_equal(accumulators.right[accumulators.right.size() - 1], b); - accumulators.right[accumulators.right.size() - 1] = b; - - n += (num_quads + 1); - return accumulators; -} - -waffle::accumulator_triple PlookupComposer::create_and_constraint(const uint32_t a, - const uint32_t b, - const size_t num_bits) -{ - return create_logic_constraint(a, b, num_bits, false); -} - -waffle::accumulator_triple PlookupComposer::create_xor_constraint(const uint32_t a, - const uint32_t b, - const size_t num_bits) -{ - return create_logic_constraint(a, b, num_bits, true); -} - -uint32_t PlookupComposer::put_constant_variable(const barretenberg::fr& variable) -{ - if (constant_variables.count(variable) == 1) { - return constant_variables.at(variable); - } else { - uint32_t variable_index = add_variable(variable); - fix_witness(variable_index, variable); - constant_variables.insert({ variable, variable_index }); - return variable_index; - } -} - -void PlookupComposer::add_lookup_selector(polynomial& small, const std::string& tag) -{ - polynomial lagrange_base(small, circuit_proving_key->small_domain.size); - small.ifft(circuit_proving_key->small_domain); - polynomial large(small, circuit_proving_key->n * 4); - large.coset_fft(circuit_proving_key->large_domain); - - circuit_proving_key->polynomial_cache.put(tag, std::move(small)); - circuit_proving_key->polynomial_cache.put(tag + "_lagrange", std::move(lagrange_base)); - circuit_proving_key->polynomial_cache.put(tag + "_fft", std::move(large)); -} - -std::shared_ptr PlookupComposer::compute_proving_key() -{ - PLOOKUP_SELECTOR_REFS; - if (circuit_proving_key) { - return circuit_proving_key; - } - - ASSERT(n == q_m.size()); - ASSERT(n == q_c.size()); - ASSERT(n == q_1.size()); - ASSERT(n == q_2.size()); - ASSERT(n == q_3.size()); - ASSERT(n == q_3.size()); - ASSERT(n == q_4.size()); - ASSERT(n == q_5.size()); - ASSERT(n == q_arith.size()); - ASSERT(n == q_ecc_1.size()); - ASSERT(n == q_elliptic.size()); - ASSERT(n == q_range.size()); - ASSERT(n == q_sort.size()); - ASSERT(n == q_logic.size()); - ASSERT(n == q_lookup_index.size()); - ASSERT(n == q_lookup_type.size()); - - size_t tables_size = 0; - size_t lookups_size = 0; - for (const auto& table : lookup_tables) { - tables_size += table.size; - lookups_size += table.lookup_gates.size(); - } - - ComposerBase::compute_proving_key_base(type, tables_size + lookups_size, NUM_RESERVED_GATES); - - const size_t subgroup_size = circuit_proving_key->n; - - polynomial poly_q_table_1(subgroup_size); - polynomial poly_q_table_2(subgroup_size); - polynomial poly_q_table_3(subgroup_size); - polynomial poly_q_table_4(subgroup_size); - size_t offset = subgroup_size - tables_size - s_randomness; - - for (size_t i = 0; i < offset; ++i) { - poly_q_table_1[i] = 0; - poly_q_table_2[i] = 0; - poly_q_table_3[i] = 0; - poly_q_table_4[i] = 0; - } - - for (const auto& table : lookup_tables) { - const fr table_index(table.table_index); - - for (size_t i = 0; i < table.size; ++i) { - poly_q_table_1[offset] = table.column_1[i]; - poly_q_table_2[offset] = table.column_2[i]; - poly_q_table_3[offset] = table.column_3[i]; - poly_q_table_4[offset] = table_index; - ++offset; - } - } - - // Initialise the last `s_randomness` positions in table polynomials with 0. - // These will be the positions where we will be adding random scalars to add zero knowledge - // to plookup. - for (size_t i = 0; i < s_randomness; ++i) { - poly_q_table_1[offset] = 0; - poly_q_table_2[offset] = 0; - poly_q_table_3[offset] = 0; - poly_q_table_4[offset] = 0; - ++offset; - } - - add_lookup_selector(poly_q_table_1, "table_value_1"); - add_lookup_selector(poly_q_table_2, "table_value_2"); - add_lookup_selector(poly_q_table_3, "table_value_3"); - add_lookup_selector(poly_q_table_4, "table_value_4"); - - polynomial z_lookup_fft(subgroup_size * 4, subgroup_size * 4); - polynomial s_fft(subgroup_size * 4, subgroup_size * 4); - circuit_proving_key->polynomial_cache.put("z_lookup_fft", std::move(z_lookup_fft)); - circuit_proving_key->polynomial_cache.put("s_fft", std::move(s_fft)); - - compute_sigma_permutations<4, true>(circuit_proving_key.get()); - - return circuit_proving_key; -} - -std::shared_ptr PlookupComposer::compute_verification_key() -{ - if (circuit_verification_key) { - return circuit_verification_key; - } - if (!circuit_proving_key) { - compute_proving_key(); - } - circuit_verification_key = - plookup_composer::compute_verification_key(circuit_proving_key, crs_factory_->get_verifier_crs()); - circuit_verification_key->composer_type = type; - return circuit_verification_key; -} - -void PlookupComposer::compute_witness() -{ - if (computed_witness) { - return; - } - - size_t tables_size = 0; - size_t lookups_size = 0; - for (const auto& table : lookup_tables) { - tables_size += table.size; - lookups_size += table.lookup_gates.size(); - } - - const size_t filled_gates = n + public_inputs.size(); - const size_t total_num_gates = std::max(filled_gates, tables_size + lookups_size); - - const size_t subgroup_size = get_circuit_subgroup_size(total_num_gates + NUM_RESERVED_GATES); - - for (size_t i = filled_gates; i < subgroup_size; ++i) { - w_l.emplace_back(zero_idx); - w_r.emplace_back(zero_idx); - w_o.emplace_back(zero_idx); - w_4.emplace_back(zero_idx); - } - - polynomial poly_w_1(subgroup_size); - polynomial poly_w_2(subgroup_size); - polynomial poly_w_3(subgroup_size); - polynomial poly_w_4(subgroup_size); - polynomial s_1(subgroup_size); - polynomial s_2(subgroup_size); - polynomial s_3(subgroup_size); - polynomial s_4(subgroup_size); - for (size_t i = 0; i < public_inputs.size(); ++i) { - poly_w_1[i] = 0; - poly_w_2[i] = variables[public_inputs[i]]; - poly_w_3[i] = 0; - poly_w_4[i] = 0; - } - for (size_t i = public_inputs.size(); i < subgroup_size; ++i) { - poly_w_1[i] = variables[w_l[i - public_inputs.size()]]; - poly_w_2[i] = variables[w_r[i - public_inputs.size()]]; - poly_w_3[i] = variables[w_o[i - public_inputs.size()]]; - poly_w_4[i] = variables[w_4[i - public_inputs.size()]]; - } - - // Save space for adding random scalars in s polynomial later - // We need to make space for adding randomness into witness polynomials and - // lookup polynomials. - size_t count = subgroup_size - tables_size - lookups_size - s_randomness; - for (size_t i = 0; i < count; ++i) { - s_1[i] = 0; - s_2[i] = 0; - s_3[i] = 0; - s_4[i] = 0; - } - - for (auto& table : lookup_tables) { - const fr table_index(table.table_index); - auto& lookup_gates = table.lookup_gates; - for (size_t i = 0; i < table.size; ++i) { - if (table.use_twin_keys) { - lookup_gates.push_back({ - { - table.column_1[i].from_montgomery_form().data[0], - table.column_2[i].from_montgomery_form().data[0], - }, - { - table.column_3[i], - 0, - }, - }); - } else { - lookup_gates.push_back({ - { - table.column_1[i].from_montgomery_form().data[0], - 0, - }, - { - table.column_2[i], - table.column_3[i], - }, - }); - } - } - - std::sort(lookup_gates.begin(), lookup_gates.end()); - - for (const auto& entry : lookup_gates) { - const auto components = entry.to_sorted_list_components(table.use_twin_keys); - s_1[count] = components[0]; - s_2[count] = components[1]; - s_3[count] = components[2]; - s_4[count] = table_index; - ++count; - } - } - - // Initialise the last `s_randomness` positions in s polynomials with 0. - // These will be the positions where we will be adding random scalars to add zero knowledge - // to plookup. - for (size_t i = 0; i < s_randomness; ++i) { - s_1[count] = 0; - s_2[count] = 0; - s_3[count] = 0; - s_4[count] = 0; - ++count; - } - - circuit_proving_key->polynomial_cache.put("w_1_lagrange", std::move(poly_w_1)); - circuit_proving_key->polynomial_cache.put("w_2_lagrange", std::move(poly_w_2)); - circuit_proving_key->polynomial_cache.put("w_3_lagrange", std::move(poly_w_3)); - circuit_proving_key->polynomial_cache.put("w_4_lagrange", std::move(poly_w_4)); - circuit_proving_key->polynomial_cache.put("s_1_lagrange", std::move(s_1)); - circuit_proving_key->polynomial_cache.put("s_2_lagrange", std::move(s_2)); - circuit_proving_key->polynomial_cache.put("s_3_lagrange", std::move(s_3)); - circuit_proving_key->polynomial_cache.put("s_4_lagrange", std::move(s_4)); - - computed_witness = true; -} - -PlookupProver PlookupComposer::create_prover() -{ - compute_proving_key(); - compute_witness(); - PlookupProver output_state(circuit_proving_key, create_manifest(public_inputs.size())); - - std::unique_ptr> permutation_widget = - std::make_unique>(circuit_proving_key.get()); - std::unique_ptr> plookup_widget = - std::make_unique>(circuit_proving_key.get()); - - std::unique_ptr> arithmetic_widget = - std::make_unique>(circuit_proving_key.get()); - std::unique_ptr> fixed_base_widget = - std::make_unique>(circuit_proving_key.get()); - std::unique_ptr> sort_widget = - std::make_unique>(circuit_proving_key.get()); - std::unique_ptr> logic_widget = - std::make_unique>(circuit_proving_key.get()); - std::unique_ptr> elliptic_widget = - std::make_unique>(circuit_proving_key.get()); - - output_state.random_widgets.emplace_back(std::move(permutation_widget)); - output_state.random_widgets.emplace_back(std::move(plookup_widget)); - - output_state.transition_widgets.emplace_back(std::move(arithmetic_widget)); - output_state.transition_widgets.emplace_back(std::move(fixed_base_widget)); - output_state.transition_widgets.emplace_back(std::move(sort_widget)); - output_state.transition_widgets.emplace_back(std::move(logic_widget)); - output_state.transition_widgets.emplace_back(std::move(elliptic_widget)); - - std::unique_ptr> kate_commitment_scheme = - std::make_unique>(); - - output_state.commitment_scheme = std::move(kate_commitment_scheme); - - return output_state; -} - -UnrolledPlookupProver PlookupComposer::create_unrolled_prover() -{ - compute_proving_key(); - compute_witness(); - - UnrolledPlookupProver output_state(circuit_proving_key, create_unrolled_manifest(public_inputs.size())); - - std::unique_ptr> permutation_widget = - std::make_unique>(circuit_proving_key.get()); - std::unique_ptr> plookup_widget = - std::make_unique>(circuit_proving_key.get()); - - std::unique_ptr> arithmetic_widget = - std::make_unique>(circuit_proving_key.get()); - std::unique_ptr> fixed_base_widget = - std::make_unique>(circuit_proving_key.get()); - std::unique_ptr> sort_widget = - std::make_unique>(circuit_proving_key.get()); - std::unique_ptr> logic_widget = - std::make_unique>(circuit_proving_key.get()); - std::unique_ptr> elliptic_widget = - std::make_unique>(circuit_proving_key.get()); - - output_state.random_widgets.emplace_back(std::move(permutation_widget)); - output_state.random_widgets.emplace_back(std::move(plookup_widget)); - - output_state.transition_widgets.emplace_back(std::move(arithmetic_widget)); - output_state.transition_widgets.emplace_back(std::move(fixed_base_widget)); - output_state.transition_widgets.emplace_back(std::move(sort_widget)); - output_state.transition_widgets.emplace_back(std::move(logic_widget)); - output_state.transition_widgets.emplace_back(std::move(elliptic_widget)); - - std::unique_ptr> kate_commitment_scheme = - std::make_unique>(); - - output_state.commitment_scheme = std::move(kate_commitment_scheme); - - return output_state; -} - -PlookupVerifier PlookupComposer::create_verifier() -{ - compute_verification_key(); - - PlookupVerifier output_state(circuit_verification_key, create_manifest(public_inputs.size())); - - std::unique_ptr> kate_commitment_scheme = - std::make_unique>(); - - output_state.commitment_scheme = std::move(kate_commitment_scheme); - - return output_state; -} - -UnrolledPlookupVerifier PlookupComposer::create_unrolled_verifier() -{ - compute_verification_key(); - - UnrolledPlookupVerifier output_state(circuit_verification_key, create_unrolled_manifest(public_inputs.size())); - - std::unique_ptr> kate_commitment_scheme = - std::make_unique>(); - - output_state.commitment_scheme = std::move(kate_commitment_scheme); - - return output_state; -} - -void PlookupComposer::initialize_precomputed_table( - const PlookupBasicTableId id, - bool (*generator)(std::vector&, std ::vector&, std::vector&), - std::array (*get_values_from_key)(const std::array)) -{ - for (auto table : lookup_tables) { - ASSERT(table.id != id); - } - PlookupBasicTable new_table; - new_table.id = id; - new_table.table_index = lookup_tables.size() + 1; - new_table.use_twin_keys = generator(new_table.column_1, new_table.column_2, new_table.column_3); - new_table.size = new_table.column_1.size(); - new_table.get_values_from_key = get_values_from_key; - lookup_tables.emplace_back(new_table); -} - -PlookupBasicTable& PlookupComposer::get_table(const PlookupBasicTableId id) -{ - for (PlookupBasicTable& table : lookup_tables) { - if (table.id == id) { - return table; - } - } - // Hmm. table doesn't exist! try to create it - lookup_tables.emplace_back(plookup::create_basic_table(id, lookup_tables.size())); - return lookup_tables[lookup_tables.size() - 1]; -} - -std::array, 3> PlookupComposer::read_sequence_from_multi_table( - const PlookupMultiTableId& id, - const PlookupReadData& read_values, - const uint32_t key_a_index, - std::optional key_b_index) - -{ - PLOOKUP_SELECTOR_REFS; - const auto& multi_table = plookup::create_table(id); - const size_t num_lookups = read_values.column_1_accumulator_values.size(); - std::array, 3> column_indices; - for (size_t i = 0; i < num_lookups; ++i) { - auto& table = get_table(multi_table.lookup_ids[i]); - - table.lookup_gates.emplace_back(read_values.key_entries[i]); - - const auto first_idx = (i == 0) ? key_a_index : add_variable(read_values.column_1_accumulator_values[i]); - const auto second_idx = (i == 0 && (key_b_index.has_value())) - ? key_b_index.value() - : add_variable(read_values.column_2_accumulator_values[i]); - const auto third_idx = add_variable(read_values.column_3_accumulator_values[i]); - - column_indices[0].push_back(first_idx); - column_indices[1].push_back(second_idx); - column_indices[2].push_back(third_idx); - assert_valid_variables({ first_idx, second_idx, third_idx }); - - q_lookup_type.emplace_back(fr(1)); - q_lookup_index.emplace_back(fr(table.table_index)); - w_l.emplace_back(first_idx); - w_r.emplace_back(second_idx); - w_o.emplace_back(third_idx); - w_4.emplace_back(zero_idx); - q_1.emplace_back(0); - q_2.emplace_back((i == (num_lookups - 1) ? 0 : -multi_table.column_1_step_sizes[i + 1])); - q_3.emplace_back(0); - q_m.emplace_back((i == (num_lookups - 1) ? 0 : -multi_table.column_2_step_sizes[i + 1])); - q_c.emplace_back((i == (num_lookups - 1) ? 0 : -multi_table.column_3_step_sizes[i + 1])); - q_arith.emplace_back(0); - q_4.emplace_back(0); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(0); - q_logic.emplace_back(0); - q_elliptic.emplace_back(0); - - ++n; - } - return column_indices; -} - -/** - * Generalized Permutation Methods - **/ - -PlookupComposer::RangeList PlookupComposer::create_range_list(const uint64_t target_range) -{ - RangeList result; - const auto range_tag = get_new_tag(); // current_tag + 1; - const auto tau_tag = get_new_tag(); // current_tag + 2; - create_tag(range_tag, tau_tag); - create_tag(tau_tag, range_tag); - result.target_range = target_range; - result.range_tag = range_tag; - result.tau_tag = tau_tag; - - uint64_t num_multiples_of_three = (target_range / 3); - - result.variable_indices.reserve((uint32_t)num_multiples_of_three); - for (uint64_t i = 0; i <= num_multiples_of_three; ++i) { - const uint32_t index = add_variable(i * 3); - result.variable_indices.emplace_back(index); - assign_tag(index, result.range_tag); - } - { - const uint32_t index = add_variable(target_range); - result.variable_indices.emplace_back(index); - assign_tag(index, result.range_tag); - } - // Need this because these variables will not appear in the witness otherwise - create_dummy_constraints(result.variable_indices); - - return result; -} -// range constraint a value by decomposing it into limbs whose size should be the default range constraint size -std::vector PlookupComposer::decompose_into_default_range(const uint32_t variable_index, - const size_t num_bits, - std::string const& msg) -{ - assert_valid_variables({ variable_index }); - - ASSERT(num_bits > 0); - std::vector sums; - const size_t limb_num = (size_t)num_bits / DEFAULT_PLOOKUP_RANGE_BITNUM; - const size_t last_limb_size = num_bits - (limb_num * DEFAULT_PLOOKUP_RANGE_BITNUM); - if (limb_num < 2) { - std::cerr << "number of bits in range must be at least twice default range size" << std::endl; - return sums; - } - - const uint256_t val = (uint256_t)(get_variable(variable_index)); - // check witness value is indeed in range (commented out cause interferes with negative tests) - // ASSERT(val < ((uint256_t)1 << num_bits) - 1); // Q:ask Zac what happens with wrapping when converting fr to - std::vector val_limbs; - std::vector val_slices; - for (size_t i = 0; i < limb_num; i++) { - val_slices.emplace_back( - barretenberg::fr(val.slice(DEFAULT_PLOOKUP_RANGE_BITNUM * i, DEFAULT_PLOOKUP_RANGE_BITNUM * (i + 1)))); - val_limbs.emplace_back(add_variable(val_slices[i])); - create_new_range_constraint(val_limbs[i], DEFAULT_PLOOKUP_RANGE_SIZE); - } - - uint64_t last_limb_range = ((uint64_t)1 << last_limb_size) - 1; - size_t total_limb_num = limb_num; - if (last_limb_size > 0) { - val_slices.emplace_back(fr(val.slice(num_bits - last_limb_size, num_bits))); - - val_limbs.emplace_back(add_variable(val_slices[val_slices.size() - 1])); - create_new_range_constraint(val_limbs[val_limbs.size() - 1], last_limb_range); - total_limb_num++; - } - // pad slices and limbs in case there are odd num of them - if (total_limb_num % 2 == 1) { - val_limbs.emplace_back(zero_idx); // TODO: check this is zero - val_slices.emplace_back(0); - total_limb_num++; - } - fr shift = fr(1 << DEFAULT_PLOOKUP_RANGE_BITNUM); - fr second_shift = shift * shift; - sums.emplace_back(add_variable(val_slices[0] + shift * val_slices[1])); - create_add_gate({ val_limbs[0], val_limbs[1], sums[0], 1, shift, -1, 0 }); - fr cur_shift = (second_shift); - fr cur_second_shift = cur_shift * shift; - for (size_t i = 2; i < total_limb_num; i = i + 2) { - sums.emplace_back(add_variable(get_variable(sums[sums.size() - 1]) + cur_shift * val_slices[i] + - cur_second_shift * val_slices[i + 1])); - create_big_add_gate({ sums[sums.size() - 2], - val_limbs[i], - val_limbs[i + 1], - sums[sums.size() - 1], - 1, - cur_shift, - cur_second_shift, - -1, - 0 }); - cur_shift *= second_shift; - cur_second_shift *= second_shift; - } - assert_equal(sums[sums.size() - 1], variable_index, msg); - return sums; -} -void PlookupComposer::create_new_range_constraint(const uint32_t variable_index, const uint64_t target_range) -{ - if (range_lists.count(target_range) == 0) { - range_lists.insert({ target_range, create_range_list(target_range) }); - } - - auto& list = range_lists[target_range]; - assign_tag(variable_index, list.range_tag); - list.variable_indices.emplace_back(variable_index); -} -void PlookupComposer::process_range_list(const RangeList& list) -{ - assert_valid_variables(list.variable_indices); - - ASSERT(list.variable_indices.size() > 0); - // go over variables - // for each variable, create mirror variable with same value - with tau tag - // need to make sure that, in original list, increments of at most 3 - std::vector sorted_list; - sorted_list.reserve(list.variable_indices.size()); - for (const auto variable_index : list.variable_indices) { - const auto& field_element = get_variable(variable_index); - const uint64_t shrinked_value = field_element.from_montgomery_form().data[0]; - sorted_list.emplace_back(shrinked_value); - } - std::sort(sorted_list.begin(), sorted_list.end()); - std::vector indices; - - // list must be padded to a multiple of 4 and larger than 4 - size_t padding = (4 - (list.variable_indices.size() % 4)) % 4; // TODO: this 4 maybe tied to program_width - if (list.variable_indices.size() == 4) - padding += 4; - for (size_t i = 0; i < padding; ++i) { - indices.emplace_back(zero_idx); - } - for (const auto sorted_value : sorted_list) { - const uint32_t index = add_variable(sorted_value); - assign_tag(index, list.tau_tag); - indices.emplace_back(index); - } - create_sort_constraint_with_edges(indices, 0, list.target_range); -} -void PlookupComposer::process_range_lists() -{ - for (const auto& i : range_lists) - process_range_list(i.second); -} -/* - Create range constraint: - * add variable index to a list of range constrained variables - * data structures: vector of lists, each list contains: - * - the range size - * - the list of variables in the range - * - a generalised permutation tag - * - * create range constraint parameters: variable index && range size - * - * std::map range_lists; -*/ -// Check for a sequence of variables that neighboring differences are at most 3 (used for batched range checkj) -void PlookupComposer::create_sort_constraint(const std::vector& variable_index) -{ - PLOOKUP_SELECTOR_REFS - ASSERT(variable_index.size() % 4 == 0); - assert_valid_variables(variable_index); - - for (size_t i = 0; i < variable_index.size(); i += 4) { - w_l.emplace_back(variable_index[i]); - w_r.emplace_back(variable_index[i + 1]); - w_o.emplace_back(variable_index[i + 2]); - w_4.emplace_back(variable_index[i + 3]); - ++n; - q_m.emplace_back(0); - q_1.emplace_back(0); - q_2.emplace_back(0); - q_3.emplace_back(0); - q_c.emplace_back(0); - q_arith.emplace_back(0); - q_4.emplace_back(0); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_logic.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(1); - q_elliptic.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - } - // dummy gate needed because of sort widget's check of next row - w_l.emplace_back(variable_index[variable_index.size() - 1]); - w_r.emplace_back(zero_idx); - w_o.emplace_back(zero_idx); - w_4.emplace_back(zero_idx); - ++n; - q_m.emplace_back(0); - q_1.emplace_back(0); - q_2.emplace_back(0); - q_3.emplace_back(0); - q_c.emplace_back(0); - q_arith.emplace_back(0); - q_4.emplace_back(0); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_logic.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(0); - q_elliptic.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); -} -// useful to put variables in the witness that aren't already used - e.g. the dummy variables of the range constraint in -// multiples of three -void PlookupComposer::create_dummy_constraints(const std::vector& variable_index) -{ - PLOOKUP_SELECTOR_REFS - // ASSERT(variable_index.size() % 4 == 0); - std::vector padded_list = variable_index; - const uint64_t padding = (4 - (padded_list.size() % 4)) % 4; - for (uint64_t i = 0; i < padding; ++i) { - padded_list.emplace_back(zero_idx); - } - assert_valid_variables(variable_index); - assert_valid_variables(padded_list); - - for (size_t i = 0; i < padded_list.size(); i += 4) { - - w_l.emplace_back(padded_list[i]); - w_r.emplace_back(padded_list[i + 1]); - w_o.emplace_back(padded_list[i + 2]); - w_4.emplace_back(padded_list[i + 3]); - ++n; - q_m.emplace_back(0); - q_1.emplace_back(0); - q_2.emplace_back(0); - q_3.emplace_back(0); - q_c.emplace_back(0); - q_arith.emplace_back(0); - q_4.emplace_back(0); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_logic.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(0); - q_elliptic.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - } -} -// Check for a sequence of variables that neighboring differences are at most 3 (used for batched range checks) -void PlookupComposer::create_sort_constraint_with_edges(const std::vector& variable_index, - const fr& start, - const fr& end) -{ - PLOOKUP_SELECTOR_REFS - // Convenient to assume size is at least 8 for separate gates for start and end conditions - ASSERT(variable_index.size() % 4 == 0 && variable_index.size() > 4); - assert_valid_variables(variable_index); - - // enforce range checks of first row and starting at start - w_l.emplace_back(variable_index[0]); - w_r.emplace_back(variable_index[1]); - w_o.emplace_back(variable_index[2]); - w_4.emplace_back(variable_index[3]); - ++n; - q_m.emplace_back(0); - q_1.emplace_back(1); - q_2.emplace_back(0); - q_3.emplace_back(0); - q_c.emplace_back(-start); - q_arith.emplace_back(1); - q_4.emplace_back(0); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_logic.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(1); - q_elliptic.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - // enforce range check for middle rows - for (size_t i = 4; i < variable_index.size() - 4; i += 4) { - - w_l.emplace_back(variable_index[i]); - w_r.emplace_back(variable_index[i + 1]); - w_o.emplace_back(variable_index[i + 2]); - w_4.emplace_back(variable_index[i + 3]); - ++n; - q_m.emplace_back(0); - q_1.emplace_back(0); - q_2.emplace_back(0); - q_3.emplace_back(0); - q_c.emplace_back(0); - q_arith.emplace_back(0); - q_4.emplace_back(0); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_logic.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(1); - q_elliptic.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - } - // enforce range checks of last row and ending at end - w_l.emplace_back(variable_index[variable_index.size() - 4]); - w_r.emplace_back(variable_index[variable_index.size() - 3]); - w_o.emplace_back(variable_index[variable_index.size() - 2]); - w_4.emplace_back(variable_index[variable_index.size() - 1]); - ++n; - q_m.emplace_back(0); - q_1.emplace_back(0); - q_2.emplace_back(0); - q_3.emplace_back(0); - q_c.emplace_back(-end); - q_arith.emplace_back(1); - q_4.emplace_back(1); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_logic.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(1); - q_elliptic.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); - // dummy gate needed because of sort widget's check of next row - w_l.emplace_back(variable_index[variable_index.size() - 1]); - w_r.emplace_back(zero_idx); - w_o.emplace_back(zero_idx); - w_4.emplace_back(zero_idx); - ++n; - q_m.emplace_back(0); - q_1.emplace_back(0); - q_2.emplace_back(0); - q_3.emplace_back(0); - q_c.emplace_back(0); - q_arith.emplace_back(0); - q_4.emplace_back(0); - q_5.emplace_back(0); - q_ecc_1.emplace_back(0); - q_logic.emplace_back(0); - q_range.emplace_back(0); - q_sort.emplace_back(0); - q_elliptic.emplace_back(0); - q_lookup_index.emplace_back(0); - q_lookup_type.emplace_back(0); -} - -// range constraint a value by decomposing it into limbs whose size should be the default range constraint size -std::vector PlookupComposer::decompose_into_default_range_better_for_oddlimbnum(const uint32_t variable_index, - const size_t num_bits, - std::string const& msg) -{ - std::vector sums; - const size_t limb_num = (size_t)num_bits / DEFAULT_PLOOKUP_RANGE_BITNUM; - const size_t last_limb_size = num_bits - (limb_num * DEFAULT_PLOOKUP_RANGE_BITNUM); - if (limb_num < 3) { - std::cerr - << "number of bits in range must be an integer multipe of DEFAULT_PLOOKUP_RANGE_BITNUM of size at least 3" - << std::endl; - return sums; - } - - const uint256_t val = (uint256_t)(get_variable(variable_index)); - // check witness value is indeed in range (commented out cause interferes with negative tests) - // ASSERT(val < ((uint256_t)1 << num_bits) - 1); // Q:ask Zac what happens with wrapping when converting fr to - // uint256 - // ASSERT(limb_num % 3 == 0); // TODO: write version of method that doesn't need this - std::vector val_limbs; - std::vector val_slices; - for (size_t i = 0; i < limb_num; i++) { - val_slices.emplace_back( - barretenberg::fr(val.slice(DEFAULT_PLOOKUP_RANGE_BITNUM * i, DEFAULT_PLOOKUP_RANGE_BITNUM * (i + 1) - 1))); - val_limbs.emplace_back(add_variable(val_slices[i])); - create_new_range_constraint(val_limbs[i], DEFAULT_PLOOKUP_RANGE_SIZE); - } - - uint64_t last_limb_range = ((uint64_t)1 << last_limb_size) - 1; - fr last_slice(0); - uint32_t last_limb(zero_idx); - size_t total_limb_num = limb_num; - if (last_limb_size > 0) { - val_slices.emplace_back(fr(val.slice(num_bits - last_limb_size, num_bits))); - val_limbs.emplace_back(add_variable(last_slice)); - create_new_range_constraint(last_limb, last_limb_range); - total_limb_num++; - } - // pad slices and limbs in case they are not 2 mod 3 - if (total_limb_num % 3 == 1) { - val_limbs.emplace_back(zero_idx); // TODO: check this is zero - val_slices.emplace_back(0); - total_limb_num++; - } - fr shift = fr(1 << DEFAULT_PLOOKUP_RANGE_BITNUM); - fr second_shift = shift * shift; - sums.emplace_back(add_variable(val_slices[0] + shift * val_slices[1] + second_shift * val_slices[2])); - create_big_add_gate({ val_limbs[0], val_limbs[1], val_limbs[2], sums[0], 1, shift, second_shift, -1, 0 }); - fr cur_shift = (shift * second_shift); - fr cur_second_shift = cur_shift * shift; - for (size_t i = 3; i < total_limb_num; i = i + 2) { - sums.emplace_back(add_variable(get_variable(sums[sums.size() - 1]) + cur_shift * val_slices[i] + - cur_second_shift * val_slices[i + 1])); - create_big_add_gate({ sums[sums.size() - 2], - val_limbs[i], - val_limbs[i + 1], - sums[sums.size() - 1], - 1, - cur_shift, - cur_second_shift, - -1, - 0 }); - cur_shift *= second_shift; - cur_second_shift *= second_shift; - } - assert_equal(sums[sums.size() - 1], variable_index, msg); - return sums; -} - -} // namespace waffle diff --git a/cpp/src/aztec/plonk/composer/plookup_composer.hpp b/cpp/src/aztec/plonk/composer/plookup_composer.hpp deleted file mode 100644 index 236ddb02b5..0000000000 --- a/cpp/src/aztec/plonk/composer/plookup_composer.hpp +++ /dev/null @@ -1,345 +0,0 @@ -#pragma once -#include "composer_base.hpp" -#include "plookup_tables/plookup_tables.hpp" -#include - -namespace waffle { - -class PlookupComposer : public ComposerBase { - - public: - static constexpr ComposerType type = ComposerType::PLOOKUP; - static constexpr size_t NUM_PLOOKUP_SELECTORS = 15; - static constexpr size_t NUM_RESERVED_GATES = 4; // this must be >= num_roots_cut_out_of_vanishing_polynomial - static constexpr size_t UINT_LOG2_BASE = 6; - // the plookup range proof requires work linear in range size, thus cannot be used directly for - // large ranges such as 2^64. For such ranges the element will be decomposed into smaller - // chuncks according to the parameter below - static constexpr size_t DEFAULT_PLOOKUP_RANGE_BITNUM = 17; - static constexpr size_t DEFAULT_PLOOKUP_RANGE_SIZE = (1 << DEFAULT_PLOOKUP_RANGE_BITNUM) - 1; - - struct RangeList { - uint64_t target_range; - uint32_t range_tag; - uint32_t tau_tag; - std::vector variable_indices; - }; - - enum PlookupSelectors { - QM = 0, - QC = 1, - Q1 = 2, - Q2 = 3, - Q3 = 4, - Q4 = 5, - Q5 = 6, - QARITH = 7, - QECC_1 = 8, - QRANGE = 9, - QSORT = 10, - QLOGIC = 11, - QELLIPTIC = 12, - QLOOKUPINDEX = 13, - QLOOKUPTYPE = 14, - }; - - PlookupComposer(); - PlookupComposer(std::string const& crs_path, const size_t size_hint = 0); - PlookupComposer(std::unique_ptr&& crs_factory, const size_t size_hint = 0); - PlookupComposer(std::shared_ptr const& p_key, - std::shared_ptr const& v_key, - size_t size_hint = 0); - PlookupComposer(PlookupComposer&& other) = default; - PlookupComposer& operator=(PlookupComposer&& other) = default; - ~PlookupComposer() {} - - std::shared_ptr compute_proving_key() override; - std::shared_ptr compute_verification_key() override; - void compute_witness() override; - - PlookupProver create_prover(); - PlookupVerifier create_verifier(); - - UnrolledPlookupProver create_unrolled_prover(); - UnrolledPlookupVerifier create_unrolled_verifier(); - - void create_add_gate(const add_triple& in) override; - - void create_big_add_gate(const add_quad& in); - void create_big_add_gate_with_bit_extraction(const add_quad& in); - void create_big_mul_gate(const mul_quad& in); - void create_balanced_add_gate(const add_quad& in); - - void create_mul_gate(const mul_triple& in) override; - void create_bool_gate(const uint32_t a) override; - void create_poly_gate(const poly_triple& in) override; - void create_fixed_group_add_gate(const fixed_group_add_quad& in); - void create_fixed_group_add_gate_with_init(const fixed_group_add_quad& in, const fixed_group_init_quad& init); - void create_fixed_group_add_gate_final(const add_quad& in); - - void create_ecc_add_gate(const ecc_add_gate& in); - - void fix_witness(const uint32_t witness_index, const barretenberg::fr& witness_value); - - std::vector decompose_into_base4_accumulators(const uint32_t witness_index, - const size_t num_bits, - std::string const& msg = "create_range_constraint"); - accumulator_triple create_logic_constraint(const uint32_t a, - const uint32_t b, - const size_t num_bits, - bool is_xor_gate); - accumulator_triple create_and_constraint(const uint32_t a, const uint32_t b, const size_t num_bits); - accumulator_triple create_xor_constraint(const uint32_t a, const uint32_t b, const size_t num_bits); - - uint32_t put_constant_variable(const barretenberg::fr& variable); - - size_t get_num_constant_gates() const override { return 0; } - - void assert_equal_constant(const uint32_t a_idx, - const barretenberg::fr& b, - std::string const& msg = "assert equal constant") - { - if (variables[a_idx] != b && !failed) { - failed = true; - err = msg; - } - auto b_idx = put_constant_variable(b); - assert_equal(a_idx, b_idx, msg); - } - - /** - * Plookup Methods - **/ - void add_lookup_selector(polynomial& small, const std::string& tag); - void initialize_precomputed_table( - const PlookupBasicTableId id, - bool (*generator)(std::vector&, - std::vector&, - std::vector&), - std::array (*get_values_from_key)(const std::array)); - - PlookupBasicTable& get_table(const PlookupBasicTableId id); - PlookupMultiTable& create_table(const PlookupMultiTableId id); - - std::array, 3> read_sequence_from_multi_table( - const PlookupMultiTableId& id, - const PlookupReadData& read_values, - const uint32_t key_a_index, - std::optional key_b_index = std::nullopt); - /** - * Generalized Permutation Methods - **/ - std::vector decompose_into_default_range(const uint32_t variable_index, - const size_t num_bits, - std::string const& msg = "decompose_into_default_range"); - std::vector decompose_into_default_range_better_for_oddlimbnum( - const uint32_t variable_index, - const size_t num_bits, - std::string const& msg = "decompose_into_default_range_better_for_oddlimbnum"); - void create_dummy_constraints(const std::vector& variable_index); - void create_sort_constraint(const std::vector& variable_index); - void create_sort_constraint_with_edges(const std::vector& variable_index, - const barretenberg::fr&, - const barretenberg::fr&); - void assign_tag(const uint32_t variable_index, const uint32_t tag) - { - ASSERT(tag <= current_tag); - ASSERT(variable_tags[real_variable_index[variable_index]] == DUMMY_TAG); - variable_tags[real_variable_index[variable_index]] = tag; - } - - uint32_t create_tag(const uint32_t tag_index, const uint32_t tau_index) - { - tau.insert({ tag_index, tau_index }); - current_tag++; - return current_tag; - } - uint32_t get_new_tag() - { - current_tag++; - return current_tag; - } - - RangeList create_range_list(const uint64_t target_range); - void create_new_range_constraint(const uint32_t variable_index, const uint64_t target_range); - void process_range_list(const RangeList& list); - void process_range_lists(); - - /** - * Member Variables - **/ - uint32_t zero_idx = 0; - - // This variable controls the amount with which the lookup table and witness values need to be shifted - // above to make room for adding randomness into the permutation and witness polynomials in plookup widget. - // This must be (num_roots_cut_out_of_the_vanishing_polynomial - 1), since the variable num_roots_cut_out_of_ - // vanishing_polynomial cannot be trivially fetched here, I am directly setting this to 4 - 1 = 3. - static constexpr size_t s_randomness = 3; - - // these are variables that we have used a gate on, to enforce that they are equal to a defined value - std::map constant_variables; - - std::vector lookup_tables; - std::vector lookup_multi_tables; - std::map range_lists; - - /** - * Program Manifests - **/ - static transcript::Manifest create_manifest(const size_t num_public_inputs) - { - // add public inputs.... - constexpr size_t g1_size = 64; - constexpr size_t fr_size = 32; - const size_t public_input_size = fr_size * num_public_inputs; - const transcript::Manifest output = transcript::Manifest( - { transcript::Manifest::RoundManifest( - { { "circuit_size", 4, true }, { "public_input_size", 4, true } }, "init", 1), - transcript::Manifest::RoundManifest({ { "public_inputs", public_input_size, false }, - { "W_1", g1_size, false }, - { "W_2", g1_size, false }, - { "W_3", g1_size, false }, - { "W_4", g1_size, false } }, - "eta", - 1), - transcript::Manifest::RoundManifest({ { "S", g1_size, false } }, "beta", 2), - transcript::Manifest::RoundManifest( - { { "Z_PERM", g1_size, false }, { "Z_LOOKUP", g1_size, false } }, "alpha", 1), - transcript::Manifest::RoundManifest({ { "T_1", g1_size, false }, - { "T_2", g1_size, false }, - { "T_3", g1_size, false }, - { "T_4", g1_size, false } }, - "z", - 1), - transcript::Manifest::RoundManifest( - { - { "w_1", fr_size, false, 0 }, - { "w_2", fr_size, false, 1 }, - { "w_3", fr_size, false, 2 }, - { "w_4", fr_size, false, 3 }, - { "sigma_1", fr_size, false, 4 }, - { "sigma_2", fr_size, false, 5 }, - { "sigma_3", fr_size, false, 6 }, - { "q_arith", fr_size, false, 7 }, - { "q_ecc_1", fr_size, false, 8 }, - { "q_2", fr_size, false, 9 }, - { "q_3", fr_size, false, 10 }, - { "q_4", fr_size, false, 11 }, - { "q_5", fr_size, false, 12 }, - { "q_m", fr_size, false, 13 }, - { "q_c", fr_size, false, 14 }, - { "table_value_1", fr_size, false, 15 }, - { "table_value_2", fr_size, false, 16 }, - { "table_value_3", fr_size, false, 17 }, - { "table_value_4", fr_size, false, 18 }, - { "table_index", fr_size, false, 19 }, - { "table_type", fr_size, false, 20 }, - { "s", fr_size, false, 21 }, - { "z_lookup", fr_size, false, 22 }, - { "id_1", fr_size, false, 24 }, - { "id_2", fr_size, false, 25 }, - { "id_3", fr_size, false, 26 }, - { "id_4", fr_size, false, 27 }, - { "z_perm_omega", fr_size, false, -1 }, - { "w_1_omega", fr_size, false, 0 }, - { "w_2_omega", fr_size, false, 1 }, - { "w_3_omega", fr_size, false, 2 }, - { "w_4_omega", fr_size, false, 3 }, - { "table_value_1_omega", fr_size, false, 4 }, - { "table_value_2_omega", fr_size, false, 5 }, - { "table_value_3_omega", fr_size, false, 6 }, - { "table_value_4_omega", fr_size, false, 7 }, - { "s_omega", fr_size, false, 8 }, - { "z_lookup_omega", fr_size, false, 9 }, - }, - "nu", - 28, - true), - transcript::Manifest::RoundManifest( - { { "PI_Z", g1_size, false }, { "PI_Z_OMEGA", g1_size, false } }, "separator", 1) }); - return output; - } - - static transcript::Manifest create_unrolled_manifest(const size_t num_public_inputs) - { - // add public inputs.... - constexpr size_t g1_size = 64; - constexpr size_t fr_size = 32; - const size_t public_input_size = fr_size * num_public_inputs; - const transcript::Manifest output = transcript::Manifest( - { transcript::Manifest::RoundManifest( - { { "circuit_size", 4, true }, { "public_input_size", 4, true } }, "init", 1), - transcript::Manifest::RoundManifest({ { "public_inputs", public_input_size, false }, - { "W_1", g1_size, false }, - { "W_2", g1_size, false }, - { "W_3", g1_size, false }, - { "W_4", g1_size, false } }, - "eta", - 1), - transcript::Manifest::RoundManifest({ { "S", g1_size, false } }, "beta", 2), - transcript::Manifest::RoundManifest( - { { "Z_PERM", g1_size, false }, { "Z_LOOKUP", g1_size, false } }, "alpha", 1), - transcript::Manifest::RoundManifest({ { "T_1", g1_size, false }, - { "T_2", g1_size, false }, - { "T_3", g1_size, false }, - { "T_4", g1_size, false } }, - "z", - 1), - transcript::Manifest::RoundManifest( - { - { "t", fr_size, true, -1 }, - { "w_1", fr_size, false, 0 }, - { "w_2", fr_size, false, 1 }, - { "w_3", fr_size, false, 2 }, - { "w_4", fr_size, false, 3 }, - { "sigma_1", fr_size, false, 4 }, - { "sigma_2", fr_size, false, 5 }, - { "sigma_3", fr_size, false, 6 }, - { "sigma_4", fr_size, false, 7 }, - { "q_1", fr_size, false, 8 }, - { "q_2", fr_size, false, 9 }, - { "q_3", fr_size, false, 10 }, - { "q_4", fr_size, false, 11 }, - { "q_5", fr_size, false, 12 }, - { "q_m", fr_size, false, 13 }, - { "q_c", fr_size, false, 14 }, - { "q_arith", fr_size, false, 15 }, - { "q_logic", fr_size, false, 16 }, - { "q_range", fr_size, false, 17 }, - { "q_sort", fr_size, false, 18 }, - { "q_ecc_1", fr_size, false, 19 }, - { "q_elliptic", fr_size, false, 20 }, - { "table_index", fr_size, false, 21 }, - { "table_type", fr_size, false, 22 }, - { "s", fr_size, false, 23 }, - { "z_lookup", fr_size, false, 24 }, - { "table_value_1", fr_size, false, 25 }, - { "table_value_2", fr_size, false, 26 }, - { "table_value_3", fr_size, false, 27 }, - { "table_value_4", fr_size, false, 28 }, - { "z_perm", fr_size, false, 29 }, - { "id_1", fr_size, false, 30 }, - { "id_2", fr_size, false, 31 }, - { "id_3", fr_size, false, 32 }, - { "id_4", fr_size, false, 33 }, - { "z_perm_omega", fr_size, false, -1 }, - { "w_1_omega", fr_size, false, 0 }, - { "w_2_omega", fr_size, false, 1 }, - { "w_3_omega", fr_size, false, 2 }, - { "w_4_omega", fr_size, false, 3 }, - { "s_omega", fr_size, false, 4 }, - { "z_lookup_omega", fr_size, false, 5 }, - { "table_value_1_omega", fr_size, false, 6 }, - { "table_value_2_omega", fr_size, false, 7 }, - { "table_value_3_omega", fr_size, false, 8 }, - { "table_value_4_omega", fr_size, false, 9 }, - }, - "nu", - 34, - true), - transcript::Manifest::RoundManifest( - { { "PI_Z", g1_size, false }, { "PI_Z_OMEGA", g1_size, false } }, "separator", 3) }); - return output; - } -}; -} // namespace waffle diff --git a/cpp/src/aztec/plonk/composer/plookup_tables/aes128.hpp b/cpp/src/aztec/plonk/composer/plookup_tables/aes128.hpp index 22a863958f..5345991ec5 100644 --- a/cpp/src/aztec/plonk/composer/plookup_tables/aes128.hpp +++ b/cpp/src/aztec/plonk/composer/plookup_tables/aes128.hpp @@ -8,8 +8,7 @@ #include "types.hpp" #include "sparse.hpp" -namespace waffle { - +namespace plookup { namespace aes128_tables { static constexpr uint64_t AES_BASE = 9; static constexpr uint64_t aes_normalization_table[AES_BASE]{ @@ -22,9 +21,9 @@ inline std::array get_aes_sparse_values_from_key(const std: return { barretenberg::fr(sparse), barretenberg::fr(0) }; } -inline PlookupBasicTable generate_aes_sparse_table(PlookupBasicTableId id, const size_t table_index) +inline BasicTable generate_aes_sparse_table(BasicTableId id, const size_t table_index) { - PlookupBasicTable table; + BasicTable table; table.id = id; table.table_index = table_index; table.size = 256; @@ -50,9 +49,9 @@ inline std::array get_aes_sparse_normalization_values_from_ return { barretenberg::fr(numeric::map_into_sparse_form(byte)), barretenberg::fr(0) }; } -inline PlookupBasicTable generate_aes_sparse_normalization_table(PlookupBasicTableId id, const size_t table_index) +inline BasicTable generate_aes_sparse_normalization_table(BasicTableId id, const size_t table_index) { - PlookupBasicTable table; + BasicTable table; table.id = id; table.table_index = table_index; for (uint64_t i = 0; i < AES_BASE; ++i) { @@ -86,7 +85,7 @@ inline PlookupBasicTable generate_aes_sparse_normalization_table(PlookupBasicTab return table; } -inline PlookupMultiTable get_aes_normalization_table(const PlookupMultiTableId id = AES_NORMALIZE) +inline MultiTable get_aes_normalization_table(const MultiTableId id = AES_NORMALIZE) { const size_t num_entries = 2; std::vector column_1_coefficients; @@ -99,7 +98,7 @@ inline PlookupMultiTable get_aes_normalization_table(const PlookupMultiTableId i column_3_coefficients.emplace_back(0); } - PlookupMultiTable table(column_1_coefficients, column_2_coefficients, column_3_coefficients); + MultiTable table(column_1_coefficients, column_2_coefficients, column_3_coefficients); table.id = id; for (size_t i = 0; i < num_entries; ++i) { @@ -110,11 +109,11 @@ inline PlookupMultiTable get_aes_normalization_table(const PlookupMultiTableId i return table; } -inline PlookupMultiTable get_aes_input_table(const PlookupMultiTableId id = AES_INPUT) +inline MultiTable get_aes_input_table(const MultiTableId id = AES_INPUT) { const size_t num_entries = 16; - PlookupMultiTable table(256, 0, 0, num_entries); + MultiTable table(256, 0, 0, num_entries); table.id = id; for (size_t i = 0; i < num_entries; ++i) { @@ -134,9 +133,9 @@ inline std::array get_aes_sbox_values_from_key(const std::a barretenberg::fr(numeric::map_into_sparse_form((uint8_t)(sbox_value ^ swizzled))) }; } -inline PlookupBasicTable generate_aes_sbox_table(PlookupBasicTableId id, const size_t table_index) +inline BasicTable generate_aes_sbox_table(BasicTableId id, const size_t table_index) { - PlookupBasicTable table; + BasicTable table; table.id = id; table.table_index = table_index; table.size = 256; @@ -160,11 +159,11 @@ inline PlookupBasicTable generate_aes_sbox_table(PlookupBasicTableId id, const s return table; } -inline PlookupMultiTable get_aes_sbox_table(const PlookupMultiTableId id = AES_SBOX) +inline MultiTable get_aes_sbox_table(const MultiTableId id = AES_SBOX) { const size_t num_entries = 1; - PlookupMultiTable table(0, 0, 0, 1); + MultiTable table(0, 0, 0, 1); table.id = id; for (size_t i = 0; i < num_entries; ++i) { @@ -175,4 +174,4 @@ inline PlookupMultiTable get_aes_sbox_table(const PlookupMultiTableId id = AES_S return table; } } // namespace aes128_tables -} // namespace waffle \ No newline at end of file +} // namespace plookup \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/plookup_tables/blake2s.hpp b/cpp/src/aztec/plonk/composer/plookup_tables/blake2s.hpp new file mode 100644 index 0000000000..69062fb032 --- /dev/null +++ b/cpp/src/aztec/plonk/composer/plookup_tables/blake2s.hpp @@ -0,0 +1,219 @@ +#pragma once + +#include + +#include "types.hpp" +#include "sparse.hpp" + +namespace plookup { +namespace blake2s_tables { + +static constexpr size_t BITS_IN_LAST_SLICE = 5UL; +static constexpr size_t SIZE_OF_LAST_SLICE = (1UL << BITS_IN_LAST_SLICE); + +/** + * This functions performs the operation ROTR^{k}(a ^ b) when filter is false and + * ROTR^{k}((a % 4) ^ (a % 4)) when filter is true. In other words, (filter = true) implies + * that the XOR operation works only on the two least significant bits. + */ +template +inline std::array get_xor_rotate_values_from_key(const std::array key) +{ + uint64_t filtered_key0 = filter ? key[0] & 3ULL : key[0]; + uint64_t filtered_key1 = filter ? key[1] & 3ULL : key[1]; + return { uint256_t(numeric::rotate32(uint32_t(filtered_key0) ^ uint32_t(filtered_key1), + uint32_t(num_rotated_output_bits))), + 0ULL }; +} + +/** + * Generates a basic 32-bit (XOR + ROTR) lookup table. + */ +template +inline BasicTable generate_xor_rotate_table(BasicTableId id, const size_t table_index) +{ + const uint64_t base = 1UL << bits_per_slice; + BasicTable table; + table.id = id; + table.table_index = table_index; + table.size = base * base; + table.use_twin_keys = true; + + for (uint64_t i = 0; i < base; ++i) { + for (uint64_t j = 0; j < base; ++j) { + table.column_1.emplace_back(i); + table.column_2.emplace_back(j); + uint64_t i_copy = i; + uint64_t j_copy = j; + if (filter) { + i_copy &= 3ULL; + j_copy &= 3ULL; + } + table.column_3.emplace_back( + uint256_t(numeric::rotate32(uint32_t(i_copy) ^ uint32_t(j_copy), uint32_t(num_rotated_output_bits)))); + } + } + + table.get_values_from_key = &get_xor_rotate_values_from_key; + + table.column_1_step_size = base; + table.column_2_step_size = base; + table.column_3_step_size = base; + + return table; +} + +/** + * Generates a multi-lookup-table with 5 slices for 32-bit XOR operation (a ^ b). + * + * Details: + * + * The following table summarizes the shifts required for each slice for different operations. + * We need to ensure that the coefficient of s0 always is 1, so we need adjust other coefficients + * accordingly. For example, the coefficient of slice s4 for ROTR_16 should be set to + * (2^8 / 2^{16}) = 2^{-8}. + * + * ----------------------------------------------- + * | Slice | ROTR_16 | ROTR_12 | ROTR_8 | ROTR_7 | + * |-------|---------|---------|--------|--------| + * | s0 | 16 | 20 | 24 | 25 | + * | s1 | 22 | 26 | 0 | 0 | + * | s2 | 0 | 0 | 4 | 5 | + * | s3 | 2 | 6 | 10 | 11 | + * | s4 | 8 | 12 | 16 | 17 | + * | s5 | 14 | 18 | 22 | 23 | + * ----------------------------------------------- + * + * We don't need to have a separate table for ROTR_12 as its output can be derived from an XOR table. + * Thus, we have a blake2s_xor_table function below. + */ +inline MultiTable get_blake2s_xor_table(const MultiTableId id = BLAKE_XOR) +{ + const size_t num_entries = (32 + 2) / 6 + 1; + const uint64_t base = 1 << 6; + MultiTable table(base, base, base, num_entries); + + table.id = id; + for (size_t i = 0; i < num_entries - 1; ++i) { + table.slice_sizes.emplace_back(base); + table.lookup_ids.emplace_back(BLAKE_XOR_ROTATE0); + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key<6, 0>); + } + + table.slice_sizes.emplace_back(SIZE_OF_LAST_SLICE); + table.lookup_ids.emplace_back(BLAKE_XOR_ROTATE0_SLICE5_MOD4); + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key); + + return table; +} + +/** + * Generates a multi-lookup-table with 5 slices for 32-bit operation ROTR^{16}(a ^ b). + */ +inline MultiTable get_blake2s_xor_rotate_16_table(const MultiTableId id = BLAKE_XOR_ROTATE_16) +{ + const uint64_t base = 1 << 6; + constexpr barretenberg::fr coefficient_16 = barretenberg::fr(1) / barretenberg::fr(1 << 16); + + std::vector column_1_coefficients{ barretenberg::fr(1), barretenberg::fr(1 << 6), + barretenberg::fr(1 << 12), barretenberg::fr(1 << 18), + barretenberg::fr(1 << 24), barretenberg::fr(1 << 30) }; + + std::vector column_3_coefficients{ barretenberg::fr(1), + barretenberg::fr(1 << 6), + coefficient_16, + coefficient_16 * barretenberg::fr(1 << 2), + coefficient_16 * barretenberg::fr(1 << 8), + coefficient_16 * barretenberg::fr(1 << 14) }; + + MultiTable table(column_1_coefficients, column_1_coefficients, column_3_coefficients); + + table.id = id; + table.slice_sizes = { base, base, base, base, base, SIZE_OF_LAST_SLICE }; + table.lookup_ids = { BLAKE_XOR_ROTATE0, BLAKE_XOR_ROTATE0, BLAKE_XOR_ROTATE4, + BLAKE_XOR_ROTATE0, BLAKE_XOR_ROTATE0, BLAKE_XOR_ROTATE0_SLICE5_MOD4 }; + + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key<6, 0>); + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key<6, 0>); + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key<6, 4>); + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key<6, 0>); + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key<6, 0>); + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key); + + return table; +} + +/** + * Generates a multi-lookup-table with 5 slices for 32-bit operation ROTR^{8}(a ^ b). + */ +inline MultiTable get_blake2s_xor_rotate_8_table(const MultiTableId id = BLAKE_XOR_ROTATE_8) +{ + const uint64_t base = 1 << 6; + constexpr barretenberg::fr coefficient_24 = barretenberg::fr(1) / barretenberg::fr(1 << 24); + + std::vector column_1_coefficients{ barretenberg::fr(1), barretenberg::fr(1 << 6), + barretenberg::fr(1 << 12), barretenberg::fr(1 << 18), + barretenberg::fr(1 << 24), barretenberg::fr(1 << 30) }; + + std::vector column_3_coefficients{ barretenberg::fr(1), + coefficient_24, + coefficient_24 * barretenberg::fr(1 << 4), + coefficient_24 * barretenberg::fr(1 << (4 + 6)), + coefficient_24 * barretenberg::fr(1 << (4 + 12)), + coefficient_24 * barretenberg::fr(1 << (4 + 18)) }; + + MultiTable table(column_1_coefficients, column_1_coefficients, column_3_coefficients); + + table.id = id; + table.slice_sizes = { base, base, base, base, base, SIZE_OF_LAST_SLICE }; + table.lookup_ids = { BLAKE_XOR_ROTATE0, BLAKE_XOR_ROTATE2, BLAKE_XOR_ROTATE0, + BLAKE_XOR_ROTATE0, BLAKE_XOR_ROTATE0, BLAKE_XOR_ROTATE0_SLICE5_MOD4 }; + + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key<6, 0>); + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key<6, 2>); + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key<6, 0>); + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key<6, 0>); + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key<6, 0>); + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key); + + return table; +} + +/** + * Generates a multi-lookup-table with 5 slices for 32-bit operation ROTR^{7}(a ^ b). + */ +inline MultiTable get_blake2s_xor_rotate_7_table(const MultiTableId id = BLAKE_XOR_ROTATE_7) +{ + const uint64_t base = 1 << 6; + constexpr barretenberg::fr coefficient_25 = barretenberg::fr(1) / barretenberg::fr(1 << 25); + + std::vector column_1_coefficients{ barretenberg::fr(1), barretenberg::fr(1 << 6), + barretenberg::fr(1 << 12), barretenberg::fr(1 << 18), + barretenberg::fr(1 << 24), barretenberg::fr(1 << 30) }; + + std::vector column_3_coefficients{ barretenberg::fr(1), + coefficient_25, + coefficient_25 * barretenberg::fr(1 << 5), + coefficient_25 * barretenberg::fr(1 << (5 + 6)), + coefficient_25 * barretenberg::fr(1 << (5 + 12)), + coefficient_25 * barretenberg::fr(1 << (5 + 18)) }; + + MultiTable table(column_1_coefficients, column_1_coefficients, column_3_coefficients); + + table.id = id; + table.slice_sizes = { base, base, base, base, base, SIZE_OF_LAST_SLICE }; + table.lookup_ids = { BLAKE_XOR_ROTATE0, BLAKE_XOR_ROTATE1, BLAKE_XOR_ROTATE0, + BLAKE_XOR_ROTATE0, BLAKE_XOR_ROTATE0, BLAKE_XOR_ROTATE0_SLICE5_MOD4 }; + + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key<6, 0>); + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key<6, 1>); + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key<6, 0>); + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key<6, 0>); + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key<6, 0>); + table.get_table_values.emplace_back(&get_xor_rotate_values_from_key); + + return table; +} + +} // namespace blake2s_tables +} // namespace plookup diff --git a/cpp/src/aztec/plonk/composer/plookup_tables/non_native_group_generator.cpp b/cpp/src/aztec/plonk/composer/plookup_tables/non_native_group_generator.cpp new file mode 100644 index 0000000000..5a70295dea --- /dev/null +++ b/cpp/src/aztec/plonk/composer/plookup_tables/non_native_group_generator.cpp @@ -0,0 +1,498 @@ +#include "non_native_group_generator.hpp" + +namespace plookup { +namespace ecc_generator_tables { + +/** + * Init 8-bit generator lookup tables + * The 8-bit wNAF is structured so that entries are in the range [0, ..., 255] + * + * The actual scalar value = (wNAF * 2) - 255 + * + * scalar values are from the values [-255, -253, ..., -3, -1, 1, 3, ..., 253, 255] + **/ +template void ecc_generator_table::init_generator_tables() +{ + if (init) { + return; + } + element base_point = G1::one; + + auto d2 = base_point.dbl(); + std::array point_table; + point_table[128] = base_point; + for (size_t i = 1; i < 128; ++i) { + point_table[i + 128] = point_table[i + 127] + d2; + } + for (size_t i = 0; i < 128; ++i) { + point_table[127 - i] = -point_table[128 + i]; + } + element::batch_normalize(&point_table[0], 256); + + auto beta = G1::Fq::cube_root_of_unity(); + for (size_t i = 0; i < 256; ++i) { + uint256_t endo_x = static_cast(point_table[i].x * beta); + uint256_t x = static_cast(point_table[i].x); + uint256_t y = static_cast(point_table[i].y); + + const uint256_t SHIFT = uint256_t(1) << 68; + const uint256_t MASK = SHIFT - 1; + uint256_t x0 = x & MASK; + x = x >> 68; + uint256_t x1 = x & MASK; + x = x >> 68; + uint256_t x2 = x & MASK; + x = x >> 68; + uint256_t x3 = x & MASK; + + uint256_t endox0 = endo_x & MASK; + endo_x = endo_x >> 68; + uint256_t endox1 = endo_x & MASK; + endo_x = endo_x >> 68; + uint256_t endox2 = endo_x & MASK; + endo_x = endo_x >> 68; + uint256_t endox3 = endo_x & MASK; + + uint256_t y0 = y & MASK; + y = y >> 68; + uint256_t y1 = y & MASK; + y = y >> 68; + uint256_t y2 = y & MASK; + y = y >> 68; + uint256_t y3 = y & MASK; + ecc_generator_table::generator_xlo_table[i] = std::make_pair(x0, x1); + ecc_generator_table::generator_xhi_table[i] = std::make_pair(x2, x3); + ecc_generator_table::generator_endo_xlo_table[i] = + std::make_pair(endox0, endox1); + ecc_generator_table::generator_endo_xhi_table[i] = + std::make_pair(endox2, endox3); + ecc_generator_table::generator_ylo_table[i] = std::make_pair(y0, y1); + ecc_generator_table::generator_yhi_table[i] = std::make_pair(y2, y3); + ecc_generator_table::generator_xyprime_table[i] = std::make_pair( + barretenberg::fr(uint256_t(point_table[i].x)), barretenberg::fr(uint256_t(point_table[i].y))); + ecc_generator_table::generator_endo_xyprime_table[i] = std::make_pair( + barretenberg::fr(uint256_t(point_table[i].x * beta)), barretenberg::fr(uint256_t(point_table[i].y))); + } + init = true; +} + +// map 0 to 255 into 0 to 510 in steps of two +// actual naf value = (position * 2) - 255 +template size_t ecc_generator_table::convert_position_to_shifted_naf(const size_t position) +{ + return (position * 2); +} + +template size_t ecc_generator_table::convert_shifted_naf_to_position(const size_t shifted_naf) +{ + return shifted_naf / 2; +} + +/** + * Get 2 low 68-bit limbs of x-coordinate + **/ +template +std::array ecc_generator_table::get_xlo_values(const std::array key) +{ + init_generator_tables(); + const size_t index = static_cast(key[0]); + return { ecc_generator_table::generator_xlo_table[index].first, + ecc_generator_table::generator_xlo_table[index].second }; +} + +/** + * Get 2 high 68-bit limbs of x-coordinate + **/ +template +std::array ecc_generator_table::get_xhi_values(const std::array key) +{ + init_generator_tables(); + const size_t index = static_cast(key[0]); + return { ecc_generator_table::generator_xhi_table[index].first, + ecc_generator_table::generator_xhi_table[index].second }; +} + +/** + * Get 2 low 68-bit limbs of x-coordinate (for endomorphism point \lambda.[P]) + **/ +template +std::array ecc_generator_table::get_xlo_endo_values(const std::array key) +{ + init_generator_tables(); + const size_t index = static_cast(key[0]); + return { ecc_generator_table::generator_endo_xlo_table[index].first, + ecc_generator_table::generator_endo_xlo_table[index].second }; +} + +/** + * Get 2 high 68-bit limbs of x-coordinate (for endomorphism point \lambda.[1]) + **/ +template +std::array ecc_generator_table::get_xhi_endo_values(const std::array key) +{ + init_generator_tables(); + const size_t index = static_cast(key[0]); + return { ecc_generator_table::generator_endo_xhi_table[index].first, + ecc_generator_table::generator_endo_xhi_table[index].second }; +} + +/** + * Get 2 low 68-bit limbs of y-coordinate + **/ +template +std::array ecc_generator_table::get_ylo_values(const std::array key) +{ + init_generator_tables(); + const size_t index = static_cast(key[0]); + return { ecc_generator_table::generator_ylo_table[index].first, + ecc_generator_table::generator_ylo_table[index].second }; +} + +/** + * Get 2 high 68-bit limbs of y-coordinate + **/ +template +std::array ecc_generator_table::get_yhi_values(const std::array key) +{ + init_generator_tables(); + const size_t index = static_cast(key[0]); + return { ecc_generator_table::generator_yhi_table[index].first, + ecc_generator_table::generator_yhi_table[index].second }; +} + +/** + * Get the prime basis limbs for the x and y coordinates + **/ +template +std::array ecc_generator_table::get_xyprime_values(const std::array key) +{ + init_generator_tables(); + const size_t index = static_cast(key[0]); + return { ecc_generator_table::generator_xyprime_table[index].first, + ecc_generator_table::generator_xyprime_table[index].second }; +} + +/** + * Get the prime basis limbs for the x and y coordinates (endomorphism version for \lambda.[1]) + **/ +template +std::array ecc_generator_table::get_xyprime_endo_values(const std::array key) +{ + init_generator_tables(); + const size_t index = static_cast(key[0]); + return { ecc_generator_table::generator_endo_xyprime_table[index].first, + ecc_generator_table::generator_endo_xyprime_table[index].second }; +} + +template BasicTable ecc_generator_table::generate_xlo_table(BasicTableId id, const size_t table_index) +{ + BasicTable table; + table.id = id; + table.table_index = table_index; + table.size = 256; + table.use_twin_keys = false; + + for (size_t i = 0; i < table.size; ++i) { + table.column_1.emplace_back((i)); + table.column_2.emplace_back(ecc_generator_table::generator_xlo_table[i].first); + table.column_3.emplace_back(ecc_generator_table::generator_xlo_table[i].second); + } + + table.get_values_from_key = &get_xlo_values; + + table.column_1_step_size = 0; + table.column_2_step_size = 0; + table.column_3_step_size = 0; + + return table; +} + +template BasicTable ecc_generator_table::generate_xhi_table(BasicTableId id, const size_t table_index) +{ + BasicTable table; + table.id = id; + table.table_index = table_index; + table.size = 256; + table.use_twin_keys = false; + + for (size_t i = 0; i < table.size; ++i) { + table.column_1.emplace_back((i)); + table.column_2.emplace_back(ecc_generator_table::generator_xhi_table[i].first); + table.column_3.emplace_back(ecc_generator_table::generator_xhi_table[i].second); + } + + table.get_values_from_key = &get_xhi_values; + + table.column_1_step_size = 0; + table.column_2_step_size = 0; + table.column_3_step_size = 0; + + return table; +} + +template +BasicTable ecc_generator_table::generate_xlo_endo_table(BasicTableId id, const size_t table_index) +{ + BasicTable table; + table.id = id; + table.table_index = table_index; + table.size = 256; + table.use_twin_keys = false; + + for (size_t i = 0; i < table.size; ++i) { + table.column_1.emplace_back((i)); + table.column_2.emplace_back(ecc_generator_table::generator_endo_xlo_table[i].first); + table.column_3.emplace_back(ecc_generator_table::generator_endo_xlo_table[i].second); + } + + table.get_values_from_key = &get_xlo_endo_values; + + table.column_1_step_size = 0; + table.column_2_step_size = 0; + table.column_3_step_size = 0; + + return table; +} + +template +BasicTable ecc_generator_table::generate_xhi_endo_table(BasicTableId id, const size_t table_index) +{ + BasicTable table; + table.id = id; + table.table_index = table_index; + table.size = 256; + table.use_twin_keys = false; + + for (size_t i = 0; i < table.size; ++i) { + table.column_1.emplace_back((i)); + table.column_2.emplace_back(ecc_generator_table::generator_endo_xhi_table[i].first); + table.column_3.emplace_back(ecc_generator_table::generator_endo_xhi_table[i].second); + } + + table.get_values_from_key = &get_xhi_endo_values; + + table.column_1_step_size = 0; + table.column_2_step_size = 0; + table.column_3_step_size = 0; + + return table; +} + +template BasicTable ecc_generator_table::generate_ylo_table(BasicTableId id, const size_t table_index) +{ + BasicTable table; + table.id = id; + table.table_index = table_index; + table.size = 256; + table.use_twin_keys = false; + + for (size_t i = 0; i < table.size; ++i) { + table.column_1.emplace_back((i)); + table.column_2.emplace_back(ecc_generator_table::generator_ylo_table[i].first); + table.column_3.emplace_back(ecc_generator_table::generator_ylo_table[i].second); + } + + table.get_values_from_key = &get_ylo_values; + + table.column_1_step_size = 0; + table.column_2_step_size = 0; + table.column_3_step_size = 0; + + return table; +} + +template BasicTable ecc_generator_table::generate_yhi_table(BasicTableId id, const size_t table_index) +{ + BasicTable table; + table.id = id; + table.table_index = table_index; + table.size = 256; + table.use_twin_keys = false; + + for (size_t i = 0; i < table.size; ++i) { + table.column_1.emplace_back((i)); + table.column_2.emplace_back(ecc_generator_table::generator_yhi_table[i].first); + table.column_3.emplace_back(ecc_generator_table::generator_yhi_table[i].second); + } + + table.get_values_from_key = &get_yhi_values; + + table.column_1_step_size = 0; + table.column_2_step_size = 0; + table.column_3_step_size = 0; + + return table; +} + +template +BasicTable ecc_generator_table::generate_xyprime_table(BasicTableId id, const size_t table_index) +{ + BasicTable table; + table.id = id; + table.table_index = table_index; + table.size = 256; + table.use_twin_keys = false; + + for (size_t i = 0; i < table.size; ++i) { + table.column_1.emplace_back((i)); + table.column_2.emplace_back(ecc_generator_table::generator_xyprime_table[i].first); + table.column_3.emplace_back(ecc_generator_table::generator_xyprime_table[i].second); + } + + table.get_values_from_key = &get_xyprime_values; + + table.column_1_step_size = 0; + table.column_2_step_size = 0; + table.column_3_step_size = 0; + + return table; +} + +template +BasicTable ecc_generator_table::generate_xyprime_endo_table(BasicTableId id, const size_t table_index) +{ + BasicTable table; + table.id = id; + table.table_index = table_index; + table.size = 256; + table.use_twin_keys = false; + + for (size_t i = 0; i < table.size; ++i) { + table.column_1.emplace_back((i)); + table.column_2.emplace_back(ecc_generator_table::generator_endo_xyprime_table[i].first); + table.column_3.emplace_back(ecc_generator_table::generator_endo_xyprime_table[i].second); + } + + table.get_values_from_key = &get_xyprime_endo_values; + + table.column_1_step_size = 0; + table.column_2_step_size = 0; + table.column_3_step_size = 0; + + return table; +} + +template +MultiTable ecc_generator_table::get_xlo_table(const MultiTableId id, const BasicTableId basic_id) +{ + const size_t num_entries = 1; + MultiTable table(256, 0, 0, 1); + + table.id = id; + for (size_t i = 0; i < num_entries; ++i) { + table.slice_sizes.emplace_back(512); + table.lookup_ids.emplace_back(basic_id); + table.get_table_values.emplace_back(&get_xlo_values); + } + return table; +} + +template +MultiTable ecc_generator_table::get_xhi_table(const MultiTableId id, const BasicTableId basic_id) +{ + const size_t num_entries = 1; + MultiTable table(256, 0, 0, 1); + + table.id = id; + for (size_t i = 0; i < num_entries; ++i) { + table.slice_sizes.emplace_back(512); + table.lookup_ids.emplace_back(basic_id); + table.get_table_values.emplace_back(&get_xhi_values); + } + return table; +} + +template +MultiTable ecc_generator_table::get_xlo_endo_table(const MultiTableId id, const BasicTableId basic_id) +{ + const size_t num_entries = 1; + MultiTable table(256, 0, 0, 1); + + table.id = id; + for (size_t i = 0; i < num_entries; ++i) { + table.slice_sizes.emplace_back(512); + table.lookup_ids.emplace_back(basic_id); + table.get_table_values.emplace_back(&get_xlo_endo_values); + } + return table; +} + +template +MultiTable ecc_generator_table::get_xhi_endo_table(const MultiTableId id, const BasicTableId basic_id) +{ + const size_t num_entries = 1; + MultiTable table(256, 0, 0, 1); + + table.id = id; + for (size_t i = 0; i < num_entries; ++i) { + table.slice_sizes.emplace_back(512); + table.lookup_ids.emplace_back(basic_id); + table.get_table_values.emplace_back(&get_xhi_endo_values); + } + return table; +} + +template +MultiTable ecc_generator_table::get_ylo_table(const MultiTableId id, const BasicTableId basic_id) +{ + const size_t num_entries = 1; + MultiTable table(256, 0, 0, 1); + + table.id = id; + for (size_t i = 0; i < num_entries; ++i) { + table.slice_sizes.emplace_back(512); + table.lookup_ids.emplace_back(basic_id); + table.get_table_values.emplace_back(&get_ylo_values); + } + return table; +} + +template +MultiTable ecc_generator_table::get_yhi_table(const MultiTableId id, const BasicTableId basic_id) +{ + const size_t num_entries = 1; + MultiTable table(256, 0, 0, 1); + + table.id = id; + for (size_t i = 0; i < num_entries; ++i) { + table.slice_sizes.emplace_back(512); + table.lookup_ids.emplace_back(basic_id); + table.get_table_values.emplace_back(&get_yhi_values); + } + return table; +} + +template +MultiTable ecc_generator_table::get_xyprime_table(const MultiTableId id, const BasicTableId basic_id) +{ + const size_t num_entries = 1; + MultiTable table(256, 0, 0, 1); + + table.id = id; + for (size_t i = 0; i < num_entries; ++i) { + table.slice_sizes.emplace_back(512); + table.lookup_ids.emplace_back(basic_id); + table.get_table_values.emplace_back(&get_xyprime_values); + } + return table; +} + +template +MultiTable ecc_generator_table::get_xyprime_endo_table(const MultiTableId id, const BasicTableId basic_id) +{ + const size_t num_entries = 1; + MultiTable table(256, 0, 0, 1); + + table.id = id; + for (size_t i = 0; i < num_entries; ++i) { + table.slice_sizes.emplace_back(512); + table.lookup_ids.emplace_back(basic_id); + table.get_table_values.emplace_back(&get_xyprime_endo_values); + } + return table; +} +template class ecc_generator_table; +template class ecc_generator_table; + +} // namespace ecc_generator_tables +} // namespace plookup \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/plookup_tables/non_native_group_generator.hpp b/cpp/src/aztec/plonk/composer/plookup_tables/non_native_group_generator.hpp new file mode 100644 index 0000000000..3192a4c656 --- /dev/null +++ b/cpp/src/aztec/plonk/composer/plookup_tables/non_native_group_generator.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include "./types.hpp" +#include +#include +#include +#include + +namespace plookup { +namespace ecc_generator_tables { + +template class ecc_generator_table { + public: + typedef typename G1::element element; + /** + * Store arrays of precomputed 8-bit lookup tables for generator point coordinates (and their endomorphism + *equivalents) + **/ + inline static std::array, 256> generator_endo_xlo_table; + inline static std::array, 256> generator_endo_xhi_table; + inline static std::array, 256> generator_xlo_table; + inline static std::array, 256> generator_xhi_table; + inline static std::array, 256> generator_ylo_table; + inline static std::array, 256> generator_yhi_table; + inline static std::array, 256> generator_xyprime_table; + inline static std::array, 256> generator_endo_xyprime_table; + inline static bool init = false; + + static void init_generator_tables(); + + static size_t convert_position_to_shifted_naf(const size_t position); + static size_t convert_shifted_naf_to_position(const size_t shifted_naf); + static std::array get_xlo_endo_values(const std::array key); + static std::array get_xhi_endo_values(const std::array key); + static std::array get_xlo_values(const std::array key); + static std::array get_xhi_values(const std::array key); + static std::array get_ylo_values(const std::array key); + static std::array get_yhi_values(const std::array key); + static std::array get_xyprime_values(const std::array key); + static std::array get_xyprime_endo_values(const std::array key); + static BasicTable generate_xlo_table(BasicTableId id, const size_t table_index); + static BasicTable generate_xhi_table(BasicTableId id, const size_t table_index); + static BasicTable generate_xlo_endo_table(BasicTableId id, const size_t table_index); + static BasicTable generate_xhi_endo_table(BasicTableId id, const size_t table_index); + static BasicTable generate_ylo_table(BasicTableId id, const size_t table_index); + static BasicTable generate_yhi_table(BasicTableId id, const size_t table_index); + static BasicTable generate_xyprime_table(BasicTableId id, const size_t table_index); + static BasicTable generate_xyprime_endo_table(BasicTableId id, const size_t table_index); + static MultiTable get_xlo_table(const MultiTableId id, const BasicTableId basic_id); + static MultiTable get_xhi_table(const MultiTableId id, const BasicTableId basic_id); + static MultiTable get_xlo_endo_table(const MultiTableId id, const BasicTableId basic_id); + static MultiTable get_xhi_endo_table(const MultiTableId id, const BasicTableId basic_id); + static MultiTable get_ylo_table(const MultiTableId id, const BasicTableId basic_id); + static MultiTable get_yhi_table(const MultiTableId id, const BasicTableId basic_id); + static MultiTable get_xyprime_table(const MultiTableId id, const BasicTableId basic_id); + static MultiTable get_xyprime_endo_table(const MultiTableId id, const BasicTableId basic_id); +}; + +extern template class ecc_generator_table; +extern template class ecc_generator_table; + +} // namespace ecc_generator_tables +} // namespace plookup \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/plookup_tables/pedersen.hpp b/cpp/src/aztec/plonk/composer/plookup_tables/pedersen.hpp index 999fd5c4bb..26c7a74b1a 100644 --- a/cpp/src/aztec/plonk/composer/plookup_tables/pedersen.hpp +++ b/cpp/src/aztec/plonk/composer/plookup_tables/pedersen.hpp @@ -2,40 +2,49 @@ #include "./types.hpp" -#include +#include #include #include #include -namespace waffle { +namespace plookup { namespace pedersen_tables { +namespace basic { template -inline std::array get_sidon_pedersen_table_values(const std::array key) +inline std::array get_basic_pedersen_table_values(const std::array key) { - const auto& sidon_table = crypto::pedersen::sidon::get_table(generator_index); + const auto& basic_table = crypto::pedersen::lookup::get_table(generator_index); const size_t index = static_cast(key[0]); - return { sidon_table[index].x, sidon_table[index].y }; + return { basic_table[index].x, basic_table[index].y }; } -template -inline PlookupBasicTable generate_sidon_pedersen_table(PlookupBasicTableId id, const size_t table_index) +inline std::array get_pedersen_iv_table_values(const std::array key) +{ + const auto& iv_table = crypto::pedersen::lookup::get_iv_table(); + const size_t index = static_cast(key[0]); + return { iv_table[index].x, iv_table[index].y }; +} + +template +inline BasicTable generate_basic_pedersen_table(BasicTableId id, const size_t table_index) { - PlookupBasicTable table; + BasicTable table; table.id = id; table.table_index = table_index; - table.size = crypto::pedersen::sidon::PEDERSEN_TABLE_SIZE; + table.size = + is_small ? crypto::pedersen::lookup::PEDERSEN_SMALL_TABLE_SIZE : crypto::pedersen::lookup::PEDERSEN_TABLE_SIZE; table.use_twin_keys = false; - const auto& sidon_table = crypto::pedersen::sidon::get_table(generator_index); + const auto& basic_table = crypto::pedersen::lookup::get_table(generator_index); for (size_t i = 0; i < table.size; ++i) { table.column_1.emplace_back(i); - table.column_2.emplace_back(sidon_table[i].x); - table.column_3.emplace_back(sidon_table[i].y); + table.column_2.emplace_back(basic_table[i].x); + table.column_3.emplace_back(basic_table[i].y); } - table.get_values_from_key = &get_sidon_pedersen_table_values; + table.get_values_from_key = &get_basic_pedersen_table_values; table.column_1_step_size = table.size; table.column_2_step_size = 0; @@ -44,69 +53,141 @@ inline PlookupBasicTable generate_sidon_pedersen_table(PlookupBasicTableId id, c return table; } -inline PlookupMultiTable get_pedersen_left_table(const PlookupMultiTableId id = PEDERSEN_LEFT) +inline BasicTable generate_pedersen_iv_table(BasicTableId id) { - const size_t num_entries = - (256 + crypto::pedersen::sidon::BITS_PER_TABLE - 1) / crypto::pedersen::sidon::BITS_PER_TABLE; - PlookupMultiTable table(crypto::pedersen::sidon::PEDERSEN_TABLE_SIZE, 0, 0, num_entries); + BasicTable table; + table.id = id; + table.table_index = 0; + table.size = crypto::pedersen::lookup::PEDERSEN_IV_TABLE_SIZE; + table.use_twin_keys = false; + + const auto& iv_table = crypto::pedersen::lookup::get_iv_table(); + + for (size_t i = 0; i < table.size; ++i) { + table.column_1.emplace_back(i); + table.column_2.emplace_back(iv_table[i].x); + table.column_3.emplace_back(iv_table[i].y); + } + + table.get_values_from_key = &get_pedersen_iv_table_values; + + table.column_1_step_size = table.size; + table.column_2_step_size = 0; + table.column_3_step_size = 0; + + return table; +} + +inline MultiTable get_pedersen_iv_table(const MultiTableId id = PEDERSEN_IV) +{ + MultiTable table(crypto::pedersen::lookup::PEDERSEN_IV_TABLE_SIZE, 0, 0, 1); + table.id = id; + table.slice_sizes.emplace_back(crypto::pedersen::lookup::PEDERSEN_IV_TABLE_SIZE); + table.get_table_values.emplace_back(&get_pedersen_iv_table_values); + table.lookup_ids = { PEDERSEN_IV_BASE }; + + return table; +} + +inline MultiTable get_pedersen_left_lo_table(const MultiTableId id = PEDERSEN_LEFT_LO) +{ + const size_t num_entries = 126 / crypto::pedersen::lookup::BITS_PER_TABLE; + MultiTable table(crypto::pedersen::lookup::PEDERSEN_TABLE_SIZE, 0, 0, num_entries); table.id = id; for (size_t i = 0; i < num_entries; ++i) { - table.slice_sizes.emplace_back(crypto::pedersen::sidon::PEDERSEN_TABLE_SIZE); + table.slice_sizes.emplace_back(crypto::pedersen::lookup::PEDERSEN_TABLE_SIZE); } - table.get_table_values = { - &get_sidon_pedersen_table_values<0>, &get_sidon_pedersen_table_values<0>, &get_sidon_pedersen_table_values<0>, - &get_sidon_pedersen_table_values<1>, &get_sidon_pedersen_table_values<1>, &get_sidon_pedersen_table_values<1>, - &get_sidon_pedersen_table_values<2>, &get_sidon_pedersen_table_values<2>, &get_sidon_pedersen_table_values<2>, - &get_sidon_pedersen_table_values<3>, &get_sidon_pedersen_table_values<3>, &get_sidon_pedersen_table_values<3>, - &get_sidon_pedersen_table_values<4>, &get_sidon_pedersen_table_values<4>, &get_sidon_pedersen_table_values<4>, - &get_sidon_pedersen_table_values<5>, &get_sidon_pedersen_table_values<5>, &get_sidon_pedersen_table_values<5>, - &get_sidon_pedersen_table_values<6>, &get_sidon_pedersen_table_values<6>, &get_sidon_pedersen_table_values<6>, - &get_sidon_pedersen_table_values<7>, &get_sidon_pedersen_table_values<7>, &get_sidon_pedersen_table_values<7>, - &get_sidon_pedersen_table_values<8>, &get_sidon_pedersen_table_values<8>, - }; - - table.lookup_ids = { - PEDERSEN_0, PEDERSEN_0, PEDERSEN_0, PEDERSEN_1, PEDERSEN_1, PEDERSEN_1, PEDERSEN_2, PEDERSEN_2, PEDERSEN_2, - PEDERSEN_3, PEDERSEN_3, PEDERSEN_3, PEDERSEN_4, PEDERSEN_4, PEDERSEN_4, PEDERSEN_5, PEDERSEN_5, PEDERSEN_5, - PEDERSEN_6, PEDERSEN_6, PEDERSEN_6, PEDERSEN_7, PEDERSEN_7, PEDERSEN_7, PEDERSEN_8, PEDERSEN_8, - }; + table.get_table_values = { &get_basic_pedersen_table_values<0>, &get_basic_pedersen_table_values<0>, + &get_basic_pedersen_table_values<1>, &get_basic_pedersen_table_values<1>, + &get_basic_pedersen_table_values<2>, &get_basic_pedersen_table_values<2>, + &get_basic_pedersen_table_values<3>, &get_basic_pedersen_table_values<3>, + &get_basic_pedersen_table_values<4>, &get_basic_pedersen_table_values<4>, + &get_basic_pedersen_table_values<5>, &get_basic_pedersen_table_values<5>, + &get_basic_pedersen_table_values<6>, &get_basic_pedersen_table_values<6> }; + + table.lookup_ids = { PEDERSEN_0, PEDERSEN_0, PEDERSEN_1, PEDERSEN_1, PEDERSEN_2, PEDERSEN_2, PEDERSEN_3, + PEDERSEN_3, PEDERSEN_4, PEDERSEN_4, PEDERSEN_5, PEDERSEN_5, PEDERSEN_6, PEDERSEN_6 }; return table; } -inline PlookupMultiTable get_pedersen_right_table(const PlookupMultiTableId id = PEDERSEN_RIGHT) +inline MultiTable get_pedersen_left_hi_table(const MultiTableId id = PEDERSEN_LEFT_HI) { const size_t num_entries = - (256 + crypto::pedersen::sidon::BITS_PER_TABLE) / crypto::pedersen::sidon::BITS_PER_TABLE; - PlookupMultiTable table(crypto::pedersen::sidon::PEDERSEN_TABLE_SIZE, 0, 0, num_entries); + (128 + crypto::pedersen::lookup::BITS_PER_TABLE) / crypto::pedersen::lookup::BITS_PER_TABLE; + MultiTable table(crypto::pedersen::lookup::PEDERSEN_TABLE_SIZE, 0, 0, num_entries); + + table.id = id; + for (size_t i = 0; i < num_entries - 1; ++i) { + table.slice_sizes.emplace_back(crypto::pedersen::lookup::PEDERSEN_TABLE_SIZE); + } + table.slice_sizes.emplace_back(crypto::pedersen::lookup::PEDERSEN_SMALL_TABLE_SIZE); + + table.get_table_values = { &get_basic_pedersen_table_values<7>, &get_basic_pedersen_table_values<7>, + &get_basic_pedersen_table_values<8>, &get_basic_pedersen_table_values<8>, + &get_basic_pedersen_table_values<9>, &get_basic_pedersen_table_values<9>, + &get_basic_pedersen_table_values<10>, &get_basic_pedersen_table_values<10>, + &get_basic_pedersen_table_values<11>, &get_basic_pedersen_table_values<11>, + &get_basic_pedersen_table_values<12>, &get_basic_pedersen_table_values<12>, + &get_basic_pedersen_table_values<13>, &get_basic_pedersen_table_values<13>, + &get_basic_pedersen_table_values<14> }; + + table.lookup_ids = { PEDERSEN_7, PEDERSEN_7, PEDERSEN_8, PEDERSEN_8, PEDERSEN_9, + PEDERSEN_9, PEDERSEN_10, PEDERSEN_10, PEDERSEN_11, PEDERSEN_11, + PEDERSEN_12, PEDERSEN_12, PEDERSEN_13, PEDERSEN_13, PEDERSEN_14_SMALL }; + return table; +} + +inline MultiTable get_pedersen_right_lo_table(const MultiTableId id = PEDERSEN_RIGHT_LO) +{ + const size_t num_entries = 126 / crypto::pedersen::lookup::BITS_PER_TABLE; + MultiTable table(crypto::pedersen::lookup::PEDERSEN_TABLE_SIZE, 0, 0, num_entries); table.id = id; for (size_t i = 0; i < num_entries; ++i) { - table.slice_sizes.emplace_back(crypto::pedersen::sidon::PEDERSEN_TABLE_SIZE); + table.slice_sizes.emplace_back(crypto::pedersen::lookup::PEDERSEN_TABLE_SIZE); } - table.get_table_values = { - &get_sidon_pedersen_table_values<9>, &get_sidon_pedersen_table_values<9>, - &get_sidon_pedersen_table_values<9>, &get_sidon_pedersen_table_values<10>, - &get_sidon_pedersen_table_values<10>, &get_sidon_pedersen_table_values<10>, - &get_sidon_pedersen_table_values<11>, &get_sidon_pedersen_table_values<11>, - &get_sidon_pedersen_table_values<11>, &get_sidon_pedersen_table_values<12>, - &get_sidon_pedersen_table_values<12>, &get_sidon_pedersen_table_values<12>, - &get_sidon_pedersen_table_values<13>, &get_sidon_pedersen_table_values<13>, - &get_sidon_pedersen_table_values<13>, &get_sidon_pedersen_table_values<14>, - &get_sidon_pedersen_table_values<14>, &get_sidon_pedersen_table_values<14>, - &get_sidon_pedersen_table_values<15>, &get_sidon_pedersen_table_values<15>, - &get_sidon_pedersen_table_values<15>, &get_sidon_pedersen_table_values<16>, - &get_sidon_pedersen_table_values<16>, &get_sidon_pedersen_table_values<16>, - &get_sidon_pedersen_table_values<17>, &get_sidon_pedersen_table_values<17>, - }; - - table.lookup_ids = { PEDERSEN_9, PEDERSEN_9, PEDERSEN_9, PEDERSEN_10, PEDERSEN_10, PEDERSEN_10, PEDERSEN_11, - PEDERSEN_11, PEDERSEN_11, PEDERSEN_12, PEDERSEN_12, PEDERSEN_12, PEDERSEN_13, PEDERSEN_13, - PEDERSEN_13, PEDERSEN_14, PEDERSEN_14, PEDERSEN_14, PEDERSEN_15, PEDERSEN_15, PEDERSEN_15, - PEDERSEN_16, PEDERSEN_16, PEDERSEN_16, PEDERSEN_17, PEDERSEN_17 }; + table.get_table_values = { &get_basic_pedersen_table_values<15>, &get_basic_pedersen_table_values<15>, + &get_basic_pedersen_table_values<16>, &get_basic_pedersen_table_values<16>, + &get_basic_pedersen_table_values<17>, &get_basic_pedersen_table_values<17>, + &get_basic_pedersen_table_values<18>, &get_basic_pedersen_table_values<18>, + &get_basic_pedersen_table_values<19>, &get_basic_pedersen_table_values<19>, + &get_basic_pedersen_table_values<20>, &get_basic_pedersen_table_values<20>, + &get_basic_pedersen_table_values<21>, &get_basic_pedersen_table_values<21> }; + + table.lookup_ids = { PEDERSEN_15, PEDERSEN_15, PEDERSEN_16, PEDERSEN_16, PEDERSEN_17, PEDERSEN_17, PEDERSEN_18, + PEDERSEN_18, PEDERSEN_19, PEDERSEN_19, PEDERSEN_20, PEDERSEN_20, PEDERSEN_21, PEDERSEN_21 }; + return table; +} + +inline MultiTable get_pedersen_right_hi_table(const MultiTableId id = PEDERSEN_RIGHT_HI) +{ + const size_t num_entries = + (128 + crypto::pedersen::lookup::BITS_PER_TABLE) / crypto::pedersen::lookup::BITS_PER_TABLE; + MultiTable table(crypto::pedersen::lookup::PEDERSEN_TABLE_SIZE, 0, 0, num_entries); + + table.id = id; + for (size_t i = 0; i < num_entries - 1; ++i) { + table.slice_sizes.emplace_back(crypto::pedersen::lookup::PEDERSEN_TABLE_SIZE); + } + table.slice_sizes.emplace_back(crypto::pedersen::lookup::PEDERSEN_SMALL_TABLE_SIZE); + + table.get_table_values = { &get_basic_pedersen_table_values<22>, &get_basic_pedersen_table_values<22>, + &get_basic_pedersen_table_values<23>, &get_basic_pedersen_table_values<23>, + &get_basic_pedersen_table_values<24>, &get_basic_pedersen_table_values<24>, + &get_basic_pedersen_table_values<25>, &get_basic_pedersen_table_values<25>, + &get_basic_pedersen_table_values<26>, &get_basic_pedersen_table_values<26>, + &get_basic_pedersen_table_values<27>, &get_basic_pedersen_table_values<27>, + &get_basic_pedersen_table_values<28>, &get_basic_pedersen_table_values<28>, + &get_basic_pedersen_table_values<29> }; + + table.lookup_ids = { PEDERSEN_22, PEDERSEN_22, PEDERSEN_23, PEDERSEN_23, PEDERSEN_24, + PEDERSEN_24, PEDERSEN_25, PEDERSEN_25, PEDERSEN_26, PEDERSEN_26, + PEDERSEN_27, PEDERSEN_27, PEDERSEN_28, PEDERSEN_28, PEDERSEN_29_SMALL }; return table; } +} // namespace basic } // namespace pedersen_tables -} // namespace waffle \ No newline at end of file +} // namespace plookup \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/plookup_tables/plookup_tables.cpp b/cpp/src/aztec/plonk/composer/plookup_tables/plookup_tables.cpp index 8c362ef3d2..26cae5826a 100644 --- a/cpp/src/aztec/plonk/composer/plookup_tables/plookup_tables.cpp +++ b/cpp/src/aztec/plonk/composer/plookup_tables/plookup_tables.cpp @@ -1,42 +1,91 @@ #include "plookup_tables.hpp" -namespace waffle { namespace plookup { using namespace barretenberg; namespace { -static std::array MULTI_TABLES; +static std::array MULTI_TABLES; static bool inited = false; void init_multi_tables() { - MULTI_TABLES[PlookupMultiTableId::SHA256_CH_INPUT] = - sha256_tables::get_choose_input_table(PlookupMultiTableId::SHA256_CH_INPUT); - MULTI_TABLES[PlookupMultiTableId::SHA256_MAJ_INPUT] = - sha256_tables::get_majority_input_table(PlookupMultiTableId::SHA256_MAJ_INPUT); - MULTI_TABLES[PlookupMultiTableId::SHA256_WITNESS_INPUT] = - sha256_tables::get_witness_extension_input_table(PlookupMultiTableId::SHA256_WITNESS_INPUT); - MULTI_TABLES[PlookupMultiTableId::SHA256_CH_OUTPUT] = - sha256_tables::get_choose_output_table(PlookupMultiTableId::SHA256_CH_OUTPUT); - MULTI_TABLES[PlookupMultiTableId::SHA256_MAJ_OUTPUT] = - sha256_tables::get_majority_output_table(PlookupMultiTableId::SHA256_MAJ_OUTPUT); - MULTI_TABLES[PlookupMultiTableId::SHA256_WITNESS_OUTPUT] = - sha256_tables::get_witness_extension_output_table(PlookupMultiTableId::SHA256_WITNESS_OUTPUT); - MULTI_TABLES[PlookupMultiTableId::AES_NORMALIZE] = - aes128_tables::get_aes_normalization_table(PlookupMultiTableId::AES_NORMALIZE); - MULTI_TABLES[PlookupMultiTableId::AES_INPUT] = aes128_tables::get_aes_input_table(PlookupMultiTableId::AES_INPUT); - MULTI_TABLES[PlookupMultiTableId::AES_SBOX] = aes128_tables::get_aes_sbox_table(PlookupMultiTableId::AES_SBOX); - MULTI_TABLES[PlookupMultiTableId::PEDERSEN_LEFT] = - pedersen_tables::get_pedersen_left_table(PlookupMultiTableId::PEDERSEN_LEFT); - MULTI_TABLES[PlookupMultiTableId::PEDERSEN_RIGHT] = - pedersen_tables::get_pedersen_right_table(PlookupMultiTableId::PEDERSEN_RIGHT); - MULTI_TABLES[PlookupMultiTableId::UINT32_XOR] = uint_tables::get_uint32_xor_table(PlookupMultiTableId::UINT32_XOR); - MULTI_TABLES[PlookupMultiTableId::UINT32_AND] = uint_tables::get_uint32_and_table(PlookupMultiTableId::UINT32_AND); + MULTI_TABLES[MultiTableId::SHA256_CH_INPUT] = sha256_tables::get_choose_input_table(MultiTableId::SHA256_CH_INPUT); + MULTI_TABLES[MultiTableId::SHA256_MAJ_INPUT] = + sha256_tables::get_majority_input_table(MultiTableId::SHA256_MAJ_INPUT); + MULTI_TABLES[MultiTableId::SHA256_WITNESS_INPUT] = + sha256_tables::get_witness_extension_input_table(MultiTableId::SHA256_WITNESS_INPUT); + MULTI_TABLES[MultiTableId::SHA256_CH_OUTPUT] = + sha256_tables::get_choose_output_table(MultiTableId::SHA256_CH_OUTPUT); + MULTI_TABLES[MultiTableId::SHA256_MAJ_OUTPUT] = + sha256_tables::get_majority_output_table(MultiTableId::SHA256_MAJ_OUTPUT); + MULTI_TABLES[MultiTableId::SHA256_WITNESS_OUTPUT] = + sha256_tables::get_witness_extension_output_table(MultiTableId::SHA256_WITNESS_OUTPUT); + MULTI_TABLES[MultiTableId::AES_NORMALIZE] = aes128_tables::get_aes_normalization_table(MultiTableId::AES_NORMALIZE); + MULTI_TABLES[MultiTableId::AES_INPUT] = aes128_tables::get_aes_input_table(MultiTableId::AES_INPUT); + MULTI_TABLES[MultiTableId::AES_SBOX] = aes128_tables::get_aes_sbox_table(MultiTableId::AES_SBOX); + MULTI_TABLES[MultiTableId::PEDERSEN_LEFT_HI] = + pedersen_tables::basic::get_pedersen_left_hi_table(MultiTableId::PEDERSEN_LEFT_HI); + MULTI_TABLES[MultiTableId::PEDERSEN_LEFT_LO] = + pedersen_tables::basic::get_pedersen_left_lo_table(MultiTableId::PEDERSEN_LEFT_LO); + MULTI_TABLES[MultiTableId::PEDERSEN_RIGHT_HI] = + pedersen_tables::basic::get_pedersen_right_hi_table(MultiTableId::PEDERSEN_RIGHT_HI); + MULTI_TABLES[MultiTableId::PEDERSEN_RIGHT_LO] = + pedersen_tables::basic::get_pedersen_right_lo_table(MultiTableId::PEDERSEN_RIGHT_LO); + MULTI_TABLES[MultiTableId::PEDERSEN_IV] = pedersen_tables::basic::get_pedersen_iv_table(MultiTableId::PEDERSEN_IV); + MULTI_TABLES[MultiTableId::UINT32_XOR] = uint_tables::get_uint32_xor_table(MultiTableId::UINT32_XOR); + MULTI_TABLES[MultiTableId::UINT32_AND] = uint_tables::get_uint32_and_table(MultiTableId::UINT32_AND); + MULTI_TABLES[MultiTableId::BN254_XLO] = ecc_generator_tables::ecc_generator_table::get_xlo_table( + MultiTableId::BN254_XLO, BasicTableId::BN254_XLO_BASIC); + MULTI_TABLES[MultiTableId::BN254_XHI] = ecc_generator_tables::ecc_generator_table::get_xhi_table( + MultiTableId::BN254_XHI, BasicTableId::BN254_XHI_BASIC); + MULTI_TABLES[MultiTableId::BN254_YLO] = ecc_generator_tables::ecc_generator_table::get_ylo_table( + MultiTableId::BN254_YLO, BasicTableId::BN254_YLO_BASIC); + MULTI_TABLES[MultiTableId::BN254_YHI] = ecc_generator_tables::ecc_generator_table::get_yhi_table( + MultiTableId::BN254_YHI, BasicTableId::BN254_YHI_BASIC); + MULTI_TABLES[MultiTableId::BN254_XYPRIME] = + ecc_generator_tables::ecc_generator_table::get_xyprime_table( + MultiTableId::BN254_XYPRIME, BasicTableId::BN254_XYPRIME_BASIC); + MULTI_TABLES[MultiTableId::BN254_XLO_ENDO] = + ecc_generator_tables::ecc_generator_table::get_xlo_endo_table( + MultiTableId::BN254_XLO_ENDO, BasicTableId::BN254_XLO_ENDO_BASIC); + MULTI_TABLES[MultiTableId::BN254_XHI_ENDO] = + ecc_generator_tables::ecc_generator_table::get_xhi_endo_table( + MultiTableId::BN254_XHI_ENDO, BasicTableId::BN254_XHI_ENDO_BASIC); + MULTI_TABLES[MultiTableId::BN254_XYPRIME_ENDO] = + ecc_generator_tables::ecc_generator_table::get_xyprime_endo_table( + MultiTableId::BN254_XYPRIME_ENDO, BasicTableId::BN254_XYPRIME_ENDO_BASIC); + MULTI_TABLES[MultiTableId::SECP256K1_XLO] = ecc_generator_tables::ecc_generator_table::get_xlo_table( + MultiTableId::SECP256K1_XLO, BasicTableId::SECP256K1_XLO_BASIC); + MULTI_TABLES[MultiTableId::SECP256K1_XHI] = ecc_generator_tables::ecc_generator_table::get_xhi_table( + MultiTableId::SECP256K1_XHI, BasicTableId::SECP256K1_XHI_BASIC); + MULTI_TABLES[MultiTableId::SECP256K1_YLO] = ecc_generator_tables::ecc_generator_table::get_ylo_table( + MultiTableId::SECP256K1_YLO, BasicTableId::SECP256K1_YLO_BASIC); + MULTI_TABLES[MultiTableId::SECP256K1_YHI] = ecc_generator_tables::ecc_generator_table::get_yhi_table( + MultiTableId::SECP256K1_YHI, BasicTableId::SECP256K1_YHI_BASIC); + MULTI_TABLES[MultiTableId::SECP256K1_XYPRIME] = + ecc_generator_tables::ecc_generator_table::get_xyprime_table( + MultiTableId::SECP256K1_XYPRIME, BasicTableId::SECP256K1_XYPRIME_BASIC); + MULTI_TABLES[MultiTableId::SECP256K1_XLO_ENDO] = + ecc_generator_tables::ecc_generator_table::get_xlo_endo_table( + MultiTableId::SECP256K1_XLO_ENDO, BasicTableId::SECP256K1_XLO_ENDO_BASIC); + MULTI_TABLES[MultiTableId::SECP256K1_XHI_ENDO] = + ecc_generator_tables::ecc_generator_table::get_xhi_endo_table( + MultiTableId::SECP256K1_XHI_ENDO, BasicTableId::SECP256K1_XHI_ENDO_BASIC); + MULTI_TABLES[MultiTableId::SECP256K1_XYPRIME_ENDO] = + ecc_generator_tables::ecc_generator_table::get_xyprime_endo_table( + MultiTableId::SECP256K1_XYPRIME_ENDO, BasicTableId::SECP256K1_XYPRIME_ENDO_BASIC); + MULTI_TABLES[MultiTableId::BLAKE_XOR] = blake2s_tables::get_blake2s_xor_table(MultiTableId::BLAKE_XOR); + MULTI_TABLES[MultiTableId::BLAKE_XOR_ROTATE_16] = + blake2s_tables::get_blake2s_xor_rotate_16_table(MultiTableId::BLAKE_XOR_ROTATE_16); + MULTI_TABLES[MultiTableId::BLAKE_XOR_ROTATE_8] = + blake2s_tables::get_blake2s_xor_rotate_8_table(MultiTableId::BLAKE_XOR_ROTATE_8); + MULTI_TABLES[MultiTableId::BLAKE_XOR_ROTATE_7] = + blake2s_tables::get_blake2s_xor_rotate_7_table(MultiTableId::BLAKE_XOR_ROTATE_7); } } // namespace -const PlookupMultiTable& create_table(const PlookupMultiTableId id) +const MultiTable& create_table(const MultiTableId id) { if (!inited) { init_multi_tables(); @@ -45,16 +94,16 @@ const PlookupMultiTable& create_table(const PlookupMultiTableId id) return MULTI_TABLES[id]; } -PlookupReadData get_table_values(const PlookupMultiTableId id, - const fr& key_a, - const fr& key_b, - const bool is_2_to_1_lookup) +ReadData get_lookup_accumulators(const MultiTableId id, + const fr& key_a, + const fr& key_b, + const bool is_2_to_1_lookup) { + // return multi-table, populating global array of all multi-tables if need be const auto& multi_table = create_table(id); - const size_t num_lookups = multi_table.lookup_ids.size(); - PlookupReadData result; + ReadData lookup; const auto key_a_slices = numeric::slice_input_using_variable_bases(key_a, multi_table.slice_sizes); const auto key_b_slices = numeric::slice_input_using_variable_bases(key_b, multi_table.slice_sizes); @@ -64,30 +113,68 @@ PlookupReadData get_table_values(const PlookupMultiTableId id, std::vector column_3_raw_values; for (size_t i = 0; i < num_lookups; ++i) { + // get i-th table query function and then submit query const auto values = multi_table.get_table_values[i]({ key_a_slices[i], key_b_slices[i] }); + // store all query data in raw columns and key entry column_1_raw_values.emplace_back(key_a_slices[i]); column_2_raw_values.emplace_back(is_2_to_1_lookup ? key_b_slices[i] : values[0]); column_3_raw_values.emplace_back(is_2_to_1_lookup ? values[0] : values[1]); - const PlookupBasicTable::KeyEntry key_entry{ { key_a_slices[i], key_b_slices[i] }, values }; - result.key_entries.emplace_back(key_entry); + // Question: why are we storing the key slices twice? + const BasicTable::KeyEntry key_entry{ { key_a_slices[i], key_b_slices[i] }, values }; + lookup.key_entries.emplace_back(key_entry); } - result.column_1_accumulator_values.resize(num_lookups); - result.column_2_accumulator_values.resize(num_lookups); - result.column_3_accumulator_values.resize(num_lookups); - - result.column_1_accumulator_values[num_lookups - 1] = column_1_raw_values[num_lookups - 1]; - result.column_2_accumulator_values[num_lookups - 1] = column_2_raw_values[num_lookups - 1]; - result.column_3_accumulator_values[num_lookups - 1] = column_3_raw_values[num_lookups - 1]; + lookup[ColumnIdx::C1].resize(num_lookups); + lookup[ColumnIdx::C2].resize(num_lookups); + lookup[ColumnIdx::C3].resize(num_lookups); + + /** + * A multi-table consists of multiple basic tables (say L = 6). + * + * [ ] [ ] + * [ ]| |[ ][ ]| |[ ] + * M ≡ | B1 || B2 || B3 || B4 || B5 || B6 | + * [ ]| |[ ][ ]| |[ ] + * [ ] [ ] + * |̐ |̐ |̐ |̐ |̐ |̐ + * s1 s2 s3 s4 s5 s6 + * + * Note that different basic tables can be of different sizes. Every lookup query generates L output slices (one for + * each basic table, here, s1, s2, ..., s6). In other words, every lookup query add L lookup gates to the program. + * Let the input slices/keys be (a1, b1), (a2, b2), ..., (a6, b6). The lookup gate structure is as follows: + * + * +---+-----------------------------------+----------------------------------+-----------------------------------+ + * | s | key_a | key_b | output | + * |---+-----------------------------------+----------------------------------+-----------------------------------| + * | 6 | a6 + p.a5 + p^2.a4 + ... + p^5.a1 | b6 + q.b5 + qq.b4 + ... + q^5.b1 | s6 + r.s5 + r^2.s4 + ... + r^5.s1 | + * | 5 | a5 + p.a4 + ...... + p^4.a1 | b5 + q.b4 + ...... + q^4.b1 | s5 + r.s4 + ...... + r^4.s1 | + * | 4 | a4 + p.a3 + ... + p^3.a1 | b4 + q.b3 + ... + q^3.b1 | s4 + r.s3 + ... + r^3.s1 | + * | 3 | a3 + p.a2 + p^2.a1 | b3 + q.b2 + q^2.b1 | s3 + r.s2 + r^2.s1 | + * | 2 | a2 + p.a1 | b2 + q.b1 | s2 + r.a1 | + * | 1 | a1 | b1 | s1 | + * +---+-----------------------------------+----------------------------------+-----------------------------------+ + * + * Note that we compute the accumulating sums of the slices so as to avoid using additonal gates for the purpose of + * reconstructing inputs/outputs. Here, (p, q, r) are referred to as column coefficients/step sizes. + * In the next few lines, we compute these accumulating sums from raw column values (a1, ..., a6), (b1, ..., b6), + * (s1, ..., s6) and column coefficients (p, q, r). + * + * For more details: see + * https://app.gitbook.com/o/-LgCgJ8TCO7eGlBr34fj/s/-MEwtqp3H6YhHUTQ_pVJ/plookup-gates-for-ultraplonk/lookup-table-structures + * + */ + lookup[ColumnIdx::C1][num_lookups - 1] = column_1_raw_values[num_lookups - 1]; + lookup[ColumnIdx::C2][num_lookups - 1] = column_2_raw_values[num_lookups - 1]; + lookup[ColumnIdx::C3][num_lookups - 1] = column_3_raw_values[num_lookups - 1]; for (size_t i = 1; i < num_lookups; ++i) { - const auto& previous_1 = result.column_1_accumulator_values[num_lookups - i]; - const auto& previous_2 = result.column_2_accumulator_values[num_lookups - i]; - const auto& previous_3 = result.column_3_accumulator_values[num_lookups - i]; + const auto& previous_1 = lookup[ColumnIdx::C1][num_lookups - i]; + const auto& previous_2 = lookup[ColumnIdx::C2][num_lookups - i]; + const auto& previous_3 = lookup[ColumnIdx::C3][num_lookups - i]; - auto& current_1 = result.column_1_accumulator_values[num_lookups - 1 - i]; - auto& current_2 = result.column_2_accumulator_values[num_lookups - 1 - i]; - auto& current_3 = result.column_3_accumulator_values[num_lookups - 1 - i]; + auto& current_1 = lookup[ColumnIdx::C1][num_lookups - 1 - i]; + auto& current_2 = lookup[ColumnIdx::C2][num_lookups - 1 - i]; + auto& current_3 = lookup[ColumnIdx::C3][num_lookups - 1 - i]; const auto& raw_1 = column_1_raw_values[num_lookups - 1 - i]; const auto& raw_2 = column_2_raw_values[num_lookups - 1 - i]; @@ -97,8 +184,7 @@ PlookupReadData get_table_values(const PlookupMultiTableId id, current_2 = raw_2 + previous_2 * multi_table.column_2_step_sizes[num_lookups - i]; current_3 = raw_3 + previous_3 * multi_table.column_3_step_sizes[num_lookups - i]; } - return result; + return lookup; } -} // namespace plookup -} // namespace waffle \ No newline at end of file +} // namespace plookup \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/plookup_tables/plookup_tables.hpp b/cpp/src/aztec/plonk/composer/plookup_tables/plookup_tables.hpp index dbc3c092c2..518e025982 100644 --- a/cpp/src/aztec/plonk/composer/plookup_tables/plookup_tables.hpp +++ b/cpp/src/aztec/plonk/composer/plookup_tables/plookup_tables.hpp @@ -7,18 +7,18 @@ #include "sparse.hpp" #include "pedersen.hpp" #include "uint.hpp" - -namespace waffle { +#include "non_native_group_generator.hpp" +#include "blake2s.hpp" namespace plookup { -const PlookupMultiTable& create_table(const PlookupMultiTableId id); +const MultiTable& create_table(const MultiTableId id); -PlookupReadData get_table_values(const PlookupMultiTableId id, - const barretenberg::fr& key_a, - const barretenberg::fr& key_b = 0, - const bool is_2_to_1_map = false); +ReadData get_lookup_accumulators(const MultiTableId id, + const barretenberg::fr& key_a, + const barretenberg::fr& key_b = 0, + const bool is_2_to_1_map = false); -inline PlookupBasicTable create_basic_table(const PlookupBasicTableId id, const size_t index) +inline BasicTable create_basic_table(const BasicTableId id, const size_t index) { switch (id) { case AES_SPARSE_MAP: { @@ -66,65 +66,175 @@ inline PlookupBasicTable create_basic_table(const PlookupBasicTableId id, const case SHA256_BASE16_ROTATE2: { return sparse_tables::generate_sparse_table_with_rotation<16, 11, 2>(SHA256_BASE16_ROTATE2, index); } + case UINT_XOR_ROTATE0: { + return uint_tables::generate_xor_rotate_table<6, 0>(UINT_XOR_ROTATE0, index); + } + case UINT_AND_ROTATE0: { + return uint_tables::generate_and_rotate_table<6, 0>(UINT_AND_ROTATE0, index); + } + case BN254_XLO_BASIC: { + return ecc_generator_tables::ecc_generator_table::generate_xlo_table(BN254_XLO_BASIC, index); + } + case BN254_XHI_BASIC: { + return ecc_generator_tables::ecc_generator_table::generate_xhi_table(BN254_XHI_BASIC, index); + } + case BN254_YLO_BASIC: { + return ecc_generator_tables::ecc_generator_table::generate_ylo_table(BN254_YLO_BASIC, index); + } + case BN254_YHI_BASIC: { + return ecc_generator_tables::ecc_generator_table::generate_yhi_table(BN254_YHI_BASIC, index); + } + case BN254_XYPRIME_BASIC: { + return ecc_generator_tables::ecc_generator_table::generate_xyprime_table(BN254_XYPRIME_BASIC, + index); + } + case BN254_XLO_ENDO_BASIC: { + return ecc_generator_tables::ecc_generator_table::generate_xlo_endo_table( + BN254_XLO_ENDO_BASIC, index); + } + case BN254_XHI_ENDO_BASIC: { + return ecc_generator_tables::ecc_generator_table::generate_xhi_endo_table( + BN254_XHI_ENDO_BASIC, index); + } + case BN254_XYPRIME_ENDO_BASIC: { + return ecc_generator_tables::ecc_generator_table::generate_xyprime_endo_table( + BN254_XYPRIME_ENDO_BASIC, index); + } + case SECP256K1_XLO_BASIC: { + return ecc_generator_tables::ecc_generator_table::generate_xlo_table(SECP256K1_XLO_BASIC, index); + } + case SECP256K1_XHI_BASIC: { + return ecc_generator_tables::ecc_generator_table::generate_xhi_table(SECP256K1_XHI_BASIC, index); + } + case SECP256K1_YLO_BASIC: { + return ecc_generator_tables::ecc_generator_table::generate_ylo_table(SECP256K1_YLO_BASIC, index); + } + case SECP256K1_YHI_BASIC: { + return ecc_generator_tables::ecc_generator_table::generate_yhi_table(SECP256K1_YHI_BASIC, index); + } + case SECP256K1_XYPRIME_BASIC: { + return ecc_generator_tables::ecc_generator_table::generate_xyprime_table(SECP256K1_XYPRIME_BASIC, + index); + } + case SECP256K1_XLO_ENDO_BASIC: { + return ecc_generator_tables::ecc_generator_table::generate_xlo_endo_table( + SECP256K1_XLO_ENDO_BASIC, index); + } + case SECP256K1_XHI_ENDO_BASIC: { + return ecc_generator_tables::ecc_generator_table::generate_xhi_endo_table( + SECP256K1_XHI_ENDO_BASIC, index); + } + case SECP256K1_XYPRIME_ENDO_BASIC: { + return ecc_generator_tables::ecc_generator_table::generate_xyprime_endo_table( + SECP256K1_XYPRIME_ENDO_BASIC, index); + } + case BLAKE_XOR_ROTATE0: { + return blake2s_tables::generate_xor_rotate_table<6, 0>(BLAKE_XOR_ROTATE0, index); + } + case BLAKE_XOR_ROTATE0_SLICE5_MOD4: { + return blake2s_tables::generate_xor_rotate_table<5, 0, true>(BLAKE_XOR_ROTATE0_SLICE5_MOD4, index); + } + case BLAKE_XOR_ROTATE2: { + return blake2s_tables::generate_xor_rotate_table<6, 2>(BLAKE_XOR_ROTATE2, index); + } + case BLAKE_XOR_ROTATE1: { + return blake2s_tables::generate_xor_rotate_table<6, 1>(BLAKE_XOR_ROTATE1, index); + } + case BLAKE_XOR_ROTATE4: { + return blake2s_tables::generate_xor_rotate_table<6, 4>(BLAKE_XOR_ROTATE4, index); + } case PEDERSEN_0: { - return pedersen_tables::generate_sidon_pedersen_table<0>(PEDERSEN_0, index); + return pedersen_tables::basic::generate_basic_pedersen_table<0>(PEDERSEN_0, index); } case PEDERSEN_1: { - return pedersen_tables::generate_sidon_pedersen_table<1>(PEDERSEN_1, index); + return pedersen_tables::basic::generate_basic_pedersen_table<1>(PEDERSEN_1, index); } case PEDERSEN_2: { - return pedersen_tables::generate_sidon_pedersen_table<2>(PEDERSEN_2, index); + return pedersen_tables::basic::generate_basic_pedersen_table<2>(PEDERSEN_2, index); } case PEDERSEN_3: { - return pedersen_tables::generate_sidon_pedersen_table<3>(PEDERSEN_3, index); + return pedersen_tables::basic::generate_basic_pedersen_table<3>(PEDERSEN_3, index); } case PEDERSEN_4: { - return pedersen_tables::generate_sidon_pedersen_table<4>(PEDERSEN_4, index); + return pedersen_tables::basic::generate_basic_pedersen_table<4>(PEDERSEN_4, index); } case PEDERSEN_5: { - return pedersen_tables::generate_sidon_pedersen_table<5>(PEDERSEN_5, index); + return pedersen_tables::basic::generate_basic_pedersen_table<5>(PEDERSEN_5, index); } case PEDERSEN_6: { - return pedersen_tables::generate_sidon_pedersen_table<6>(PEDERSEN_6, index); + return pedersen_tables::basic::generate_basic_pedersen_table<6>(PEDERSEN_6, index); } case PEDERSEN_7: { - return pedersen_tables::generate_sidon_pedersen_table<7>(PEDERSEN_7, index); + return pedersen_tables::basic::generate_basic_pedersen_table<7>(PEDERSEN_7, index); } case PEDERSEN_8: { - return pedersen_tables::generate_sidon_pedersen_table<8>(PEDERSEN_8, index); + return pedersen_tables::basic::generate_basic_pedersen_table<8>(PEDERSEN_8, index); } case PEDERSEN_9: { - return pedersen_tables::generate_sidon_pedersen_table<9>(PEDERSEN_9, index); + return pedersen_tables::basic::generate_basic_pedersen_table<9>(PEDERSEN_9, index); } case PEDERSEN_10: { - return pedersen_tables::generate_sidon_pedersen_table<10>(PEDERSEN_10, index); + return pedersen_tables::basic::generate_basic_pedersen_table<10>(PEDERSEN_10, index); } case PEDERSEN_11: { - return pedersen_tables::generate_sidon_pedersen_table<11>(PEDERSEN_11, index); + return pedersen_tables::basic::generate_basic_pedersen_table<11>(PEDERSEN_11, index); } case PEDERSEN_12: { - return pedersen_tables::generate_sidon_pedersen_table<12>(PEDERSEN_12, index); + return pedersen_tables::basic::generate_basic_pedersen_table<12>(PEDERSEN_12, index); } case PEDERSEN_13: { - return pedersen_tables::generate_sidon_pedersen_table<13>(PEDERSEN_13, index); + return pedersen_tables::basic::generate_basic_pedersen_table<13>(PEDERSEN_13, index); } - case PEDERSEN_14: { - return pedersen_tables::generate_sidon_pedersen_table<14>(PEDERSEN_14, index); + case PEDERSEN_14_SMALL: { + return pedersen_tables::basic::generate_basic_pedersen_table<14, true>(PEDERSEN_14_SMALL, index); } case PEDERSEN_15: { - return pedersen_tables::generate_sidon_pedersen_table<15>(PEDERSEN_15, index); + return pedersen_tables::basic::generate_basic_pedersen_table<15>(PEDERSEN_15, index); } case PEDERSEN_16: { - return pedersen_tables::generate_sidon_pedersen_table<16>(PEDERSEN_16, index); + return pedersen_tables::basic::generate_basic_pedersen_table<16>(PEDERSEN_16, index); } case PEDERSEN_17: { - return pedersen_tables::generate_sidon_pedersen_table<17>(PEDERSEN_17, index); + return pedersen_tables::basic::generate_basic_pedersen_table<17>(PEDERSEN_17, index); } - case UINT_XOR_ROTATE0: { - return uint_tables::generate_xor_rotate_table<6, 0>(UINT_XOR_ROTATE0, index); + case PEDERSEN_18: { + return pedersen_tables::basic::generate_basic_pedersen_table<18>(PEDERSEN_18, index); } - case UINT_AND_ROTATE0: { - return uint_tables::generate_and_rotate_table<6, 0>(UINT_AND_ROTATE0, index); + case PEDERSEN_19: { + return pedersen_tables::basic::generate_basic_pedersen_table<19>(PEDERSEN_19, index); + } + case PEDERSEN_20: { + return pedersen_tables::basic::generate_basic_pedersen_table<20>(PEDERSEN_20, index); + } + case PEDERSEN_21: { + return pedersen_tables::basic::generate_basic_pedersen_table<21>(PEDERSEN_21, index); + } + case PEDERSEN_22: { + return pedersen_tables::basic::generate_basic_pedersen_table<22>(PEDERSEN_22, index); + } + case PEDERSEN_23: { + return pedersen_tables::basic::generate_basic_pedersen_table<23>(PEDERSEN_23, index); + } + case PEDERSEN_24: { + return pedersen_tables::basic::generate_basic_pedersen_table<24>(PEDERSEN_24, index); + } + case PEDERSEN_25: { + return pedersen_tables::basic::generate_basic_pedersen_table<25>(PEDERSEN_25, index); + } + case PEDERSEN_26: { + return pedersen_tables::basic::generate_basic_pedersen_table<26>(PEDERSEN_26, index); + } + case PEDERSEN_27: { + return pedersen_tables::basic::generate_basic_pedersen_table<27>(PEDERSEN_27, index); + } + case PEDERSEN_28: { + return pedersen_tables::basic::generate_basic_pedersen_table<28>(PEDERSEN_28, index); + } + case PEDERSEN_29_SMALL: { + return pedersen_tables::basic::generate_basic_pedersen_table<29, true>(PEDERSEN_29_SMALL, index); + } + case PEDERSEN_IV_BASE: { + return pedersen_tables::basic::generate_pedersen_iv_table(PEDERSEN_IV_BASE); } default: { throw_or_abort("table id does not exist"); @@ -132,5 +242,4 @@ inline PlookupBasicTable create_basic_table(const PlookupBasicTableId id, const } } } -} // namespace plookup -} // namespace waffle \ No newline at end of file +} // namespace plookup \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/plookup_tables/sha256.hpp b/cpp/src/aztec/plonk/composer/plookup_tables/sha256.hpp index 5cd27765ef..4ce3a709e7 100644 --- a/cpp/src/aztec/plonk/composer/plookup_tables/sha256.hpp +++ b/cpp/src/aztec/plonk/composer/plookup_tables/sha256.hpp @@ -8,7 +8,7 @@ #include "types.hpp" #include "sparse.hpp" -namespace waffle { +namespace plookup { namespace sha256_tables { static constexpr uint64_t choose_normalization_table[28]{ @@ -92,28 +92,27 @@ static constexpr uint64_t witness_extension_normalization_table[16]{ 2, }; -inline PlookupBasicTable generate_witness_extension_normalization_table(PlookupBasicTableId id, - const size_t table_index) +inline BasicTable generate_witness_extension_normalization_table(BasicTableId id, const size_t table_index) { return sparse_tables::generate_sparse_normalization_table<16, 3, witness_extension_normalization_table>( id, table_index); } -inline PlookupBasicTable generate_choose_normalization_table(PlookupBasicTableId id, const size_t table_index) +inline BasicTable generate_choose_normalization_table(BasicTableId id, const size_t table_index) { return sparse_tables::generate_sparse_normalization_table<28, 2, choose_normalization_table>(id, table_index); } -inline PlookupBasicTable generate_majority_normalization_table(PlookupBasicTableId id, const size_t table_index) +inline BasicTable generate_majority_normalization_table(BasicTableId id, const size_t table_index) { return sparse_tables::generate_sparse_normalization_table<16, 3, majority_normalization_table>(id, table_index); } -inline PlookupMultiTable get_witness_extension_output_table(const PlookupMultiTableId id = SHA256_WITNESS_OUTPUT) +inline MultiTable get_witness_extension_output_table(const MultiTableId id = SHA256_WITNESS_OUTPUT) { const size_t num_entries = 11; - PlookupMultiTable table(numeric::pow64(16, 3), 1 << 3, 0, num_entries); + MultiTable table(numeric::pow64(16, 3), 1 << 3, 0, num_entries); table.id = id; for (size_t i = 0; i < num_entries; ++i) { @@ -125,11 +124,11 @@ inline PlookupMultiTable get_witness_extension_output_table(const PlookupMultiTa return table; } -inline PlookupMultiTable get_choose_output_table(const PlookupMultiTableId id = SHA256_CH_OUTPUT) +inline MultiTable get_choose_output_table(const MultiTableId id = SHA256_CH_OUTPUT) { const size_t num_entries = 16; - PlookupMultiTable table(numeric::pow64(28, 2), 1 << 2, 0, num_entries); + MultiTable table(numeric::pow64(28, 2), 1 << 2, 0, num_entries); table.id = id; for (size_t i = 0; i < num_entries; ++i) { @@ -141,11 +140,11 @@ inline PlookupMultiTable get_choose_output_table(const PlookupMultiTableId id = return table; } -inline PlookupMultiTable get_majority_output_table(const PlookupMultiTableId id = SHA256_MAJ_OUTPUT) +inline MultiTable get_majority_output_table(const MultiTableId id = SHA256_MAJ_OUTPUT) { const size_t num_entries = 11; - PlookupMultiTable table(numeric::pow64(16, 3), 1 << 3, 0, num_entries); + MultiTable table(numeric::pow64(16, 3), 1 << 3, 0, num_entries); table.id = id; for (size_t i = 0; i < num_entries; ++i) { @@ -159,24 +158,18 @@ inline PlookupMultiTable get_majority_output_table(const PlookupMultiTableId id inline std::array get_majority_rotation_multipliers() { - constexpr uint64_t base = 16; - + constexpr uint64_t base_temp = 16; + auto base = barretenberg::fr(base_temp); // scaling factors applied to a's sparse limbs, excluding the rotated limb - const std::array rot6_coefficients{ barretenberg::fr(0), - barretenberg::fr(base).pow(11 - 2), - barretenberg::fr(base).pow(22 - 2) }; - const std::array rot11_coefficients{ barretenberg::fr(base).pow(32 - 13), - barretenberg::fr(0), - barretenberg::fr(base).pow(22 - 13) }; - const std::array rot25_coefficients{ barretenberg::fr(base).pow(32 - 22), - barretenberg::fr(base).pow(32 - 22 + 11), - barretenberg::fr(0) }; + const std::array rot2_coefficients{ 0, base.pow(11 - 2), base.pow(22 - 2) }; + const std::array rot13_coefficients{ base.pow(32 - 13), 0, base.pow(22 - 13) }; + const std::array rot22_coefficients{ base.pow(32 - 22), base.pow(32 - 22 + 11), 0 }; // these are the coefficients that we want const std::array target_rotation_coefficients{ - rot6_coefficients[0] + rot11_coefficients[0] + rot25_coefficients[0], - rot6_coefficients[1] + rot11_coefficients[1] + rot25_coefficients[1], - rot6_coefficients[2] + rot11_coefficients[2] + rot25_coefficients[2], + rot2_coefficients[0] + rot13_coefficients[0] + rot22_coefficients[0], + rot2_coefficients[1] + rot13_coefficients[1] + rot22_coefficients[1], + rot2_coefficients[2] + rot13_coefficients[2] + rot22_coefficients[2], }; barretenberg::fr column_2_row_1_multiplier = target_rotation_coefficients[0]; @@ -216,7 +209,8 @@ inline std::array get_choose_rotation_multipliers() rot6_coefficients[2] + rot11_coefficients[2] + rot25_coefficients[2], }; - barretenberg::fr column_2_row_1_multiplier = barretenberg::fr(1) * target_rotation_coefficients[0]; + barretenberg::fr column_2_row_1_multiplier = + barretenberg::fr(1) * target_rotation_coefficients[0]; // why multiply by one? // this gives us the correct scaling factor for a0's 1st limb std::array current_coefficients{ @@ -233,12 +227,12 @@ inline std::array get_choose_rotation_multipliers() return rotation_multipliers; } -inline PlookupMultiTable get_witness_extension_input_table(const PlookupMultiTableId id = SHA256_WITNESS_INPUT) +inline MultiTable get_witness_extension_input_table(const MultiTableId id = SHA256_WITNESS_INPUT) { std::vector column_1_coefficients{ 1, 1 << 3, 1 << 10, 1 << 18 }; std::vector column_2_coefficients{ 0, 0, 0, 0 }; std::vector column_3_coefficients{ 0, 0, 0, 0 }; - PlookupMultiTable table(column_1_coefficients, column_2_coefficients, column_3_coefficients); + MultiTable table(column_1_coefficients, column_2_coefficients, column_3_coefficients); table.id = id; table.slice_sizes = { (1 << 3), (1 << 7), (1 << 8), (1 << 18) }; table.lookup_ids = { SHA256_WITNESS_SLICE_3, @@ -255,7 +249,7 @@ inline PlookupMultiTable get_witness_extension_input_table(const PlookupMultiTab return table; } -inline PlookupMultiTable get_choose_input_table(const PlookupMultiTableId id = SHA256_CH_INPUT) +inline MultiTable get_choose_input_table(const MultiTableId id = SHA256_CH_INPUT) { /** * When reading from our lookup tables, we can read from the differences between adjacent rows in program memory, @@ -295,11 +289,11 @@ inline PlookupMultiTable get_choose_input_table(const PlookupMultiTableId id = S * * In sparse form, we can represent this as: * - * (a >>> 6) + (a >>> 11) + (a >>> 25) + 7 * (a + 2 * b + 3 * c) + * 7 * (a >>> 6) + (a >>> 11) + (a >>> 25) + (a + 2 * b + 3 * c) * * When decomposing a into sparse form, we would therefore like to obtain the following: * - * (a >>> 6) + (a >>> 11) + (a >>> 25) + 7 * (a) + * 7 * (a >>> 6) + (a >>> 11) + (a >>> 25) + (a) * * We need to determine the values of the constants (q_1, q_2, q_3) that we will be scaling our lookup values by, *when assembling our accumulated sums. @@ -348,15 +342,15 @@ inline PlookupMultiTable get_choose_input_table(const PlookupMultiTableId id = S std::vector column_3_coefficients{ barretenberg::fr(1), column_3_row_2_multiplier + barretenberg::fr(1), barretenberg::fr(1) }; - PlookupMultiTable table(column_1_coefficients, column_2_coefficients, column_3_coefficients); + MultiTable table(column_1_coefficients, column_2_coefficients, column_3_coefficients); table.id = id; - table.slice_sizes = { (1 << 11), (1 << 11), (1 << 11) }; + table.slice_sizes = { (1 << 11), (1 << 11), (1 << 10) }; table.lookup_ids = { SHA256_BASE28_ROTATE6, SHA256_BASE28, SHA256_BASE28_ROTATE3 }; table.get_table_values.push_back(&sparse_tables::get_sparse_table_with_rotation_values<28, 6>); table.get_table_values.push_back(&sparse_tables::get_sparse_table_with_rotation_values<28, 0>); table.get_table_values.push_back(&sparse_tables::get_sparse_table_with_rotation_values<28, 3>); - // table.get_table_values = std::vector{ + // table.get_table_values = std::vector{ // &get_sha256_sparse_map_values<28, 0, 0>, // &get_sha256_sparse_map_values<28, 3, 0>, @@ -364,7 +358,8 @@ inline PlookupMultiTable get_choose_input_table(const PlookupMultiTableId id = S return table; } -inline PlookupMultiTable get_majority_input_table(const PlookupMultiTableId id = SHA256_MAJ_INPUT) +// This table (at third row and column) returns the sum of roations that "non-trivially wrap" +inline MultiTable get_majority_input_table(const MultiTableId id = SHA256_MAJ_INPUT) { /** * We want to tackle the SHA256 `maj` sub-algorithm @@ -373,7 +368,7 @@ inline PlookupMultiTable get_majority_input_table(const PlookupMultiTableId id = * * In sparse form, we can represent this as: * - * (a >>> 2) + (a >>> 13) + (a >>> 22) + 4 * (a + b + c) + * 4 * (a >>> 2) + (a >>> 13) + (a >>> 22) + (a + b + c) * * * We need to determine the values of the constants (q_1, q_2, q_3) that we will be scaling our lookup values by, @@ -416,9 +411,9 @@ inline PlookupMultiTable get_majority_input_table(const PlookupMultiTableId id = barretenberg::fr(1), barretenberg::fr(1) + column_2_row_3_multiplier }; - PlookupMultiTable table(column_1_coefficients, column_2_coefficients, column_3_coefficients); + MultiTable table(column_1_coefficients, column_2_coefficients, column_3_coefficients); table.id = id; - table.slice_sizes = { (1 << 11), (1 << 11), (1 << 11) }; + table.slice_sizes = { (1 << 11), (1 << 11), (1 << 10) }; table.lookup_ids = { SHA256_BASE16_ROTATE2, SHA256_BASE16_ROTATE2, SHA256_BASE16 }; table.get_table_values = { &sparse_tables::get_sparse_table_with_rotation_values<16, 2>, @@ -429,4 +424,4 @@ inline PlookupMultiTable get_majority_input_table(const PlookupMultiTableId id = } } // namespace sha256_tables -} // namespace waffle \ No newline at end of file +} // namespace plookup \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/plookup_tables/sidon_pedersen.hpp b/cpp/src/aztec/plonk/composer/plookup_tables/sidon_pedersen.hpp deleted file mode 100644 index 466f89b298..0000000000 --- a/cpp/src/aztec/plonk/composer/plookup_tables/sidon_pedersen.hpp +++ /dev/null @@ -1,258 +0,0 @@ -// #pragma once - -// #include -// #include - -// #include -// #include -// #include -// #include - -// namespace waffle { - -// namespace sidon_pedersen_tables { - -// // find the smallest prime p ,where p >= lower_bound and (p mod 4 = 3) -// // the latter condition is because our fq2 implementation requires -1 to be a quadratic non residue -// constexpr uint64_t compute_nearest_safe_prime(const uint64_t lower_bound) -// { -// uint64_t iterator = lower_bound; -// while (true) { -// if ((iterator & 0x3UL) == 0x3UL) { -// bool prime = true; -// for (uint64_t i = 2; i * i <= iterator; ++i) { -// if (iterator % i == 0) { -// prime = false; -// break; -// } -// } -// if (prime) { -// return iterator; -// } -// } -// ++iterator; -// } -// return 0; -// } - -// std::vector compute_prime_factors(uint64_t input) -// { -// uint64_t target = input; -// std::vector factors; -// while (target % 2 == 0) { -// factors.push_back(2); -// target >>= 1; -// } - -// for (uint64_t i = 3; i * i <= target; i += 2) { -// while (target % i == 0) { -// factors.push_back(i); -// target /= i; -// } -// } - -// if (target > 2) { -// factors.push_back(target); -// } - -// std::sort(factors.begin(), factors.end()); - -// std::vector unique_factors; - -// unique_factors.push_back(factors[0]); -// for (size_t i = 1; i < factors.size(); ++i) { -// if (factors[i] != factors[i - 1]) { -// unique_factors.push_back(factors[i]); -// } -// } -// return unique_factors; -// } - -// template constexpr std::array get_r_squared() -// { -// uint1024_t r_squared = uint1024_t(1) << 256; -// uint1024_t prime = uint1024_t(uint512_t(uint256_t(sidon_prime))); -// r_squared = r_squared * r_squared; -// r_squared = r_squared % prime; -// uint256_t out = r_squared.lo.lo; -// return { out.data[0], out.data[1], out.data[2], out.data[3] }; -// } - -// template constexpr uint64_t get_r_inv() -// { -// uint256_t prime(sidon_prime); -// uint512_t r{ 0, 1 }; -// uint512_t q{ -prime, 0 }; -// uint256_t q_inv = q.invmod(r).lo; -// return q_inv.data[0]; -// } - -// template class SidonFqParams { -// public: -// static constexpr uint64_t modulus_0 = sidon_prime; -// static constexpr uint64_t modulus_1 = 0; -// static constexpr uint64_t modulus_2 = 0; -// static constexpr uint64_t modulus_3 = 0; - -// static constexpr std::array r_squared = get_r_squared(); -// static constexpr uint64_t r_squared_0 = r_squared[0]; -// static constexpr uint64_t r_squared_1 = r_squared[1]; -// static constexpr uint64_t r_squared_2 = r_squared[2]; -// static constexpr uint64_t r_squared_3 = r_squared[3]; - -// static constexpr uint64_t cube_root_0 = 0; -// static constexpr uint64_t cube_root_1 = 0; -// static constexpr uint64_t cube_root_2 = 0; -// static constexpr uint64_t cube_root_3 = 0; - -// static constexpr uint64_t primitive_root_0 = 0UL; -// static constexpr uint64_t primitive_root_1 = 0UL; -// static constexpr uint64_t primitive_root_2 = 0UL; -// static constexpr uint64_t primitive_root_3 = 0UL; - -// static constexpr uint64_t endo_g1_lo = 0; -// static constexpr uint64_t endo_g1_mid = 0; -// static constexpr uint64_t endo_g1_hi = 0; -// static constexpr uint64_t endo_g2_lo = 0; -// static constexpr uint64_t endo_g2_mid = 0; -// static constexpr uint64_t endo_minus_b1_lo = 0; -// static constexpr uint64_t endo_minus_b1_mid = 0; -// static constexpr uint64_t endo_b2_lo = 0; -// static constexpr uint64_t endo_b2_mid = 0; - -// static constexpr uint64_t r_inv = get_r_inv(); - -// static constexpr uint64_t coset_generators_0[8]{ 0, 0, 0, 0, 0, 0, 0, 0 }; - -// static constexpr uint64_t coset_generators_1[8]{ 0, 0, 0, 0, 0, 0, 0, 0 }; - -// static constexpr uint64_t coset_generators_2[8]{ 0, 0, 0, 0, 0, 0, 0, 0 }; - -// static constexpr uint64_t coset_generators_3[8]{ 0, 0, 0, 0, 0, 0, 0, 0 }; -// }; - -// template struct SidonFq2Params { -// static constexpr Fq twist_coeff_b_0{ 0, 0, 0, 0 }; -// static constexpr Fq twist_coeff_b_1{ 0, 0, 0, 0 }; -// static constexpr Fq twist_mul_by_q_x_0{ 0, 0, 0, 0 }; -// static constexpr Fq twist_mul_by_q_x_1{ 0, 0, 0, 0 }; -// static constexpr Fq twist_mul_by_q_y_0{ 0, 0, 0, 0 }; -// static constexpr Fq twist_mul_by_q_y_1{ 0, 0, 0, 0 }; -// static constexpr Fq twist_cube_root_0{ 0, 0, 0, 0 }; -// static constexpr Fq twist_cube_root_1{ 0, 0, 0, 0 }; -// }; - -// template using sidon_fq = barretenberg::field>; - -// template -// using sidon_fq2 = barretenberg::field2, SidonFq2Params>>; - -// template inline sidon_fq2 get_sidon_generator() -// { -// constexpr uint64_t q = compute_nearest_safe_prime(set_size); - -// typedef barretenberg::field> fq; -// typedef barretenberg::field2> fq2; -// std::cout << "q = " << q << std::endl; -// std::vector fq2_prime_factors = compute_prime_factors(q * q - 1); - -// std::vector primitive_exponents; -// for (const auto factor : fq2_prime_factors) { -// primitive_exponents.push_back((q * q - 1) / factor); -// } - -// const auto is_generator = [&primitive_exponents](uint64_t x, uint64_t y) { -// fq2 target = fq2(fq(x), fq(y)); -// bool is_primitive = true; -// for (const auto exponent : primitive_exponents) { -// fq2 powered = target.pow(exponent); -// if (powered == fq2(1)) { -// is_primitive = false; -// break; -// } -// } -// return is_primitive; -// }; - -// uint64_t a = 1; -// uint64_t b = 1; -// while (!is_generator(a, b)) { -// a = (a + 1) % q; -// if (a == 0) { -// b = (b + 1) % q; -// } -// } -// return { a, b }; -// } - -// /** -// * Computes a Sidon set of a defined size. Implements section 3.5.2 of the following paper: -// * Bshouty, Nader. "Testers and their applications." Proceedings of the 5th conference on Innovations in theoretical -// *computer science. 2014. -// * -// * For our pedersen hash, we desire a set of integers, where the sums of any two set members are unique. -// * -// * 1. find a prime `q` that is >= our target set size -// * 2. find a generator `g` of the field Fq2 -// * 3. find the set of integers `a`, where `g^a - g` produces an element of Fq <- this is our Sidon set -// * -// * The technique can be extended to find Sidon sequences that are greater than 2 (e.g. sets where all combinations of -// 3 *set members are unique) This can be done by using a higher degree field extension - currently not implemented -// **/ -// template std::vector compute_sidon_set() -// { -// constexpr uint64_t q = compute_nearest_safe_prime(set_size); - -// const auto generator = get_sidon_generator(); - -// // 1: get generator of prime field subgroup -// // 2: compute g^i - g for all i in maximum -// std::vector set_members; - -// auto accumulator = generator; -// for (size_t a = 0; a < q * q - 1; ++a) { -// const auto target_element = accumulator - generator; -// if (target_element.c1 == 0) { -// set_members.push_back(a); -// } -// if (set_members.size() == set_size) { -// break; -// } -// accumulator *= generator; -// } -// return set_members; -// } - -// constexpr size_t BITS_PER_HASH = 512; -// constexpr size_t BITS_PER_TABLE = 10; -// constexpr size_t TABLE_SIZE = (1UL) << BITS_PER_TABLE; -// constexpr size_t TABLE_MULTIPLICITY = 3; // using our sidon sequences, we can read from the same table three times -// constexpr size_t NUM_PEDERSEN_TABLES = (BITS_PER_HASH + (BITS_PER_TABLE * TABLE_MULTIPLICITY)) / (BITS_PER_TABLE * -// TABLE_MULTIPLICITY); - -// static std::vector sidon_set; - -// static std::array, -// std::vector compute_sidon_lookup_table(const grumpkin::g1::element generator) -// { -// std::vector table; -// for (size_t i = 0; i < TABLE_SIZE; ++i) -// { -// table.emplace_back(generator * sidon_set[i]); -// } -// return table; -// } - -// init_sidon_lookup_tables() -// { -// std::vector output; - -// for (size_t i = 0; i < NUM_PEDERSEN_TABLES; ++i) -// { -// g -// } -// for (size_t i = 0; i < TABLE_SIZE; ++i) { -// } -// } -// } // namespace sidon_pedersen_tables -// } // namespace waffle \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/plookup_tables/sparse.hpp b/cpp/src/aztec/plonk/composer/plookup_tables/sparse.hpp index 1d5f0adaa3..8d135516ec 100644 --- a/cpp/src/aztec/plonk/composer/plookup_tables/sparse.hpp +++ b/cpp/src/aztec/plonk/composer/plookup_tables/sparse.hpp @@ -7,7 +7,7 @@ #include #include -namespace waffle { +namespace plookup { namespace sparse_tables { template @@ -24,9 +24,9 @@ inline std::array get_sparse_table_with_rotation_values(con } template -inline PlookupBasicTable generate_sparse_table_with_rotation(PlookupBasicTableId id, const size_t table_index) +inline BasicTable generate_sparse_table_with_rotation(BasicTableId id, const size_t table_index) { - PlookupBasicTable table; + BasicTable table; table.id = id; table.table_index = table_index; table.size = (1U << bits_per_slice); @@ -78,7 +78,7 @@ inline std::array get_sparse_normalization_values(const std } template -inline PlookupBasicTable generate_sparse_normalization_table(PlookupBasicTableId id, const size_t table_index) +inline BasicTable generate_sparse_normalization_table(BasicTableId id, const size_t table_index) { /** * If t = 7*((e >>> 6) + (e >>> 11) + (e >>> 25)) + e + 2f + 3g @@ -86,7 +86,7 @@ inline PlookupBasicTable generate_sparse_normalization_table(PlookupBasicTableId * (e >>> 6) ^ (e >>> 11) ^ (e >>> 25) + e + 2f + 3g */ - PlookupBasicTable table; + BasicTable table; table.id = id; table.table_index = table_index; table.use_twin_keys = false; @@ -116,4 +116,4 @@ inline PlookupBasicTable generate_sparse_normalization_table(PlookupBasicTableId return table; } } // namespace sparse_tables -} // namespace waffle \ No newline at end of file +} // namespace plookup \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/plookup_tables/types.hpp b/cpp/src/aztec/plonk/composer/plookup_tables/types.hpp index 5843111b37..c440459848 100644 --- a/cpp/src/aztec/plonk/composer/plookup_tables/types.hpp +++ b/cpp/src/aztec/plonk/composer/plookup_tables/types.hpp @@ -5,8 +5,9 @@ #include -namespace waffle { -enum PlookupBasicTableId { +namespace plookup { + +enum BasicTableId { XOR, AND, PEDERSEN, @@ -28,10 +29,45 @@ enum PlookupBasicTableId { SHA256_BASE16_ROTATE6, SHA256_BASE16_ROTATE7, SHA256_BASE16_ROTATE8, + UINT_XOR_ROTATE0, + UINT_AND_ROTATE0, + BN254_XLO_BASIC, + BN254_XHI_BASIC, + BN254_YLO_BASIC, + BN254_YHI_BASIC, + BN254_XYPRIME_BASIC, + BN254_XLO_ENDO_BASIC, + BN254_XHI_ENDO_BASIC, + BN254_XYPRIME_ENDO_BASIC, + SECP256K1_XLO_BASIC, + SECP256K1_XHI_BASIC, + SECP256K1_YLO_BASIC, + SECP256K1_YHI_BASIC, + SECP256K1_XYPRIME_BASIC, + SECP256K1_XLO_ENDO_BASIC, + SECP256K1_XHI_ENDO_BASIC, + SECP256K1_XYPRIME_ENDO_BASIC, + BLAKE_XOR_ROTATE0, + BLAKE_XOR_ROTATE0_SLICE5_MOD4, + BLAKE_XOR_ROTATE1, + BLAKE_XOR_ROTATE2, + BLAKE_XOR_ROTATE4, + PEDERSEN_29_SMALL, + PEDERSEN_28, + PEDERSEN_27, + PEDERSEN_26, + PEDERSEN_25, + PEDERSEN_24, + PEDERSEN_23, + PEDERSEN_22, + PEDERSEN_21, + PEDERSEN_20, + PEDERSEN_19, + PEDERSEN_18, PEDERSEN_17, PEDERSEN_16, PEDERSEN_15, - PEDERSEN_14, + PEDERSEN_14_SMALL, PEDERSEN_13, PEDERSEN_12, PEDERSEN_11, @@ -46,33 +82,55 @@ enum PlookupBasicTableId { PEDERSEN_2, PEDERSEN_1, PEDERSEN_0, - UINT_XOR_ROTATE0, - UINT_AND_ROTATE0, + PEDERSEN_IV_BASE, }; -enum PlookupMultiTableId { - SHA256_CH_INPUT = 0, - SHA256_CH_OUTPUT = 1, - SHA256_MAJ_INPUT = 2, - SHA256_MAJ_OUTPUT = 3, - SHA256_WITNESS_INPUT = 4, - SHA256_WITNESS_OUTPUT = 5, - AES_NORMALIZE = 6, - AES_INPUT = 7, - AES_SBOX = 8, - PEDERSEN_LEFT = 9, - PEDERSEN_RIGHT = 10, - UINT32_XOR = 11, - UINT32_AND = 12, - NUM_MULTI_TABLES = 13, +enum MultiTableId { + SHA256_CH_INPUT, + SHA256_CH_OUTPUT, + SHA256_MAJ_INPUT, + SHA256_MAJ_OUTPUT, + SHA256_WITNESS_INPUT, + SHA256_WITNESS_OUTPUT, + AES_NORMALIZE, + AES_INPUT, + AES_SBOX, + PEDERSEN_LEFT_HI, + PEDERSEN_LEFT_LO, + PEDERSEN_RIGHT_HI, + PEDERSEN_RIGHT_LO, + UINT32_XOR, + UINT32_AND, + BN254_XLO, + BN254_XHI, + BN254_YLO, + BN254_YHI, + BN254_XYPRIME, + BN254_XLO_ENDO, + BN254_XHI_ENDO, + BN254_XYPRIME_ENDO, + SECP256K1_XLO, + SECP256K1_XHI, + SECP256K1_YLO, + SECP256K1_YHI, + SECP256K1_XYPRIME, + SECP256K1_XLO_ENDO, + SECP256K1_XHI_ENDO, + SECP256K1_XYPRIME_ENDO, + BLAKE_XOR, + BLAKE_XOR_ROTATE_16, + BLAKE_XOR_ROTATE_8, + BLAKE_XOR_ROTATE_7, + PEDERSEN_IV, + NUM_MULTI_TABLES, }; -struct PlookupMultiTable { +struct MultiTable { std::vector column_1_coefficients; std::vector column_2_coefficients; std::vector column_3_coefficients; - PlookupMultiTableId id; - std::vector lookup_ids; + MultiTableId id; + std::vector lookup_ids; std::vector slice_sizes; std::vector column_1_step_sizes; std::vector column_2_step_sizes; @@ -103,10 +161,10 @@ struct PlookupMultiTable { } public: - PlookupMultiTable(const barretenberg::fr& col_1_repeated_coeff, - const barretenberg::fr& col_2_repeated_coeff, - const barretenberg::fr& col_3_repeated_coeff, - const size_t num_lookups) + MultiTable(const barretenberg::fr& col_1_repeated_coeff, + const barretenberg::fr& col_2_repeated_coeff, + const barretenberg::fr& col_3_repeated_coeff, + const size_t num_lookups) { column_1_coefficients.emplace_back(1); column_2_coefficients.emplace_back(1); @@ -119,9 +177,9 @@ struct PlookupMultiTable { } init_step_sizes(); } - PlookupMultiTable(const std::vector& col_1_coeffs, - const std::vector& col_2_coeffs, - const std::vector& col_3_coeffs) + MultiTable(const std::vector& col_1_coeffs, + const std::vector& col_2_coeffs, + const std::vector& col_3_coeffs) : column_1_coefficients(col_1_coeffs) , column_2_coefficients(col_2_coeffs) , column_3_coefficients(col_3_coeffs) @@ -129,12 +187,12 @@ struct PlookupMultiTable { init_step_sizes(); } - PlookupMultiTable(){}; - PlookupMultiTable(const PlookupMultiTable& other) = default; - PlookupMultiTable(PlookupMultiTable&& other) = default; + MultiTable(){}; + MultiTable(const MultiTable& other) = default; + MultiTable(MultiTable&& other) = default; - PlookupMultiTable& operator=(const PlookupMultiTable& other) = default; - PlookupMultiTable& operator=(PlookupMultiTable&& other) = default; + MultiTable& operator=(const MultiTable& other) = default; + MultiTable& operator=(MultiTable&& other) = default; }; // struct PlookupLargeKeyTable { @@ -153,7 +211,7 @@ struct PlookupMultiTable { // } // }; -// PlookupBasicTableId id; +// BasicTableId id; // size_t table_index; // size_t size; // bool use_twin_keys; @@ -181,7 +239,7 @@ struct PlookupMultiTable { // std::array to_sorted_list_components() const { return { key, values[0], values[0] }; } // } -// PlookupBasicTableId id; +// BasicTableId id; // size_t table_index; // size_t size; // bool use_twin_keys; @@ -198,7 +256,13 @@ struct PlookupMultiTable { // } -struct PlookupBasicTable { +/** + * @brief The structure contains the most basic table serving one function (for, example an xor table) + * + * @details You can find initialization example at ../ultra_composer.cpp#UltraComposer::initialize_precomputed_table(..) + * + */ +struct BasicTable { struct KeyEntry { std::array key{ 0, 0 }; std::array value{ barretenberg::fr(0), barretenberg::fr(0) }; @@ -217,9 +281,12 @@ struct PlookupBasicTable { } }; - PlookupBasicTableId id; + // Unique id of the table which is used to look it up, when we need its functionality. One of BasicTableId enum + BasicTableId id; size_t table_index; + // The size of the table size_t size; + // This means that we are using two inputs to look up stuff, not translate a single entry into another one. bool use_twin_keys; barretenberg::fr column_1_step_size = barretenberg::fr(0); @@ -233,11 +300,31 @@ struct PlookupBasicTable { std::array (*get_values_from_key)(const std::array); }; -struct PlookupReadData { - std::vector key_entries; +enum ColumnIdx { C1, C2, C3 }; - std::vector column_1_accumulator_values; - std::vector column_2_accumulator_values; - std::vector column_3_accumulator_values; +/** + * @brief Container type for lookup table reads. + * + * @tparam DataType: a native or stdlib field type, or the witness index type uint32_t + * + * @details We us this approach to indexing, using enums, rather than to make member variables column_i, to minimize + * code changes; both non-const and const versions are in use. + * + * The inner index, i.e., the index of each vector v in the array `columns`, could also be treated as an enum, but that + * might be messier. Note that v[0] represents a full accumulated sum, v[1] represents one step before that, + * and so on. See the documentation of the native version of get_lookup_accumulators. + * + */ +template class ReadData { + public: + ReadData() = default; + std::vector& operator[](ColumnIdx idx) { return columns[static_cast(idx)]; }; + const std::vector& operator[](ColumnIdx idx) const { return columns[static_cast(idx)]; }; + + std::vector key_entries; + + private: + std::array, 3> columns; }; -} // namespace waffle \ No newline at end of file + +} // namespace plookup \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/plookup_tables/uint.hpp b/cpp/src/aztec/plonk/composer/plookup_tables/uint.hpp index c2e97fcf0d..5efc4c9009 100644 --- a/cpp/src/aztec/plonk/composer/plookup_tables/uint.hpp +++ b/cpp/src/aztec/plonk/composer/plookup_tables/uint.hpp @@ -4,7 +4,7 @@ #include -namespace waffle { +namespace plookup { namespace uint_tables { template @@ -14,10 +14,10 @@ inline std::array get_xor_rotate_values_from_key(const std: } template -inline PlookupBasicTable generate_xor_rotate_table(PlookupBasicTableId id, const size_t table_index) +inline BasicTable generate_xor_rotate_table(BasicTableId id, const size_t table_index) { const uint64_t base = 1UL << bits_per_slice; - PlookupBasicTable table; + BasicTable table; table.id = id; table.table_index = table_index; table.size = base * base; @@ -47,10 +47,10 @@ inline std::array get_and_rotate_values_from_key(const std: } template -inline PlookupBasicTable generate_and_rotate_table(PlookupBasicTableId id, const size_t table_index) +inline BasicTable generate_and_rotate_table(BasicTableId id, const size_t table_index) { const uint64_t base = 1UL << bits_per_slice; - PlookupBasicTable table; + BasicTable table; table.id = id; table.table_index = table_index; table.size = base * base; @@ -73,11 +73,11 @@ inline PlookupBasicTable generate_and_rotate_table(PlookupBasicTableId id, const return table; } -inline PlookupMultiTable get_uint32_xor_table(const PlookupMultiTableId id = UINT32_XOR) +inline MultiTable get_uint32_xor_table(const MultiTableId id = UINT32_XOR) { const size_t num_entries = (32 + 5) / 6; const uint64_t base = 1 << 6; - PlookupMultiTable table(base, base, base, num_entries); + MultiTable table(base, base, base, num_entries); table.id = id; for (size_t i = 0; i < num_entries; ++i) { @@ -88,11 +88,11 @@ inline PlookupMultiTable get_uint32_xor_table(const PlookupMultiTableId id = UIN return table; } -inline PlookupMultiTable get_uint32_and_table(const PlookupMultiTableId id = UINT32_AND) +inline MultiTable get_uint32_and_table(const MultiTableId id = UINT32_AND) { const size_t num_entries = (32 + 5) / 6; const uint64_t base = 1 << 6; - PlookupMultiTable table(base, base, base, num_entries); + MultiTable table(base, base, base, num_entries); table.id = id; for (size_t i = 0; i < num_entries; ++i) { @@ -104,4 +104,4 @@ inline PlookupMultiTable get_uint32_and_table(const PlookupMultiTableId id = UIN } } // namespace uint_tables -} // namespace waffle \ No newline at end of file +} // namespace plookup \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/standard/compute_verification_key.cpp b/cpp/src/aztec/plonk/composer/standard/compute_verification_key.cpp deleted file mode 100644 index 136d4a7b24..0000000000 --- a/cpp/src/aztec/plonk/composer/standard/compute_verification_key.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "compute_verification_key.hpp" -#include -#include -#include -#include -#include - -namespace waffle { -namespace standard_composer { - -/** - * Compute the verification key (just precommitments of Q_1-3, Q_M, Q_C, SIGMA_1-3). - * - * @param circuit_proving_key Proving key containing all the necessary selectors. - * @param vrs CRS to use for commitment. - * - * @return Verification key with selector precommitments. - * */ -std::shared_ptr compute_verification_key(std::shared_ptr const& circuit_proving_key, - std::shared_ptr const& vrs) -{ - std::array poly_coefficients; - poly_coefficients[0] = circuit_proving_key->polynomial_cache.get("q_1").get_coefficients(); - poly_coefficients[1] = circuit_proving_key->polynomial_cache.get("q_2").get_coefficients(); - poly_coefficients[2] = circuit_proving_key->polynomial_cache.get("q_3").get_coefficients(); - poly_coefficients[3] = circuit_proving_key->polynomial_cache.get("q_m").get_coefficients(); - poly_coefficients[4] = circuit_proving_key->polynomial_cache.get("q_c").get_coefficients(); - poly_coefficients[5] = circuit_proving_key->polynomial_cache.get("sigma_1").get_coefficients(); - poly_coefficients[6] = circuit_proving_key->polynomial_cache.get("sigma_2").get_coefficients(); - poly_coefficients[7] = circuit_proving_key->polynomial_cache.get("sigma_3").get_coefficients(); - - std::vector commitments; - commitments.resize(8); - - for (size_t i = 0; i < 8; ++i) { - commitments[i] = - barretenberg::scalar_multiplication::pippenger(poly_coefficients[i], - circuit_proving_key->reference_string->get_monomials(), - circuit_proving_key->n, - circuit_proving_key->pippenger_runtime_state); - } - - auto circuit_verification_key = std::make_shared( - circuit_proving_key->n, circuit_proving_key->num_public_inputs, vrs, circuit_proving_key->composer_type); - - circuit_verification_key->constraint_selectors.insert({ "Q_1", commitments[0] }); - circuit_verification_key->constraint_selectors.insert({ "Q_2", commitments[1] }); - circuit_verification_key->constraint_selectors.insert({ "Q_3", commitments[2] }); - circuit_verification_key->constraint_selectors.insert({ "Q_M", commitments[3] }); - circuit_verification_key->constraint_selectors.insert({ "Q_C", commitments[4] }); - - circuit_verification_key->permutation_selectors.insert({ "SIGMA_1", commitments[5] }); - circuit_verification_key->permutation_selectors.insert({ "SIGMA_2", commitments[6] }); - circuit_verification_key->permutation_selectors.insert({ "SIGMA_3", commitments[7] }); - - circuit_verification_key->polynomial_manifest = PolynomialManifest(ComposerType::STANDARD); - - return circuit_verification_key; -} - -} // namespace standard_composer -} // namespace waffle \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/standard/compute_verification_key.hpp b/cpp/src/aztec/plonk/composer/standard/compute_verification_key.hpp deleted file mode 100644 index e8b075e7ec..0000000000 --- a/cpp/src/aztec/plonk/composer/standard/compute_verification_key.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once -#include - -namespace waffle { -struct verification_key; -struct proving_key; -class VerifierReferenceString; - -namespace standard_composer { - -std::shared_ptr compute_verification_key(std::shared_ptr const& circuit_proving_key, - std::shared_ptr const& vrs); - -} -} // namespace waffle \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/standard_composer.cpp b/cpp/src/aztec/plonk/composer/standard_composer.cpp index cd427c3963..abc5326964 100644 --- a/cpp/src/aztec/plonk/composer/standard_composer.cpp +++ b/cpp/src/aztec/plonk/composer/standard_composer.cpp @@ -1,10 +1,8 @@ #include "standard_composer.hpp" #include #include -#include #include #include -#include #include #include #include @@ -699,13 +697,13 @@ void StandardComposer::fix_witness(const uint32_t witness_index, const barretenb uint32_t StandardComposer::put_constant_variable(const barretenberg::fr& variable) { - if (constant_variables.count(variable) == 1) { - return constant_variables.at(variable); + if (constant_variable_indices.contains(variable)) { + return constant_variable_indices.at(variable); } else { uint32_t variable_index = add_variable(variable); fix_witness(variable_index, variable); - constant_variables.insert({ variable, variable_index }); + constant_variable_indices.insert({ variable, variable_index }); return variable_index; } } @@ -743,6 +741,7 @@ std::shared_ptr StandardComposer::compute_proving_key() circuit_proving_key->recursive_proof_public_input_indices = std::vector(recursive_proof_public_input_indices.begin(), recursive_proof_public_input_indices.end()); + // What does this line do exactly? circuit_proving_key->contains_recursive_proof = contains_recursive_proof; return circuit_proving_key; } @@ -761,7 +760,7 @@ std::shared_ptr StandardComposer::compute_verification_key() } circuit_verification_key = - waffle::standard_composer::compute_verification_key(circuit_proving_key, crs_factory_->get_verifier_crs()); + ComposerBase::compute_verification_key_base(circuit_proving_key, crs_factory_->get_verifier_crs()); circuit_verification_key->composer_type = type; circuit_verification_key->recursive_proof_public_input_indices = std::vector(recursive_proof_public_input_indices.begin(), recursive_proof_public_input_indices.end()); @@ -870,9 +869,8 @@ Prover StandardComposer::create_prover() void StandardComposer::assert_equal_constant(uint32_t const a_idx, fr const& b, std::string const& msg) { - if (variables[a_idx] != b && !failed) { - failed = true; - err = msg; + if (variables[a_idx] != b && !failed()) { + failure(msg); } auto b_idx = put_constant_variable(b); assert_equal(a_idx, b_idx, msg); diff --git a/cpp/src/aztec/plonk/composer/standard_composer.hpp b/cpp/src/aztec/plonk/composer/standard_composer.hpp index ce2f46c99e..6a8f745634 100644 --- a/cpp/src/aztec/plonk/composer/standard_composer.hpp +++ b/cpp/src/aztec/plonk/composer/standard_composer.hpp @@ -1,17 +1,13 @@ #pragma once #include "composer_base.hpp" -#include #include +#include +#include "../proof_system/types/polynomial_manifest.hpp" + namespace waffle { -enum StandardSelectors { - QM = 0, - QC = 1, - Q1 = 2, - Q2 = 3, - Q3 = 4, -}; +enum StandardSelectors { QM, QC, Q1, Q2, Q3, NUM }; -inline std::vector standard_sel_props() +inline std::vector standard_selector_properties() { std::vector result{ { "q_m", false }, { "q_c", false }, { "q_1", false }, { "q_2", false }, { "q_3", false }, @@ -22,10 +18,11 @@ inline std::vector standard_sel_props() class StandardComposer : public ComposerBase { public: static constexpr ComposerType type = ComposerType::STANDARD; + static constexpr MerkleHashType merkle_hash_type = MerkleHashType::FIXED_BASE_PEDERSEN; static constexpr size_t UINT_LOG2_BASE = 2; StandardComposer(const size_t size_hint = 0) - : ComposerBase(5, size_hint, standard_sel_props()) + : ComposerBase(StandardSelectors::NUM, size_hint, standard_selector_properties()) { w_l.reserve(size_hint); w_r.reserve(size_hint); @@ -33,10 +30,10 @@ class StandardComposer : public ComposerBase { zero_idx = put_constant_variable(barretenberg::fr::zero()); }; - StandardComposer(const size_t selector_num, + StandardComposer(const size_t num_selectors, const size_t size_hint, const std::vector selector_properties) - : ComposerBase(selector_num, size_hint, selector_properties) + : ComposerBase(num_selectors, size_hint, selector_properties) { w_l.reserve(size_hint); w_r.reserve(size_hint); @@ -49,7 +46,7 @@ class StandardComposer : public ComposerBase { size_hint){}; StandardComposer(std::shared_ptr const& crs_factory, const size_t size_hint = 0) - : ComposerBase(crs_factory, 5, size_hint, standard_sel_props()) + : ComposerBase(crs_factory, StandardSelectors::NUM, size_hint, standard_selector_properties()) { w_l.reserve(size_hint); w_r.reserve(size_hint); @@ -59,7 +56,7 @@ class StandardComposer : public ComposerBase { } StandardComposer(std::unique_ptr&& crs_factory, const size_t size_hint = 0) - : ComposerBase(std::move(crs_factory), 5, size_hint, standard_sel_props()) + : ComposerBase(std::move(crs_factory), StandardSelectors::NUM, size_hint, standard_selector_properties()) { w_l.reserve(size_hint); w_r.reserve(size_hint); @@ -70,7 +67,7 @@ class StandardComposer : public ComposerBase { StandardComposer(std::shared_ptr const& p_key, std::shared_ptr const& v_key, size_t size_hint = 0) - : ComposerBase(p_key, v_key, 5, size_hint, standard_sel_props()) + : ComposerBase(p_key, v_key, StandardSelectors::NUM, size_hint, standard_selector_properties()) { w_l.reserve(size_hint); w_r.reserve(size_hint); @@ -120,14 +117,17 @@ class StandardComposer : public ComposerBase { const size_t num_bits, std::string const& msg = "create_range_constraint"); - std::vector create_range_constraint(const uint32_t witness_index, - const size_t num_bits, - std::string const& msg = "create_range_constraint"); + void create_range_constraint(const uint32_t variable_index, + const size_t num_bits, + std::string const& msg = "create_range_constraint") + { + decompose_into_base4_accumulators(variable_index, num_bits, msg); + } + void add_recursive_proof(const std::vector& proof_output_witness_indices) { if (contains_recursive_proof) { - failed = true; - err = "added recursive proof when one already exists"; + failure("added recursive proof when one already exists"); } contains_recursive_proof = true; @@ -151,9 +151,9 @@ class StandardComposer : public ComposerBase { size_t get_num_constant_gates() const override { return 0; } - // these are variables that we have used a gate on, to enforce that they are - // equal to a defined value - std::map constant_variables; + // These are variables that we have used a gate on, to enforce that they are + // equal to a defined value. + std::map constant_variable_indices; /** * Create a manifest, which specifies proof rounds, elements and who supplies them. @@ -169,9 +169,12 @@ class StandardComposer : public ComposerBase { constexpr size_t fr_size = 32; const size_t public_input_size = fr_size * num_public_inputs; const transcript::Manifest output = transcript::Manifest( + { transcript::Manifest::RoundManifest( { { "circuit_size", 4, true }, { "public_input_size", 4, true } }, "init", 1), + transcript::Manifest::RoundManifest({}, "eta", 0), + transcript::Manifest::RoundManifest( { { "public_inputs", public_input_size, false }, @@ -184,6 +187,7 @@ class StandardComposer : public ComposerBase { transcript::Manifest::RoundManifest({ { "Z_PERM", g1_size, false } }, "alpha", 1), transcript::Manifest::RoundManifest( { { "T_1", g1_size, false }, { "T_2", g1_size, false }, { "T_3", g1_size, false } }, "z", 1), + transcript::Manifest::RoundManifest( { { "w_1", fr_size, false, 0 }, @@ -194,8 +198,9 @@ class StandardComposer : public ComposerBase { { "z_perm_omega", fr_size, false, -1 }, }, "nu", - 6, + STANDARD_UNROLLED_MANIFEST_SIZE - 6, true), + transcript::Manifest::RoundManifest( { { "PI_Z", g1_size, false }, { "PI_Z_OMEGA", g1_size, false } }, "separator", 1) }); return output; @@ -208,9 +213,12 @@ class StandardComposer : public ComposerBase { constexpr size_t fr_size = 32; const size_t public_input_size = fr_size * num_public_inputs; const transcript::Manifest output = transcript::Manifest( + { transcript::Manifest::RoundManifest( { { "circuit_size", 4, true }, { "public_input_size", 4, true } }, "init", 1), + transcript::Manifest::RoundManifest({}, "eta", 0), + transcript::Manifest::RoundManifest( { { "public_inputs", public_input_size, false }, @@ -223,6 +231,7 @@ class StandardComposer : public ComposerBase { transcript::Manifest::RoundManifest({ { "Z_PERM", g1_size, false } }, "alpha", 1), transcript::Manifest::RoundManifest( { { "T_1", g1_size, false }, { "T_2", g1_size, false }, { "T_3", g1_size, false } }, "z", 1), + transcript::Manifest::RoundManifest( { { "t", fr_size, true, -1 }, @@ -241,10 +250,12 @@ class StandardComposer : public ComposerBase { { "z_perm_omega", fr_size, false, -1 }, }, "nu", - 12, + STANDARD_UNROLLED_MANIFEST_SIZE, true), + transcript::Manifest::RoundManifest( { { "PI_Z", g1_size, false }, { "PI_Z_OMEGA", g1_size, false } }, "separator", 1) }); + return output; } diff --git a/cpp/src/aztec/plonk/composer/turbo/compute_verification_key.cpp b/cpp/src/aztec/plonk/composer/turbo/compute_verification_key.cpp deleted file mode 100644 index 22f095d234..0000000000 --- a/cpp/src/aztec/plonk/composer/turbo/compute_verification_key.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "compute_verification_key.hpp" -#include -#include -#include -#include -#include - -namespace waffle { -namespace turbo_composer { - -std::shared_ptr compute_verification_key(std::shared_ptr const& circuit_proving_key, - std::shared_ptr const& vrs) -{ - std::array poly_coefficients; - poly_coefficients[0] = circuit_proving_key->polynomial_cache.get("q_1").get_coefficients(); - poly_coefficients[1] = circuit_proving_key->polynomial_cache.get("q_2").get_coefficients(); - poly_coefficients[2] = circuit_proving_key->polynomial_cache.get("q_3").get_coefficients(); - poly_coefficients[3] = circuit_proving_key->polynomial_cache.get("q_4").get_coefficients(); - poly_coefficients[4] = circuit_proving_key->polynomial_cache.get("q_5").get_coefficients(); - poly_coefficients[5] = circuit_proving_key->polynomial_cache.get("q_m").get_coefficients(); - poly_coefficients[6] = circuit_proving_key->polynomial_cache.get("q_c").get_coefficients(); - poly_coefficients[7] = circuit_proving_key->polynomial_cache.get("q_arith").get_coefficients(); - poly_coefficients[8] = circuit_proving_key->polynomial_cache.get("q_ecc_1").get_coefficients(); - poly_coefficients[9] = circuit_proving_key->polynomial_cache.get("q_range").get_coefficients(); - poly_coefficients[10] = circuit_proving_key->polynomial_cache.get("q_logic").get_coefficients(); - poly_coefficients[11] = circuit_proving_key->polynomial_cache.get("sigma_1").get_coefficients(); - poly_coefficients[12] = circuit_proving_key->polynomial_cache.get("sigma_2").get_coefficients(); - poly_coefficients[13] = circuit_proving_key->polynomial_cache.get("sigma_3").get_coefficients(); - poly_coefficients[14] = circuit_proving_key->polynomial_cache.get("sigma_4").get_coefficients(); - - std::vector commitments; - commitments.resize(15); - - for (size_t i = 0; i < 15; ++i) { - commitments[i] = - barretenberg::scalar_multiplication::pippenger(poly_coefficients[i], - circuit_proving_key->reference_string->get_monomials(), - circuit_proving_key->n, - circuit_proving_key->pippenger_runtime_state); - } - - auto circuit_verification_key = std::make_shared( - circuit_proving_key->n, circuit_proving_key->num_public_inputs, vrs, circuit_proving_key->composer_type); - - circuit_verification_key->constraint_selectors.insert({ "Q_1", commitments[0] }); - circuit_verification_key->constraint_selectors.insert({ "Q_2", commitments[1] }); - circuit_verification_key->constraint_selectors.insert({ "Q_3", commitments[2] }); - circuit_verification_key->constraint_selectors.insert({ "Q_4", commitments[3] }); - circuit_verification_key->constraint_selectors.insert({ "Q_5", commitments[4] }); - circuit_verification_key->constraint_selectors.insert({ "Q_M", commitments[5] }); - circuit_verification_key->constraint_selectors.insert({ "Q_C", commitments[6] }); - circuit_verification_key->constraint_selectors.insert({ "Q_ARITHMETIC_SELECTOR", commitments[7] }); - circuit_verification_key->constraint_selectors.insert({ "Q_FIXED_BASE_SELECTOR", commitments[8] }); - circuit_verification_key->constraint_selectors.insert({ "Q_RANGE_SELECTOR", commitments[9] }); - circuit_verification_key->constraint_selectors.insert({ "Q_LOGIC_SELECTOR", commitments[10] }); - - circuit_verification_key->permutation_selectors.insert({ "SIGMA_1", commitments[11] }); - circuit_verification_key->permutation_selectors.insert({ "SIGMA_2", commitments[12] }); - circuit_verification_key->permutation_selectors.insert({ "SIGMA_3", commitments[13] }); - circuit_verification_key->permutation_selectors.insert({ "SIGMA_4", commitments[14] }); - - return circuit_verification_key; -} - -} // namespace turbo_composer -} // namespace waffle \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/turbo/compute_verification_key.hpp b/cpp/src/aztec/plonk/composer/turbo/compute_verification_key.hpp deleted file mode 100644 index 0886487ee1..0000000000 --- a/cpp/src/aztec/plonk/composer/turbo/compute_verification_key.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once -#include - -namespace waffle { -struct verification_key; -struct proving_key; -class VerifierReferenceString; - -namespace turbo_composer { - -std::shared_ptr compute_verification_key(std::shared_ptr const& circuit_proving_key, - std::shared_ptr const& vrs); - -} -} // namespace waffle \ No newline at end of file diff --git a/cpp/src/aztec/plonk/composer/turbo_composer.cpp b/cpp/src/aztec/plonk/composer/turbo_composer.cpp index 874d24d4fd..077fd80cb9 100644 --- a/cpp/src/aztec/plonk/composer/turbo_composer.cpp +++ b/cpp/src/aztec/plonk/composer/turbo_composer.cpp @@ -1,17 +1,15 @@ #include "turbo_composer.hpp" #include #include -#include #include #include -#include +#include #include #include -#include #include -#include "../proof_system/types/polynomial_manifest.hpp" #include "../proof_system/widgets/transition_widgets/transition_widget.hpp" #include "../proof_system/widgets/transition_widgets/turbo_arithmetic_widget.hpp" +#include using namespace barretenberg; @@ -26,16 +24,16 @@ namespace waffle { auto& q_4 = selectors[TurboSelectors::Q4]; \ auto& q_5 = selectors[TurboSelectors::Q5]; \ auto& q_arith = selectors[TurboSelectors::QARITH]; \ - auto& q_ecc_1 = selectors[TurboSelectors::QECC_1]; \ + auto& q_fixed_base = selectors[TurboSelectors::QFIXED]; \ auto& q_range = selectors[TurboSelectors::QRANGE]; \ auto& q_logic = selectors[TurboSelectors::QLOGIC]; std::vector turbo_sel_props() { const std::vector result{ - { "q_m", false }, { "q_c", false }, { "q_1", false }, { "q_2", false }, - { "q_3", false }, { "q_4", false }, { "q_5", false }, { "q_arith", false }, - { "q_ecc_1", false }, { "q_range", false }, { "q_logic", false }, + { "q_m", false }, { "q_c", false }, { "q_1", false }, { "q_2", false }, + { "q_3", false }, { "q_4", false }, { "q_5", false }, { "q_arith", false }, + { "q_fixed_base", false }, { "q_range", false }, { "q_logic", false }, }; return result; } @@ -67,7 +65,7 @@ TurboComposer::TurboComposer(std::string const& crs_path, const size_t size_hint * vectors during initialization. * */ TurboComposer::TurboComposer(std::shared_ptr const& crs_factory, const size_t size_hint) - : ComposerBase(crs_factory, 11, size_hint, turbo_sel_props()) + : ComposerBase(crs_factory, TurboSelectors::NUM, size_hint, turbo_sel_props()) { w_l.reserve(size_hint); w_r.reserve(size_hint); @@ -80,7 +78,7 @@ TurboComposer::TurboComposer(std::shared_ptr const& crs_ TurboComposer::TurboComposer(std::shared_ptr const& p_key, std::shared_ptr const& v_key, size_t size_hint) - : ComposerBase(p_key, v_key, 11, size_hint, turbo_sel_props()) + : ComposerBase(p_key, v_key, TurboSelectors::NUM, size_hint, turbo_sel_props()) { w_l.reserve(size_hint); w_r.reserve(size_hint); @@ -91,7 +89,7 @@ TurboComposer::TurboComposer(std::shared_ptr const& p_key, /** * Create an addition gate. - * The q_m, q_4, q_5, q_ecc_1, q_range, q_logic are zero. + * The q_m, q_4, q_5, q_fixed_base, q_range, q_logic are zero. * q_artith is one. w_4 is set to 0-variable index. * Other parameters are received from the argument. * @@ -115,7 +113,7 @@ void TurboComposer::create_add_gate(const add_triple& in) q_arith.emplace_back(fr::one()); q_4.emplace_back(fr::zero()); q_5.emplace_back(fr::zero()); - q_ecc_1.emplace_back(fr::zero()); + q_fixed_base.emplace_back(fr::zero()); q_range.emplace_back(fr::zero()); q_logic.emplace_back(fr::zero()); ++n; @@ -123,7 +121,7 @@ void TurboComposer::create_add_gate(const add_triple& in) /** * Create an addition gate that adds 4 variables. - * The q_m, q_5, q_ecc_1, q_range, q_logic are zero. + * The q_m, q_5, q_fixed_base, q_range, q_logic are zero. * q_arith is one. * Other parameters are received from the argument. * @@ -147,7 +145,7 @@ void TurboComposer::create_big_add_gate(const add_quad& in) q_arith.emplace_back(fr::one()); q_4.emplace_back(in.d_scaling); q_5.emplace_back(fr::zero()); - q_ecc_1.emplace_back(fr::zero()); + q_fixed_base.emplace_back(fr::zero()); q_range.emplace_back(fr::zero()); q_logic.emplace_back(fr::zero()); ++n; @@ -155,7 +153,7 @@ void TurboComposer::create_big_add_gate(const add_quad& in) /** * @brief Create an addition gate that adds 4 variables with bit extraction. - * The q_m, q_5, q_ecc_1, q_range, q_logic are zero. + * The q_m, q_5, q_fixed_base, q_range, q_logic are zero. * q_arith is 2, so an additional constraint is imposed in the nonlinear terms. * Other parameters are received from the argument. * @@ -167,7 +165,7 @@ void TurboComposer::create_big_add_gate(const add_quad& in) * + 6 * (high bit of c - 4d) == 0. * @warning This function assumes that c - 4d lies in the set {0, 1, 2, 3}. The circuit writer should take care to * ensure this assumption is backed by a constraint (e.g., c and d could be accumulators produced using the TurboPLONK - * function `decompose_into_base_4_accumulators`). + * function `decompose_into_base4_accumulators`). * */ void TurboComposer::create_big_add_gate_with_bit_extraction(const add_quad& in) { @@ -186,7 +184,7 @@ void TurboComposer::create_big_add_gate_with_bit_extraction(const add_quad& in) q_arith.emplace_back(fr::one() + fr::one()); q_4.emplace_back(in.d_scaling); q_5.emplace_back(fr::zero()); - q_ecc_1.emplace_back(fr::zero()); + q_fixed_base.emplace_back(fr::zero()); q_range.emplace_back(fr::zero()); q_logic.emplace_back(fr::zero()); ++n; @@ -209,7 +207,7 @@ void TurboComposer::create_big_mul_gate(const mul_quad& in) q_arith.emplace_back(fr::one()); q_4.emplace_back(in.d_scaling); q_5.emplace_back(fr::zero()); - q_ecc_1.emplace_back(fr::zero()); + q_fixed_base.emplace_back(fr::zero()); q_range.emplace_back(fr::zero()); q_logic.emplace_back(fr::zero()); ++n; @@ -249,7 +247,7 @@ void TurboComposer::create_balanced_add_gate(const add_quad& in) q_arith.emplace_back(fr::one()); q_4.emplace_back(in.d_scaling); q_5.emplace_back(fr::one()); - q_ecc_1.emplace_back(fr::zero()); + q_fixed_base.emplace_back(fr::zero()); q_range.emplace_back(fr::zero()); q_logic.emplace_back(fr::zero()); ++n; @@ -258,7 +256,7 @@ void TurboComposer::create_balanced_add_gate(const add_quad& in) /** * Create multiplication gate. * w_4 is set to the index of zero variable. - * q_1, q_2, q_4, q_4, q_ecc_1, q_range and q_logic are set to zero. + * q_1, q_2, q_4, q_4, q_fixed_base, q_range and q_logic are set to zero. * q_arith is set to 1. * * @param in Contains the values for w_l, w_r, w_o, @@ -281,7 +279,7 @@ void TurboComposer::create_mul_gate(const mul_triple& in) q_arith.emplace_back(fr::one()); q_4.emplace_back(fr::zero()); q_5.emplace_back(fr::zero()); - q_ecc_1.emplace_back(fr::zero()); + q_fixed_base.emplace_back(fr::zero()); q_range.emplace_back(fr::zero()); q_logic.emplace_back(fr::zero()); ++n; @@ -306,7 +304,7 @@ void TurboComposer::create_bool_gate(const uint32_t variable_index) q_arith.emplace_back(fr::one()); q_4.emplace_back(fr::zero()); q_5.emplace_back(fr::zero()); - q_ecc_1.emplace_back(fr::zero()); + q_fixed_base.emplace_back(fr::zero()); q_range.emplace_back(fr::zero()); q_m.emplace_back(fr::one()); @@ -321,7 +319,7 @@ void TurboComposer::create_bool_gate(const uint32_t variable_index) /** * Create poly gate as in standard composer. * w_4 is set to zero variable. - * q_range, q_logic, q_4, q_5, q_ecc_1 are set to 0. + * q_range, q_logic, q_4, q_5, q_fixed_base are set to 0. * q_arith is set to 1. * * @param in Contains the values for @@ -347,7 +345,7 @@ void TurboComposer::create_poly_gate(const poly_triple& in) q_arith.emplace_back(fr::one()); q_4.emplace_back(fr::zero()); q_5.emplace_back(fr::zero()); - q_ecc_1.emplace_back(fr::zero()); + q_fixed_base.emplace_back(fr::zero()); ++n; } @@ -377,7 +375,7 @@ void TurboComposer::create_fixed_group_add_gate(const fixed_group_add_quad& in) q_1.emplace_back(in.q_x_1); q_2.emplace_back(in.q_x_2); q_3.emplace_back(in.q_y_1); - q_ecc_1.emplace_back(in.q_y_2); + q_fixed_base.emplace_back(in.q_y_2); ++n; } @@ -409,7 +407,7 @@ void TurboComposer::create_fixed_group_add_gate_with_init(const fixed_group_add_ q_1.emplace_back(in.q_x_1); q_2.emplace_back(in.q_x_2); q_3.emplace_back(in.q_y_1); - q_ecc_1.emplace_back(in.q_y_2); + q_fixed_base.emplace_back(in.q_y_2); ++n; } @@ -441,14 +439,14 @@ void TurboComposer::fix_witness(const uint32_t witness_index, const barretenberg q_arith.emplace_back(fr::one()); q_4.emplace_back(fr::zero()); q_5.emplace_back(fr::zero()); - q_ecc_1.emplace_back(fr::zero()); + q_fixed_base.emplace_back(fr::zero()); q_range.emplace_back(fr::zero()); q_logic.emplace_back(fr::zero()); ++n; } /** - * Create a constrain placing the witness in 2^{num_bits} range. + * Create a constraint placing the witness in 2^{num_bits} range. * * @param witness_index The index of the witness variable to constrain. * @param num_bits Constraint size. @@ -518,38 +516,59 @@ std::vector TurboComposer::decompose_into_base4_accumulators(const uin * We need to start our raster scan at zero, so we simplify matters and just force the first value * to be zero. * - * The output will be in the 4th column of an otherwise unused row. Assuming this row can - * be used for a width-3 standard gate, the total number of gates for an n-bit range constraint - * is (n / 8) gates + * We will prepend 0 quads to our sequence of accumulator values so that the final accumulator value (equal to the + * witness value if the range constraint holds) will be in the 4th column of an otherwise unused row. More + * explicitly, in general (num_bits > 0), a_0 will be placed in column: + * - C if num_bits = 7, 8 mod 8 + * - D if num_bits = 1, 2 mod 8 + * - A if num_bits = 3, 4 mod 8 + * - B if num_bits = 5, 6 mod 8 + * + * Examples: + * 7,8-bit 9,10-bit 11,12-bit 13,14-bit + * +-----+-----+-----+-----+ +-----+-----+-----+-----+ +-----+-----+-----+-----+ +-----+-----+-----+-----+ + * | A | B | C | D | | A | B | C | D | | A | B | C | D | | A | B | C | D | + * +-----+-----+-----+-----+ +-----+-----+-----+-----+ +-----+-----+-----+-----+ +-----+-----+-----+-----+ + * | a2 | a1 | a0 | 0 | | 0 | 0 | 0 | 0 | | a0 | 0 | 0 | 0 | | a1 | a0 | 0 | 0 | + * | 0 | 0 | 0 | a3 | | a3 | a2 | a1 | a0 | | a4 | a3 | a2 | a1 | | a5 | a4 | a3 | a2 | + * | --- | --- | --- | --- | | 0 | 0 | 0 | a4 | | 0 | 0 | 0 | a5 | | 0 | 0 | 0 | a6 | + * +-----+-----+-----+-----+ +-----+-----+-----+-----+ +-----+-----+-----+-----+ +-----+-----+-----+-----+ + * * **/ const uint256_t witness_value(get_variable(witness_index)); - if (witness_value.get_msb() > num_bits && !failed) { - failed = true; - err = msg; + if (witness_value.get_msb() >= num_bits && !failed()) { + failure(msg); } - // one gate accumulates 4 quads, or 8 bits. - // # gates = (bits / 8) + /* num_quad_gates is the minimum number of gates needed to record num_bits-many bits in a table, putting two-bits (a + * quad) at each position. Since our table has width 4, we can fit 8 bits on a row, hence num_quad_gates is + * num_bits/8 when num_bits is a multiple of 8, and otherwise it is 1 + (num_bits/8). Because we will always pre-pad + * with 0 quads to ensure that the final accumulator is in the fourth column, num_quad_gates is also equal to one + * less than the total number of rows that will be used to record the accumulator values. */ size_t num_quad_gates = (num_bits >> 3); - num_quad_gates = (num_quad_gates << 3 == num_bits) ? num_quad_gates : num_quad_gates + 1; - // hmm std::vector* wires[4]{ &w_4, &w_o, &w_r, &w_l }; + // num_quads = the number of accumulators used in the table, not including the output row. const size_t num_quads = (num_quad_gates << 2); + // (num_quads << 1) is the number of bits in the non-output row, including 0 quads. + // ((num_quads << 1) - num_bits) >> 1 is the number of padding 0 quads. const size_t forced_zero_threshold = 1 + (((num_quads << 1) - num_bits) >> 1); - std::vector accumulators; - fr accumulator = fr::zero(); + std::vector accumulators; + fr accumulator(0); uint32_t most_significant_segment = 0; + // iterate through entries of all but final row for (size_t i = 0; i < num_quads + 1; ++i) { uint32_t accumulator_index; + // prepend padding 0 quads if (i < forced_zero_threshold) { accumulator_index = zero_idx; } else { + // accumulate quad const size_t bit_index = (num_quads - i) << 1; const uint64_t quad = static_cast(witness_value.get_bit(bit_index)) + 2ULL * static_cast(witness_value.get_bit(bit_index + 1)); @@ -562,45 +581,43 @@ std::vector TurboComposer::decompose_into_base4_accumulators(const uin accumulators.emplace_back(accumulator_index); if (i == forced_zero_threshold) { + // mark this to constrain top bit to 0 in case num_bits is odd most_significant_segment = accumulator_index; } } - // hmmmm (*(wires + (i & 3)))->emplace_back(accumulator_index); } - size_t used_gates = (num_quads + 1) / 4; - - // TODO: handle partially used gates. For now just set them to be zero - if (used_gates * 4 != (num_quads + 1)) { - ++used_gates; - } + // we use one additional gate to record the final accumulator value. + size_t used_gates = 1 + num_quad_gates; for (size_t i = 0; i < used_gates; ++i) { - q_m.emplace_back(fr::zero()); - q_1.emplace_back(fr::zero()); - q_2.emplace_back(fr::zero()); - q_3.emplace_back(fr::zero()); - q_c.emplace_back(fr::zero()); - q_arith.emplace_back(fr::zero()); - q_4.emplace_back(fr::zero()); - q_5.emplace_back(fr::zero()); - q_ecc_1.emplace_back(fr::zero()); - q_logic.emplace_back(fr::zero()); - q_range.emplace_back(fr::one()); + q_m.emplace_back(0); + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + q_4.emplace_back(0); + q_5.emplace_back(0); + q_fixed_base.emplace_back(0); + q_logic.emplace_back(0); + q_range.emplace_back(1); } - q_range[q_range.size() - 1] = fr::zero(); - + // switch off range widget for final row; fill wire values not in use with zeros + q_range[q_range.size() - 1] = 0; w_l.emplace_back(zero_idx); w_r.emplace_back(zero_idx); w_o.emplace_back(zero_idx); assert_equal(witness_index, accumulators[accumulators.size() - 1], msg); + accumulators[accumulators.size() - 1] = witness_index; n += used_gates; + // constrain top bit of top quad to zero in case num_bits is odd if ((num_bits & 1ULL) == 1ULL) { create_bool_gate(most_significant_segment); } @@ -777,7 +794,7 @@ waffle::accumulator_triple TurboComposer::create_logic_constraint(const uint32_t q_arith.emplace_back(fr::zero()); q_4.emplace_back(fr::zero()); q_5.emplace_back(fr::zero()); - q_ecc_1.emplace_back(fr::zero()); + q_fixed_base.emplace_back(fr::zero()); q_range.emplace_back(fr::zero()); if (is_xor_gate) { q_c.emplace_back(fr::neg_one()); @@ -820,12 +837,12 @@ waffle::accumulator_triple TurboComposer::create_xor_constraint(const uint32_t a uint32_t TurboComposer::put_constant_variable(const barretenberg::fr& variable) { - if (constant_variables.count(variable) == 1) { - return constant_variables.at(variable); + if (constant_variable_indices.contains(variable)) { + return constant_variable_indices.at(variable); } else { uint32_t variable_index = add_variable(variable); fix_witness(variable_index, variable); - constant_variables.insert({ variable, variable_index }); + constant_variable_indices.insert({ variable, variable_index }); return variable_index; } } @@ -962,7 +979,7 @@ std::shared_ptr TurboComposer::compute_verification_key() } circuit_verification_key = - turbo_composer::compute_verification_key(circuit_proving_key, crs_factory_->get_verifier_crs()); + ComposerBase::compute_verification_key_base(circuit_proving_key, crs_factory_->get_verifier_crs()); circuit_verification_key->composer_type = type; circuit_verification_key->recursive_proof_public_input_indices = std::vector(recursive_proof_public_input_indices.begin(), recursive_proof_public_input_indices.end()); diff --git a/cpp/src/aztec/plonk/composer/turbo_composer.hpp b/cpp/src/aztec/plonk/composer/turbo_composer.hpp index 8a453f2cee..d5b8e43fc2 100644 --- a/cpp/src/aztec/plonk/composer/turbo_composer.hpp +++ b/cpp/src/aztec/plonk/composer/turbo_composer.hpp @@ -1,24 +1,14 @@ #pragma once #include "composer_base.hpp" +#include "../proof_system/types/polynomial_manifest.hpp" namespace waffle { class TurboComposer : public ComposerBase { public: static constexpr ComposerType type = ComposerType::TURBO; + static constexpr MerkleHashType merkle_hash_type = MerkleHashType::FIXED_BASE_PEDERSEN; static constexpr size_t UINT_LOG2_BASE = 2; - enum TurboSelectors { - QM = 0, - QC = 1, - Q1 = 2, - Q2 = 3, - Q3 = 4, - Q4 = 5, - Q5 = 6, - QARITH = 7, - QECC_1 = 8, - QRANGE = 9, - QLOGIC = 10, - }; + enum TurboSelectors { QM, QC, Q1, Q2, Q3, Q4, Q5, QARITH, QFIXED, QRANGE, QLOGIC, NUM }; TurboComposer(); TurboComposer(std::string const& crs_path, const size_t size_hint = 0); @@ -60,8 +50,7 @@ class TurboComposer : public ComposerBase { void add_recursive_proof(const std::vector& proof_output_witness_indices) { if (contains_recursive_proof) { - failed = true; - err = "added recursive proof when one already exists"; + failure("added recursive proof when one already exists"); } contains_recursive_proof = true; @@ -75,7 +64,13 @@ class TurboComposer : public ComposerBase { std::vector decompose_into_base4_accumulators(const uint32_t witness_index, const size_t num_bits, - std::string const& msg = "create_range_contraint"); + std::string const& msg); + + void create_range_constraint(const uint32_t variable_index, const size_t num_bits, std::string const& msg) + { + decompose_into_base4_accumulators(variable_index, num_bits, msg); + } + accumulator_triple create_logic_constraint(const uint32_t a, const uint32_t b, const size_t num_bits, @@ -91,9 +86,8 @@ class TurboComposer : public ComposerBase { const barretenberg::fr& b, std::string const& msg = "assert_equal_constant") { - if (variables[a_idx] != b && !failed) { - failed = true; - err = msg; + if (variables[a_idx] != b && !failed()) { + failure(msg); } auto b_idx = put_constant_variable(b); assert_equal(a_idx, b_idx, msg); @@ -109,7 +103,7 @@ class TurboComposer : public ComposerBase { } // these are variables that we have used a gate on, to enforce that they are equal to a defined value - std::map constant_variables; + std::map constant_variable_indices; static transcript::Manifest create_manifest(const size_t num_public_inputs) { @@ -120,7 +114,9 @@ class TurboComposer : public ComposerBase { const transcript::Manifest output = transcript::Manifest( { transcript::Manifest::RoundManifest( { { "circuit_size", 4, true }, { "public_input_size", 4, true } }, "init", 1), + transcript::Manifest::RoundManifest({}, "eta", 0), + transcript::Manifest::RoundManifest( { { "public_inputs", public_input_size, false }, @@ -141,6 +137,7 @@ class TurboComposer : public ComposerBase { }, "z", 1), + transcript::Manifest::RoundManifest( { { "w_1", fr_size, false, 0 }, @@ -151,7 +148,7 @@ class TurboComposer : public ComposerBase { { "sigma_2", fr_size, false, 5 }, { "sigma_3", fr_size, false, 6 }, { "q_arith", fr_size, false, 7 }, - { "q_ecc_1", fr_size, false, 8 }, + { "q_fixed_base", fr_size, false, 8 }, { "q_c", fr_size, false, 9 }, { "z_perm_omega", fr_size, false, -1 }, { "w_1_omega", fr_size, false, 0 }, @@ -160,10 +157,12 @@ class TurboComposer : public ComposerBase { { "w_4_omega", fr_size, false, 3 }, }, "nu", - 11, + TURBO_UNROLLED_MANIFEST_SIZE - 10, true), + transcript::Manifest::RoundManifest( { { "PI_Z", g1_size, false }, { "PI_Z_OMEGA", g1_size, false } }, "separator", 1) }); + return output; } @@ -176,7 +175,9 @@ class TurboComposer : public ComposerBase { const transcript::Manifest output = transcript::Manifest( { transcript::Manifest::RoundManifest( { { "circuit_size", 4, true }, { "public_input_size", 4, true } }, "init", 1), + transcript::Manifest::RoundManifest({}, "eta", 0), + transcript::Manifest::RoundManifest( { { "public_inputs", public_input_size, false }, @@ -197,6 +198,7 @@ class TurboComposer : public ComposerBase { }, "z", 1), + transcript::Manifest::RoundManifest( { { "t", fr_size, true, -1 }, { "w_1", fr_size, false, 0 }, @@ -208,16 +210,18 @@ class TurboComposer : public ComposerBase { { "q_4", fr_size, false, 11 }, { "q_5", fr_size, false, 12 }, { "q_m", fr_size, false, 13 }, { "q_c", fr_size, false, 14 }, { "q_arith", fr_size, false, 15 }, { "q_logic", fr_size, false, 16 }, - { "q_range", fr_size, false, 17 }, { "q_ecc_1", fr_size, false, 18 }, + { "q_range", fr_size, false, 17 }, { "q_fixed_base", fr_size, false, 18 }, { "z_perm", fr_size, false, 19 }, { "z_perm_omega", fr_size, false, 19 }, { "w_1_omega", fr_size, false, 0 }, { "w_2_omega", fr_size, false, 1 }, { "w_3_omega", fr_size, false, 2 }, { "w_4_omega", fr_size, false, 3 }, }, "nu", - 20, + TURBO_UNROLLED_MANIFEST_SIZE, true), + transcript::Manifest::RoundManifest( { { "PI_Z", g1_size, false }, { "PI_Z_OMEGA", g1_size, false } }, "separator", 3) }); + return output; } }; @@ -264,14 +268,14 @@ class CheckGetter { return composer.selectors[TurboComposer::TurboSelectors::QM][actual_index]; case PolynomialIndex::Q_C: return composer.selectors[TurboComposer::TurboSelectors::QC][actual_index]; - case PolynomialIndex::Q_ARITHMETIC_SELECTOR: + case PolynomialIndex::Q_ARITHMETIC: return composer.selectors[TurboComposer::TurboSelectors::QARITH][actual_index]; - case PolynomialIndex::Q_LOGIC_SELECTOR: + case PolynomialIndex::Q_LOGIC: return composer.selectors[TurboComposer::TurboSelectors::QLOGIC][actual_index]; - case PolynomialIndex::Q_RANGE_SELECTOR: + case PolynomialIndex::Q_RANGE: return composer.selectors[TurboComposer::TurboSelectors::QRANGE][actual_index]; - case PolynomialIndex::Q_FIXED_BASE_SELECTOR: - return composer.selectors[TurboComposer::TurboSelectors::QECC_1][actual_index]; + case PolynomialIndex::Q_FIXED_BASE: + return composer.selectors[TurboComposer::TurboSelectors::QFIXED][actual_index]; case PolynomialIndex::W_1: return composer.get_variable_reference(composer.w_l[actual_index]); case PolynomialIndex::W_2: diff --git a/cpp/src/aztec/plonk/composer/turbo_composer.test.cpp b/cpp/src/aztec/plonk/composer/turbo_composer.test.cpp index db1af29871..cb1d7dc97f 100644 --- a/cpp/src/aztec/plonk/composer/turbo_composer.test.cpp +++ b/cpp/src/aztec/plonk/composer/turbo_composer.test.cpp @@ -490,7 +490,8 @@ TEST(turbo_composer, range_constraint) // include non-nice numbers of bits, that will bleed over gate boundaries size_t extra_bits = 2 * (i % 4); - std::vector accumulators = composer.decompose_into_base4_accumulators(witness_index, 32 + extra_bits); + std::vector accumulators = composer.decompose_into_base4_accumulators( + witness_index, 32 + extra_bits, "constraint in test range_constraint fails"); for (uint32_t j = 0; j < 16; ++j) { uint32_t result = (value >> (30U - (2 * j))); @@ -528,7 +529,7 @@ TEST(turbo_composer, range_constraint_fail) uint64_t value = 0xffffff; uint32_t witness_index = composer.add_variable(fr(value)); - composer.decompose_into_base4_accumulators(witness_index, 23); + composer.decompose_into_base4_accumulators(witness_index, 23, "yay, range constraint fails"); waffle::TurboProver prover = composer.create_prover(); @@ -569,8 +570,8 @@ TEST(turbo_composer, and_constraint_failure) bool result = verifier.verify_proof(proof); - if (composer.failed) { - info("Composer failed; ", composer.err); + if (composer.failed()) { + info("Composer failed; ", composer.err()); } EXPECT_EQ(result, false); @@ -675,8 +676,8 @@ TEST(turbo_composer, xor_constraint_failure) bool result = verifier.verify_proof(proof); - if (composer.failed) { - info("Composer failed; ", composer.err); + if (composer.failed()) { + info("Composer failed; ", composer.err()); } EXPECT_EQ(result, false); @@ -1122,7 +1123,8 @@ TEST(turbo_composer, test_check_circuit_range_constraint) // include non-nice numbers of bits, that will bleed over gate boundaries size_t extra_bits = 2 * (i % 4); - std::vector accumulators = composer.decompose_into_base4_accumulators(witness_index, 32 + extra_bits); + std::vector accumulators = composer.decompose_into_base4_accumulators( + witness_index, 32 + extra_bits, "range constraint fails in test_check_circuit_range_constraint"); } uint32_t zero_idx = composer.add_variable(fr::zero()); diff --git a/cpp/src/aztec/plonk/composer/ultra_composer.cpp b/cpp/src/aztec/plonk/composer/ultra_composer.cpp new file mode 100644 index 0000000000..3114533ecc --- /dev/null +++ b/cpp/src/aztec/plonk/composer/ultra_composer.cpp @@ -0,0 +1,2465 @@ +#include "ultra_composer.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "plookup_tables/types.hpp" +#include "plookup_tables/plookup_tables.hpp" +#include "plookup_tables/aes128.hpp" +#include "plookup_tables/sha256.hpp" + +#ifndef NO_TBB +#include +#include +#include +#include +#include +#endif + +using namespace barretenberg; + +namespace waffle { + +#define ULTRA_SELECTOR_REFS \ + auto& q_m = selectors[UltraSelectors::QM]; \ + auto& q_c = selectors[UltraSelectors::QC]; \ + auto& q_1 = selectors[UltraSelectors::Q1]; \ + auto& q_2 = selectors[UltraSelectors::Q2]; \ + auto& q_3 = selectors[UltraSelectors::Q3]; \ + auto& q_4 = selectors[UltraSelectors::Q4]; \ + auto& q_arith = selectors[UltraSelectors::QARITH]; \ + auto& q_fixed_base = selectors[UltraSelectors::QFIXED]; \ + auto& q_sort = selectors[UltraSelectors::QSORT]; \ + auto& q_elliptic = selectors[UltraSelectors::QELLIPTIC]; \ + auto& q_aux = selectors[UltraSelectors::QAUX]; \ + auto& q_lookup_type = selectors[UltraSelectors::QLOOKUPTYPE]; + +std::vector ultra_selector_properties() +{ + std::vector result{ + { "q_m", true }, { "q_c", true }, { "q_1", true }, { "q_2", true }, + { "q_3", true }, { "q_4", false }, { "q_arith", false }, { "q_fixed_base", false }, + { "q_sort", false }, { "q_elliptic", false }, { "q_aux", false }, { "table_type", true }, + }; + return result; +} + +UltraComposer::UltraComposer() + : UltraComposer("../srs_db/ignition", 0) +{} + +UltraComposer::UltraComposer(std::string const& crs_path, const size_t size_hint) + : UltraComposer(std::unique_ptr(new FileReferenceStringFactory(crs_path)), size_hint){}; + +UltraComposer::UltraComposer(std::shared_ptr const& crs_factory, const size_t size_hint) + : ComposerBase(crs_factory, UltraSelectors::NUM, size_hint, ultra_selector_properties()) +{ + w_l.reserve(size_hint); + w_r.reserve(size_hint); + w_o.reserve(size_hint); + w_4.reserve(size_hint); + zero_idx = put_constant_variable(0); + tau.insert({ DUMMY_TAG, DUMMY_TAG }); +} + +UltraComposer::UltraComposer(std::shared_ptr const& p_key, + std::shared_ptr const& v_key, + size_t size_hint) + : ComposerBase(p_key, v_key, UltraSelectors::NUM, size_hint, ultra_selector_properties()) +{ + w_r.reserve(size_hint); + w_4.reserve(size_hint); + zero_idx = put_constant_variable(0); + tau.insert({ DUMMY_TAG, DUMMY_TAG }); +} + +/** + * @brief Create an addition gate, where in.a * in.a_scaling + in.b * in.b_scaling + in.c * in.c_scaling + + * in.const_scaling = 0 + * + * @details Arithmetic selector is set to 1, all other gate selectors are 0. Mutliplication selector is set to 0 + * + * @param in A structure with variable indexes and selector values for the gate. + */ +void UltraComposer::create_add_gate(const add_triple& in) +{ + ULTRA_SELECTOR_REFS + assert_valid_variables({ in.a, in.b, in.c }); + + w_l.emplace_back(in.a); + w_r.emplace_back(in.b); + w_o.emplace_back(in.c); + w_4.emplace_back(zero_idx); + q_m.emplace_back(0); + q_1.emplace_back(in.a_scaling); + q_2.emplace_back(in.b_scaling); + q_3.emplace_back(in.c_scaling); + q_c.emplace_back(in.const_scaling); + q_arith.emplace_back(1); + q_4.emplace_back(0); + q_fixed_base.emplace_back(0); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++n; +} + +/** + * @brief Create a big addition gate, where in.a * in.a_scaling + in.b * in.b_scaling + in.c * in.c_scaling + in.d * + * in.d_scaling + in.const_scaling = 0. If include_next_gate_w_4 is enabled, then thes sum also adds the value of the + * 4-th witness at the next index. + * + * @param in Structure with variable indexes and wire selector values + * @param include_next_gate_w_4 Switches on/off the addition of w_4 at the next index + */ +void UltraComposer::create_big_add_gate(const add_quad& in, const bool include_next_gate_w_4) +{ + ULTRA_SELECTOR_REFS + assert_valid_variables({ in.a, in.b, in.c, in.d }); + + w_l.emplace_back(in.a); + w_r.emplace_back(in.b); + w_o.emplace_back(in.c); + w_4.emplace_back(in.d); + q_m.emplace_back(0); + q_1.emplace_back(in.a_scaling); + q_2.emplace_back(in.b_scaling); + q_3.emplace_back(in.c_scaling); + q_c.emplace_back(in.const_scaling); + q_arith.emplace_back(include_next_gate_w_4 ? 2 : 1); + q_4.emplace_back(in.d_scaling); + q_fixed_base.emplace_back(0); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++n; +} + +/** + * @brief A legacy method that was used to extract a bit from c-4d by using gate selectors in the Turboplonk, but is + * simulated here for ultraplonk + * + * @param in Structure with variables and witness selector values + */ +void UltraComposer::create_big_add_gate_with_bit_extraction(const add_quad& in) +{ + // This method is an artifact of a turbo plonk feature that implicitly extracts + // a high or low bit from a base-4 quad and adds it into the arithmetic gate relationship. + // This has been removed in the plookup composer due to it's infrequent use not being worth the extra + // cost incurred by the prover for the extra field muls required. + + // We have wires a, b, c, d, where + // a + b + c + d + 6 * (extracted bit) = 0 + // (extracted bit) is the high bit pulled from c - 4d + + assert_valid_variables({ in.a, in.b, in.c, in.d }); + + const uint256_t quad = get_variable(in.c) - get_variable(in.d) * 4; + const auto lo_bit = quad & uint256_t(1); + const auto hi_bit = (quad & uint256_t(2)) >> 1; + const auto lo_idx = add_variable(lo_bit); + const auto hi_idx = add_variable(hi_bit); + // lo + hi * 2 - c + 4 * d = 0 + create_big_add_gate({ + lo_idx, + hi_idx, + in.c, + in.d, + 1, + 2, + -1, + 4, + 0, + }); + + // create temporary variable t = in.a * in.a_scaling + 6 * hi_bit + const auto t = get_variable(in.a) * in.a_scaling + fr(hi_bit) * 6; + const auto t_idx = add_variable(t); + create_big_add_gate({ + in.a, + hi_idx, + t_idx, + zero_idx, + in.a_scaling, + 6, + -1, + 0, + 0, + }); + // (t = a + 6 * hi_bit) + b + c + d = 0 + create_big_add_gate({ + t_idx, + in.b, + in.c, + in.d, + 1, + in.b_scaling, + in.c_scaling, + in.d_scaling, + in.const_scaling, + }); +} +/** + * @brief Create a basic multiplication gate q_m * a * b + q_1 * a + q_2 * b + q_3 * c + q_4 * d + q_c = 0 (q_arith = 1) + * + * @param in Structure containing variables and witness selectors + */ +void UltraComposer::create_big_mul_gate(const mul_quad& in) +{ + ULTRA_SELECTOR_REFS + assert_valid_variables({ in.a, in.b, in.c, in.d }); + + w_l.emplace_back(in.a); + w_r.emplace_back(in.b); + w_o.emplace_back(in.c); + w_4.emplace_back(in.d); + q_m.emplace_back(in.mul_scaling); + q_1.emplace_back(in.a_scaling); + q_2.emplace_back(in.b_scaling); + q_3.emplace_back(in.c_scaling); + q_c.emplace_back(in.const_scaling); + q_arith.emplace_back(1); + q_4.emplace_back(in.d_scaling); + q_fixed_base.emplace_back(0); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++n; +} + +// Creates a width-4 addition gate, where the fourth witness must be a boolean. +// Can be used to normalize a 32-bit addition +void UltraComposer::create_balanced_add_gate(const add_quad& in) +{ + ULTRA_SELECTOR_REFS + assert_valid_variables({ in.a, in.b, in.c, in.d }); + + w_l.emplace_back(in.a); + w_r.emplace_back(in.b); + w_o.emplace_back(in.c); + w_4.emplace_back(in.d); + q_m.emplace_back(0); + q_1.emplace_back(in.a_scaling); + q_2.emplace_back(in.b_scaling); + q_3.emplace_back(in.c_scaling); + q_c.emplace_back(in.const_scaling); + q_arith.emplace_back(1); + q_4.emplace_back(in.d_scaling); + q_fixed_base.emplace_back(0); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++n; + // Why 3? TODO: return to this + create_new_range_constraint(in.d, 3); +} +/** + * @brief Create a multiplication gate with q_m * a * b + q_3 * c + q_const = 0 + * + * @details q_arith == 1 + * + * @param in Structure containing variables and witness selectors + */ +void UltraComposer::create_mul_gate(const mul_triple& in) +{ + ULTRA_SELECTOR_REFS + assert_valid_variables({ in.a, in.b, in.c }); + + w_l.emplace_back(in.a); + w_r.emplace_back(in.b); + w_o.emplace_back(in.c); + w_4.emplace_back(zero_idx); + q_m.emplace_back(in.mul_scaling); + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(in.c_scaling); + q_c.emplace_back(in.const_scaling); + q_arith.emplace_back(1); + q_4.emplace_back(0); + q_fixed_base.emplace_back(0); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++n; +} +/** + * @brief Generate an arithmetic gate equivalent to x^2 - x = 0, which forces x to be 0 or 1 + * + * @param variable_index Variable which needs to be constrained + */ +void UltraComposer::create_bool_gate(const uint32_t variable_index) +{ + ULTRA_SELECTOR_REFS + assert_valid_variables({ variable_index }); + + w_l.emplace_back(variable_index); + w_r.emplace_back(variable_index); + w_o.emplace_back(zero_idx); + w_4.emplace_back(zero_idx); + q_m.emplace_back(1); + q_1.emplace_back(-1); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(0); + q_sort.emplace_back(0); + + q_arith.emplace_back(1); + q_4.emplace_back(0); + q_fixed_base.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++n; +} + +/** + * @brief A plonk gate with disabled (set to zero) fourth wire. q_m * a * b + q_1 * a + q_2 * b + q_3 * c + q_const = 0 + * + * @param in Structure containing variables and witness selectors + */ +void UltraComposer::create_poly_gate(const poly_triple& in) +{ + ULTRA_SELECTOR_REFS + assert_valid_variables({ in.a, in.b, in.c }); + + w_l.emplace_back(in.a); + w_r.emplace_back(in.b); + w_o.emplace_back(in.c); + w_4.emplace_back(zero_idx); + q_m.emplace_back(in.q_m); + q_1.emplace_back(in.q_l); + q_2.emplace_back(in.q_r); + q_3.emplace_back(in.q_o); + q_c.emplace_back(in.q_c); + q_sort.emplace_back(0); + + q_arith.emplace_back(1); + q_4.emplace_back(0); + q_fixed_base.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++n; +} + +// adds a grumpkin point, from a 2-bit lookup table, into an accumulator point +void UltraComposer::create_fixed_group_add_gate(const fixed_group_add_quad& in) +{ + ULTRA_SELECTOR_REFS + assert_valid_variables({ in.a, in.b, in.c, in.d }); + + w_l.emplace_back(in.a); + w_r.emplace_back(in.b); + w_o.emplace_back(in.c); + w_4.emplace_back(in.d); + + q_1.emplace_back(in.q_x_1); + q_2.emplace_back(in.q_x_2); + q_3.emplace_back(in.q_y_1); + q_fixed_base.emplace_back(in.q_y_2); + + q_arith.emplace_back(0); + q_4.emplace_back(0); + q_m.emplace_back(0); + q_c.emplace_back(0); + q_lookup_type.emplace_back(0); + q_sort.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++n; +} + +// adds a grumpkin point into an accumulator, while also initializing the accumulator +void UltraComposer::create_fixed_group_add_gate_with_init(const fixed_group_add_quad& in, + const fixed_group_init_quad& init) +{ + ULTRA_SELECTOR_REFS + assert_valid_variables({ in.a, in.b, in.c, in.d }); + + w_l.emplace_back(in.a); + w_r.emplace_back(in.b); + w_o.emplace_back(in.c); + w_4.emplace_back(in.d); + + // Initialization differs slightly with that in TurboComposer. + q_m.emplace_back(init.q_y_1); + q_c.emplace_back(init.q_y_2); + + q_1.emplace_back(in.q_x_1); + q_2.emplace_back(in.q_x_2); + q_3.emplace_back(in.q_y_1); + q_fixed_base.emplace_back(in.q_y_2); + + q_4.emplace_back(0); + q_aux.emplace_back(0); + q_arith.emplace_back(0); + q_lookup_type.emplace_back(0); + q_sort.emplace_back(0); + q_elliptic.emplace_back(0); + + ++n; +} + +void UltraComposer::create_fixed_group_add_gate_final(const add_quad& in) +{ + create_big_add_gate(in); +} +/** + * @brief Create an elliptic curve addition gate + * + * @details x and y are defined over scalar field. Addition can handle applying the curve endomorphism to one of the + * points being summed at the time of addition. + * + * @param in Elliptic curve point addition gate parameters, including the the affine coordinates of the two points being + * added, the resulting point coordinates and the selector values that describe whether the endomorphism is used on the + * second point and whether it is negated. + */ +void UltraComposer::create_ecc_add_gate(const ecc_add_gate& in) +{ + /** + * | 1 | 2 | 3 | 4 | + * | a1 | a2 | x1 | y1 | + * | x2 | y2 | x3 | y3 | + * | -- | -- | x4 | y4 | + * + **/ + + ULTRA_SELECTOR_REFS + + assert_valid_variables({ in.x1, in.x2, in.x3, in.y1, in.y2, in.y3 }); + + bool can_fuse_into_previous_gate = true; + can_fuse_into_previous_gate = can_fuse_into_previous_gate && (w_r[n - 1] == in.x1); + can_fuse_into_previous_gate = can_fuse_into_previous_gate && (w_o[n - 1] == in.y1); + can_fuse_into_previous_gate = can_fuse_into_previous_gate && (q_3[n - 1] == 0); + can_fuse_into_previous_gate = can_fuse_into_previous_gate && (q_4[n - 1] == 0); + can_fuse_into_previous_gate = can_fuse_into_previous_gate && (q_1[n - 1] == 0); + can_fuse_into_previous_gate = can_fuse_into_previous_gate && (q_arith[n - 1] == 0); + + if (can_fuse_into_previous_gate) { + + q_3[n - 1] = in.endomorphism_coefficient; + q_4[n - 1] = in.endomorphism_coefficient.sqr(); + q_1[n - 1] = in.sign_coefficient; + q_elliptic[n - 1] = 1; + } else { + w_l.emplace_back(zero_idx); + w_r.emplace_back(in.x1); + w_o.emplace_back(in.y1); + w_4.emplace_back(zero_idx); + q_3.emplace_back(in.endomorphism_coefficient); + q_4.emplace_back(in.endomorphism_coefficient.sqr()); + q_1.emplace_back(in.sign_coefficient); + + q_arith.emplace_back(0); + q_2.emplace_back(0); + q_m.emplace_back(0); + q_c.emplace_back(0); + q_fixed_base.emplace_back(0); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(1); + q_aux.emplace_back(0); + ++n; + } + + w_l.emplace_back(in.x2); + w_4.emplace_back(in.y2); + w_r.emplace_back(in.x3); + w_o.emplace_back(in.y3); + q_m.emplace_back(0); + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + q_4.emplace_back(0); + q_fixed_base.emplace_back(0); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++n; +} + +/** + * @brief Add a gate equating a particular witness to a constant, fixing it the value + * + * @param witness_index The index of the witness we are fixing + * @param witness_value The value we are fixing it to + */ +void UltraComposer::fix_witness(const uint32_t witness_index, const barretenberg::fr& witness_value) +{ + ULTRA_SELECTOR_REFS + assert_valid_variables({ witness_index }); + + w_l.emplace_back(witness_index); + w_r.emplace_back(zero_idx); + w_o.emplace_back(zero_idx); + w_4.emplace_back(zero_idx); + q_m.emplace_back(0); + q_1.emplace_back(1); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(-witness_value); + q_arith.emplace_back(1); + q_4.emplace_back(0); + q_fixed_base.emplace_back(0); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++n; +} + +uint32_t UltraComposer::put_constant_variable(const barretenberg::fr& variable) +{ + if (constant_variable_indices.contains(variable)) { + return constant_variable_indices.at(variable); + } else { + uint32_t variable_index = add_variable(variable); + fix_witness(variable_index, variable); + constant_variable_indices.insert({ variable, variable_index }); + return variable_index; + } +} + +void UltraComposer::add_table_column_selector_poly_to_proving_key(polynomial& selector_poly_lagrange_form, + const std::string& tag) +{ + polynomial selector_poly_lagrange_form_copy(selector_poly_lagrange_form, circuit_proving_key->small_domain.size); + + selector_poly_lagrange_form.ifft(circuit_proving_key->small_domain); + auto& selector_poly_coeff_form = selector_poly_lagrange_form; + + polynomial selector_poly_coset_form(selector_poly_coeff_form, circuit_proving_key->n * 4); + selector_poly_coset_form.coset_fft(circuit_proving_key->large_domain); + + circuit_proving_key->polynomial_cache.put(tag, std::move(selector_poly_coeff_form)); + circuit_proving_key->polynomial_cache.put(tag + "_lagrange", std::move(selector_poly_lagrange_form_copy)); + circuit_proving_key->polynomial_cache.put(tag + "_fft", std::move(selector_poly_coset_form)); +} + +std::shared_ptr UltraComposer::compute_proving_key() +{ + ULTRA_SELECTOR_REFS; + + /** + * First of all, add the gates related to ROM arrays and range lists. + * Note that the total number of rows in an UltraPlonk program can be divided as following: + * 1. arithmetic gates: n_computation (includes all computation gates) + * 2. rom/memory gates: n_rom + * 3. range list gates: n_range + * 4. public inputs: n_pub + * + * Now we have two variables referred to as `n` in the code: + * 1. ComposerBase::n => refers to the size of the witness of a given program, + * 2. proving_key::n => the next power of two ≥ total witness size. + * + * In this case, we have composer.n = n_computation before we execute the following two functions. + * After these functions are executed, the composer's `n` is incremented to include the ROM + * and range list gates. Therefore we have: + * composer.n = n_computation + n_rom + n_range. + * + * Its necessary to include the (n_rom + n_range) gates at this point because if we already have a + * proving key, and we just return it without including these ROM and range list gates, the overall + * circuit size would not be correct (resulting in the code crashing while performing FFT operations). + * + * Therefore, we introduce a boolean flag `circuit_finalised` here. Once we add the rom and range gates, + * our circuit is finalised, and we must not to execute these functions again. + */ + if (!circuit_finalised) { + process_ROM_arrays(public_inputs.size()); + process_range_lists(); + circuit_finalised = true; + } + + if (circuit_proving_key) { + return circuit_proving_key; + } + + ASSERT(n == q_m.size()); + ASSERT(n == q_c.size()); + ASSERT(n == q_1.size()); + ASSERT(n == q_2.size()); + ASSERT(n == q_3.size()); + ASSERT(n == q_4.size()); + ASSERT(n == q_arith.size()); + ASSERT(n == q_fixed_base.size()); + ASSERT(n == q_elliptic.size()); + ASSERT(n == q_sort.size()); + ASSERT(n == q_lookup_type.size()); + ASSERT(n == q_aux.size()); + + size_t tables_size = 0; + size_t lookups_size = 0; + for (const auto& table : lookup_tables) { + tables_size += table.size; + lookups_size += table.lookup_gates.size(); + } + + // Compute selector polynomials and appropriate fft versions and put them in the proving key + ComposerBase::compute_proving_key_base(type, tables_size + lookups_size, NUM_RESERVED_GATES); + + const size_t subgroup_size = circuit_proving_key->n; + + polynomial poly_q_table_column_1(subgroup_size); + polynomial poly_q_table_column_2(subgroup_size); + polynomial poly_q_table_column_3(subgroup_size); + polynomial poly_q_table_column_4(subgroup_size); + + size_t offset = subgroup_size - tables_size - s_randomness - 1; + + // Create lookup selector polynomials which interpolate each table column. + // Our selector polys always need to interpolate the full subgroup size, so here we offset so as to + // put the table column's values at the end. (The first gates are for non-lookup constraints). + // [0, ..., 0, ...table, 0, 0, 0, x] + // ^^^^^^^^^ ^^^^^^^^ ^^^^^^^ ^nonzero to ensure uniqueness and to avoid infinity commitments + // | table randomness + // ignored, as used for regular constraints and padding to the next power of 2. + + for (size_t i = 0; i < offset; ++i) { + poly_q_table_column_1[i] = 0; + poly_q_table_column_2[i] = 0; + poly_q_table_column_3[i] = 0; + poly_q_table_column_4[i] = 0; + } + + for (const auto& table : lookup_tables) { + const fr table_index(table.table_index); + + for (size_t i = 0; i < table.size; ++i) { + poly_q_table_column_1[offset] = table.column_1[i]; + poly_q_table_column_2[offset] = table.column_2[i]; + poly_q_table_column_3[offset] = table.column_3[i]; + poly_q_table_column_4[offset] = table_index; + ++offset; + } + } + + // Initialise the last `s_randomness` positions in table polynomials with 0. We don't need to actually randomise the + // table polynomials. + for (size_t i = 0; i < s_randomness; ++i) { + poly_q_table_column_1[offset] = 0; + poly_q_table_column_2[offset] = 0; + poly_q_table_column_3[offset] = 0; + poly_q_table_column_4[offset] = 0; + ++offset; + } + + // In the case of using UltraComposer for a circuit which does _not_ make use of any lookup tables, all four table + // columns would be all zeros. This would result in these polys' commitments all being the point at infinity (which + // is bad because our point arithmetic assumes we'll never operate on the point at infinity). To avoid this, we set + // the last evaluation of each poly to be nonzero. The last `num_roots_cut_out_of_vanishing_poly = 4` evaluations + // are ignored by constraint checks; we arbitrarily choose the very-last evaluation to be nonzero. See + // ComposerBase::compute_proving_key_base for further explanation, as a similar trick is done there. + // We could have chosen `1` for each such evaluation here, but that would have resulted in identical commitments for + // all four columns. We don't want to have equal commitments, because biggroup operations assume no points are + // equal, so if we tried to verify an ultra proof in a circuit, the biggroup operations would fail. To combat this, + // we just choose distinct values: + ASSERT(offset == subgroup_size - 1); + auto unique_last_value = num_selectors + 1; // Note: in compute_proving_key_base, moments earlier, each selector + // vector was given a unique last value from 1..num_selectors. So we + // avoid those values and continue the count, to ensure uniqueness. + poly_q_table_column_1[subgroup_size - 1] = unique_last_value; + poly_q_table_column_2[subgroup_size - 1] = ++unique_last_value; + poly_q_table_column_3[subgroup_size - 1] = ++unique_last_value; + poly_q_table_column_4[subgroup_size - 1] = ++unique_last_value; + + add_table_column_selector_poly_to_proving_key(poly_q_table_column_1, "table_value_1"); + add_table_column_selector_poly_to_proving_key(poly_q_table_column_2, "table_value_2"); + add_table_column_selector_poly_to_proving_key(poly_q_table_column_3, "table_value_3"); + add_table_column_selector_poly_to_proving_key(poly_q_table_column_4, "table_value_4"); + + // Instantiate z_lookup and s polynomials in the proving key (no values assigned yet). + // Note: might be better to add these polys to cache only after they've been computed, as is convention + polynomial z_lookup_fft(subgroup_size * 4, subgroup_size * 4); + polynomial s_fft(subgroup_size * 4, subgroup_size * 4); + circuit_proving_key->polynomial_cache.put("z_lookup_fft", std::move(z_lookup_fft)); + circuit_proving_key->polynomial_cache.put("s_fft", std::move(s_fft)); + + // TODO: composer-level constant variable needed for the program width + compute_sigma_permutations<4, true>(circuit_proving_key.get()); + + std::copy(memory_records.begin(), memory_records.end(), std::back_inserter(circuit_proving_key->memory_records)); + + circuit_proving_key->recursive_proof_public_input_indices = + std::vector(recursive_proof_public_input_indices.begin(), recursive_proof_public_input_indices.end()); + + circuit_proving_key->contains_recursive_proof = contains_recursive_proof; + + return circuit_proving_key; +} + +std::shared_ptr UltraComposer::compute_verification_key() +{ + if (circuit_verification_key) { + return circuit_verification_key; + } + if (!circuit_proving_key) { + compute_proving_key(); + } + circuit_verification_key = + ComposerBase::compute_verification_key_base(circuit_proving_key, crs_factory_->get_verifier_crs()); + + circuit_verification_key->composer_type = type; // Invariably plookup for this class. + + // See `add_recusrive_proof()` for how this recursive data is assigned. + circuit_verification_key->recursive_proof_public_input_indices = + std::vector(recursive_proof_public_input_indices.begin(), recursive_proof_public_input_indices.end()); + + circuit_verification_key->contains_recursive_proof = contains_recursive_proof; + + return circuit_verification_key; +} + +/** + * @brief Computes `this.witness`, which is basiclly a set of polynomials mapped-to by strings. + * + * Note: this doesn't actually compute the _entire_ witness. Things missing: randomness for blinding both the wires and + * sorted `s` poly, lookup rows of the wire witnesses, the values of `z_lookup`, `z`. These are all calculated + * elsewhere. + */ +void UltraComposer::compute_witness() +{ + if (computed_witness) { + return; + } + + size_t tables_size = 0; + size_t lookups_size = 0; + for (const auto& table : lookup_tables) { + tables_size += table.size; + lookups_size += table.lookup_gates.size(); + } + + const size_t filled_gates = n + public_inputs.size(); + const size_t total_num_gates = std::max(filled_gates, tables_size + lookups_size); + + const size_t subgroup_size = get_circuit_subgroup_size(total_num_gates + NUM_RESERVED_GATES); + + // Pad the wires (pointers to `witness_indices` of the `variables` vector). + // Note: the remaining NUM_RESERVED_GATES indices are padded with zeros within `compute_witness_base` (called + // next). + for (size_t i = filled_gates; i < total_num_gates; ++i) { + w_l.emplace_back(zero_idx); + w_r.emplace_back(zero_idx); + w_o.emplace_back(zero_idx); + w_4.emplace_back(zero_idx); + } + + // Create and store polynomials which interpolate the wire values (variable values pointed-to by the `w_`s). + ComposerBase::compute_witness_base(total_num_gates); + + polynomial s_1(subgroup_size); + polynomial s_2(subgroup_size); + polynomial s_3(subgroup_size); + polynomial s_4(subgroup_size); + polynomial z_lookup(subgroup_size + 1); // Only instantiated in this function; nothing assigned. + + // Save space for adding random scalars in the s polynomial later. + // The subtracted 1 allows us to insert a `1` at the end, to ensure the evaluations (and hence coefficients) aren't + // all 0. + // See ComposerBase::compute_proving_key_base for further explanation, as a similar trick is done there. + size_t count = subgroup_size - tables_size - lookups_size - s_randomness - 1; + for (size_t i = 0; i < count; ++i) { + s_1[i] = 0; + s_2[i] = 0; + s_3[i] = 0; + s_4[i] = 0; + } + + for (auto& table : lookup_tables) { + const fr table_index(table.table_index); + auto& lookup_gates = table.lookup_gates; + for (size_t i = 0; i < table.size; ++i) { + if (table.use_twin_keys) { + lookup_gates.push_back({ + { + table.column_1[i].from_montgomery_form().data[0], + table.column_2[i].from_montgomery_form().data[0], + }, + { + table.column_3[i], + 0, + }, + }); + } else { + lookup_gates.push_back({ + { + table.column_1[i].from_montgomery_form().data[0], + 0, + }, + { + table.column_2[i], + table.column_3[i], + }, + }); + } + } + +#ifdef NO_TBB + std::sort(lookup_gates.begin(), lookup_gates.end()); +#else + std::sort(std::execution::par_unseq, lookup_gates.begin(), lookup_gates.end()); +#endif + + for (const auto& entry : lookup_gates) { + const auto components = entry.to_sorted_list_components(table.use_twin_keys); + s_1[count] = components[0]; + s_2[count] = components[1]; + s_3[count] = components[2]; + s_4[count] = table_index; + ++count; + } + } + + // Initialise the `s_randomness` positions in the s polynomials with 0. + // These will be the positions where we will be adding random scalars to add zero knowledge + // to plookup (search for `Blinding` in plonk/proof_system/widgets/random_widgets/plookup_widget_impl.hpp + // ProverPlookupWidget::compute_sorted_list_polynomial()) + for (size_t i = 0; i < s_randomness; ++i) { + s_1[count] = 0; + s_2[count] = 0; + s_3[count] = 0; + s_4[count] = 0; + ++count; + } + + circuit_proving_key->polynomial_cache.put("s_1_lagrange", std::move(s_1)); + circuit_proving_key->polynomial_cache.put("s_2_lagrange", std::move(s_2)); + circuit_proving_key->polynomial_cache.put("s_3_lagrange", std::move(s_3)); + circuit_proving_key->polynomial_cache.put("s_4_lagrange", std::move(s_4)); + + computed_witness = true; +} + +UltraProver UltraComposer::create_prover() +{ + compute_proving_key(); + compute_witness(); + UltraProver output_state(circuit_proving_key, create_manifest(public_inputs.size())); + + std::unique_ptr> permutation_widget = + std::make_unique>(circuit_proving_key.get()); + std::unique_ptr> plookup_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> arithmetic_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> fixed_base_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> sort_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> elliptic_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> auxiliary_widget = + std::make_unique>(circuit_proving_key.get()); + + output_state.random_widgets.emplace_back(std::move(permutation_widget)); + output_state.random_widgets.emplace_back(std::move(plookup_widget)); + + output_state.transition_widgets.emplace_back(std::move(arithmetic_widget)); + output_state.transition_widgets.emplace_back(std::move(fixed_base_widget)); + output_state.transition_widgets.emplace_back(std::move(sort_widget)); + output_state.transition_widgets.emplace_back(std::move(elliptic_widget)); + output_state.transition_widgets.emplace_back(std::move(auxiliary_widget)); + + std::unique_ptr> kate_commitment_scheme = + std::make_unique>(); + + output_state.commitment_scheme = std::move(kate_commitment_scheme); + + return output_state; +} + +/** + * @note 'unrolled' means 'don't use linearisation techniques from the plonk paper'. + */ +UnrolledUltraProver UltraComposer::create_unrolled_prover() +{ + compute_proving_key(); + compute_witness(); + + UnrolledUltraProver output_state(circuit_proving_key, create_unrolled_manifest(public_inputs.size())); + + std::unique_ptr> permutation_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> plookup_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> arithmetic_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> fixed_base_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> sort_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> elliptic_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> auxiliary_widget = + std::make_unique>(circuit_proving_key.get()); + + output_state.random_widgets.emplace_back(std::move(permutation_widget)); + output_state.random_widgets.emplace_back(std::move(plookup_widget)); + + output_state.transition_widgets.emplace_back(std::move(arithmetic_widget)); + output_state.transition_widgets.emplace_back(std::move(fixed_base_widget)); + output_state.transition_widgets.emplace_back(std::move(sort_widget)); + output_state.transition_widgets.emplace_back(std::move(elliptic_widget)); + output_state.transition_widgets.emplace_back(std::move(auxiliary_widget)); + + std::unique_ptr> kate_commitment_scheme = + std::make_unique>(); + + output_state.commitment_scheme = std::move(kate_commitment_scheme); + + return output_state; +} + +/** + * @brief Uses slightly different settings from the UnrolledUltraProver. + * @note 'unrolled' means 'don't use linearisation techniques from the plonk paper'. + */ +UnrolledUltraToStandardProver UltraComposer::create_unrolled_ultra_to_standard_prover() +{ + compute_proving_key(); + compute_witness(); + + UnrolledUltraToStandardProver output_state(circuit_proving_key, create_unrolled_manifest(public_inputs.size())); + + std::unique_ptr> permutation_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> plookup_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> arithmetic_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> fixed_base_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> sort_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> elliptic_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> auxiliary_widget = + std::make_unique>(circuit_proving_key.get()); + + output_state.random_widgets.emplace_back(std::move(permutation_widget)); + output_state.random_widgets.emplace_back(std::move(plookup_widget)); + + output_state.transition_widgets.emplace_back(std::move(arithmetic_widget)); + output_state.transition_widgets.emplace_back(std::move(fixed_base_widget)); + output_state.transition_widgets.emplace_back(std::move(sort_widget)); + output_state.transition_widgets.emplace_back(std::move(elliptic_widget)); + output_state.transition_widgets.emplace_back(std::move(auxiliary_widget)); + + std::unique_ptr> kate_commitment_scheme = + std::make_unique>(); + + output_state.commitment_scheme = std::move(kate_commitment_scheme); + + return output_state; +} + +UltraVerifier UltraComposer::create_verifier() +{ + compute_verification_key(); + + UltraVerifier output_state(circuit_verification_key, create_manifest(public_inputs.size())); + + std::unique_ptr> kate_commitment_scheme = + std::make_unique>(); + + output_state.commitment_scheme = std::move(kate_commitment_scheme); + + return output_state; +} + +UnrolledUltraVerifier UltraComposer::create_unrolled_verifier() +{ + compute_verification_key(); + + UnrolledUltraVerifier output_state(circuit_verification_key, create_unrolled_manifest(public_inputs.size())); + + std::unique_ptr> kate_commitment_scheme = + std::make_unique>(); + + output_state.commitment_scheme = std::move(kate_commitment_scheme); + + return output_state; +} + +UnrolledUltraToStandardVerifier UltraComposer::create_unrolled_ultra_to_standard_verifier() +{ + compute_verification_key(); + + UnrolledUltraToStandardVerifier output_state(circuit_verification_key, + create_unrolled_manifest(public_inputs.size())); + + std::unique_ptr> kate_commitment_scheme = + std::make_unique>(); + + output_state.commitment_scheme = std::move(kate_commitment_scheme); + + return output_state; +} + +void UltraComposer::initialize_precomputed_table( + const plookup::BasicTableId id, + bool (*generator)(std::vector&, std ::vector&, std::vector&), + std::array (*get_values_from_key)(const std::array)) +{ + for (auto table : lookup_tables) { + ASSERT(table.id != id); + } + plookup::BasicTable new_table; + new_table.id = id; + new_table.table_index = lookup_tables.size() + 1; + new_table.use_twin_keys = generator(new_table.column_1, new_table.column_2, new_table.column_3); + new_table.size = new_table.column_1.size(); + new_table.get_values_from_key = get_values_from_key; + lookup_tables.emplace_back(new_table); +} + +plookup::BasicTable& UltraComposer::get_table(const plookup::BasicTableId id) +{ + for (plookup::BasicTable& table : lookup_tables) { + if (table.id == id) { + return table; + } + } + // Table doesn't exist! So try to create it. + lookup_tables.emplace_back(plookup::create_basic_table(id, lookup_tables.size())); + return lookup_tables[lookup_tables.size() - 1]; +} + +/** + * @brief Perform a series of lookups, one for each 'row' in read_values. + */ +plookup::ReadData UltraComposer::create_gates_from_plookup_accumulators( + const plookup::MultiTableId& id, + const plookup::ReadData& read_values, + const uint32_t key_a_index, + std::optional key_b_index) +{ + ULTRA_SELECTOR_REFS; + const auto& multi_table = plookup::create_table(id); + const size_t num_lookups = read_values[plookup::ColumnIdx::C1].size(); + plookup::ReadData read_data; + for (size_t i = 0; i < num_lookups; ++i) { + auto& table = get_table(multi_table.lookup_ids[i]); + + table.lookup_gates.emplace_back(read_values.key_entries[i]); + + const auto first_idx = (i == 0) ? key_a_index : add_variable(read_values[plookup::ColumnIdx::C1][i]); + const auto second_idx = (i == 0 && (key_b_index.has_value())) + ? key_b_index.value() + : add_variable(read_values[plookup::ColumnIdx::C2][i]); + const auto third_idx = add_variable(read_values[plookup::ColumnIdx::C3][i]); + + read_data[plookup::ColumnIdx::C1].push_back(first_idx); + read_data[plookup::ColumnIdx::C2].push_back(second_idx); + read_data[plookup::ColumnIdx::C3].push_back(third_idx); + assert_valid_variables({ first_idx, second_idx, third_idx }); + + q_lookup_type.emplace_back(fr(1)); + q_3.emplace_back(fr(table.table_index)); + w_l.emplace_back(first_idx); + w_r.emplace_back(second_idx); + w_o.emplace_back(third_idx); + w_4.emplace_back(zero_idx); + q_1.emplace_back(0); + q_2.emplace_back((i == (num_lookups - 1) ? 0 : -multi_table.column_1_step_sizes[i + 1])); + q_m.emplace_back((i == (num_lookups - 1) ? 0 : -multi_table.column_2_step_sizes[i + 1])); + q_c.emplace_back((i == (num_lookups - 1) ? 0 : -multi_table.column_3_step_sizes[i + 1])); + q_arith.emplace_back(0); + q_4.emplace_back(0); + q_fixed_base.emplace_back(0); + q_sort.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + ++n; + } + return read_data; +} + +/** + * Generalized Permutation Methods + **/ + +UltraComposer::RangeList UltraComposer::create_range_list(const uint64_t target_range) +{ + RangeList result; + const auto range_tag = get_new_tag(); // current_tag + 1; + const auto tau_tag = get_new_tag(); // current_tag + 2; + create_tag(range_tag, tau_tag); + create_tag(tau_tag, range_tag); + result.target_range = target_range; + result.range_tag = range_tag; + result.tau_tag = tau_tag; + + uint64_t num_multiples_of_three = (target_range / DEFAULT_PLOOKUP_RANGE_STEP_SIZE); + + result.variable_indices.reserve((uint32_t)num_multiples_of_three); + for (uint64_t i = 0; i <= num_multiples_of_three; ++i) { + const uint32_t index = add_variable(i * DEFAULT_PLOOKUP_RANGE_STEP_SIZE); + result.variable_indices.emplace_back(index); + assign_tag(index, result.range_tag); + } + { + const uint32_t index = add_variable(target_range); + result.variable_indices.emplace_back(index); + assign_tag(index, result.range_tag); + } + // Need this because these variables will not appear in the witness otherwise + create_dummy_constraints(result.variable_indices); + + return result; +} + +// range constraint a value by decomposing it into limbs whose size should be the default range constraint size +std::vector UltraComposer::decompose_into_default_range(const uint32_t variable_index, + const size_t num_bits, + const size_t target_range_bitnum, + std::string const& msg) +{ + assert_valid_variables({ variable_index }); + + ASSERT(num_bits > 0); + + uint256_t val = (uint256_t)(get_variable(variable_index)); + + // If the value is out of range, set the composer error to the given msg. + if (val.get_msb() >= num_bits && !failed()) { + failure(msg); + } + + const uint64_t sublimb_mask = (1ULL << target_range_bitnum) - 1; + std::vector sublimbs; + std::vector sublimb_indices; + + const bool has_remainder_bits = (num_bits % target_range_bitnum != 0); + const size_t num_limbs = (num_bits / target_range_bitnum) + has_remainder_bits; + const size_t last_limb_size = num_bits - ((num_bits / target_range_bitnum) * target_range_bitnum); + const uint64_t last_limb_range = ((uint64_t)1 << last_limb_size) - 1; + + uint256_t accumulator = val; + for (size_t i = 0; i < num_limbs; ++i) { + sublimbs.push_back(accumulator.data[0] & sublimb_mask); + accumulator = accumulator >> target_range_bitnum; + } + for (size_t i = 0; i < sublimbs.size(); ++i) { + const auto limb_idx = add_variable(sublimbs[i]); + sublimb_indices.emplace_back(limb_idx); + if ((i == sublimbs.size() - 1) && has_remainder_bits) { + create_new_range_constraint(limb_idx, last_limb_range); + } else { + create_new_range_constraint(limb_idx, sublimb_mask); + } + } + + const size_t num_limb_triples = (num_limbs / 3) + ((num_limbs % 3) != 0); + const size_t leftovers = (num_limbs % 3) == 0 ? 3 : (num_limbs % 3); + + accumulator = val; + uint32_t accumulator_idx = variable_index; + + for (size_t i = 0; i < num_limb_triples; ++i) { + const bool real_limbs[3]{ + (i == num_limb_triples - 1 && leftovers < 1) ? false : true, + (i == num_limb_triples - 1 && leftovers < 2) ? false : true, + (i == num_limb_triples - 1 && leftovers < 3) ? false : true, + }; + + const uint64_t round_sublimbs[3]{ + real_limbs[0] ? sublimbs[3 * i] : 0, + real_limbs[1] ? sublimbs[3 * i + 1] : 0, + real_limbs[2] ? sublimbs[3 * i + 2] : 0, + }; + const uint32_t new_limbs[3]{ + real_limbs[0] ? sublimb_indices[3 * i] : zero_idx, + real_limbs[1] ? sublimb_indices[3 * i + 1] : zero_idx, + real_limbs[2] ? sublimb_indices[3 * i + 2] : zero_idx, + }; + const uint64_t shifts[3]{ + target_range_bitnum * (3 * i), + target_range_bitnum * (3 * i + 1), + target_range_bitnum * (3 * i + 2), + }; + uint256_t new_accumulator = accumulator - (uint256_t(round_sublimbs[0]) << shifts[0]) - + (uint256_t(round_sublimbs[1]) << shifts[1]) - + (uint256_t(round_sublimbs[2]) << shifts[2]); + + create_big_add_gate( + { + new_limbs[0], + new_limbs[1], + new_limbs[2], + accumulator_idx, + uint256_t(1) << shifts[0], + uint256_t(1) << shifts[1], + uint256_t(1) << shifts[2], + -1, + 0, + }, + ((i == num_limb_triples - 1) ? false : true)); + accumulator_idx = add_variable(new_accumulator); + accumulator = new_accumulator; + } + return sublimb_indices; +} + +void UltraComposer::create_new_range_constraint(const uint32_t variable_index, const uint64_t target_range) +{ + if (range_lists.count(target_range) == 0) { + range_lists.insert({ target_range, create_range_list(target_range) }); + } + + auto& list = range_lists[target_range]; + assign_tag(variable_index, list.range_tag); + list.variable_indices.emplace_back(variable_index); +} + +void UltraComposer::process_range_list(const RangeList& list) +{ + assert_valid_variables(list.variable_indices); + + ASSERT(list.variable_indices.size() > 0); + // go over variables + // for each variable, create mirror variable with same value - with tau tag + // need to make sure that, in original list, increments of at most 3 + std::vector sorted_list; + sorted_list.reserve(list.variable_indices.size()); + for (const auto variable_index : list.variable_indices) { + const auto& field_element = get_variable(variable_index); + const uint64_t shrinked_value = field_element.from_montgomery_form().data[0]; + sorted_list.emplace_back(shrinked_value); + } + +#ifdef NO_TBB + std::sort(sorted_list.begin(), sorted_list.end()); +#else + std::sort(std::execution::par_unseq, sorted_list.begin(), sorted_list.end()); +#endif + std::vector indices; + + // list must be padded to a multipe of 4 and larger than 4 (gate_width) + constexpr size_t gate_width = ultra_settings::program_width; + size_t padding = (gate_width - (list.variable_indices.size() % gate_width)) % gate_width; + if (list.variable_indices.size() <= gate_width) + padding += gate_width; + for (size_t i = 0; i < padding; ++i) { + indices.emplace_back(zero_idx); + } + for (const auto sorted_value : sorted_list) { + const uint32_t index = add_variable(sorted_value); + assign_tag(index, list.tau_tag); + indices.emplace_back(index); + } + create_sort_constraint_with_edges(indices, 0, list.target_range); +} + +void UltraComposer::process_range_lists() +{ + for (const auto& i : range_lists) + process_range_list(i.second); +} + +/* + Create range constraint: + * add variable index to a list of range constrained variables + * data structures: vector of lists, each list contains: + * - the range size + * - the list of variables in the range + * - a generalised permutation tag + * + * create range constraint parameters: variable index && range size + * + * std::map range_lists; +*/ +// Check for a sequence of variables that neighboring differences are at most 3 (used for batched range checkj) +void UltraComposer::create_sort_constraint(const std::vector& variable_index) +{ + ULTRA_SELECTOR_REFS + constexpr size_t gate_width = ultra_settings::program_width; + ASSERT(variable_index.size() % gate_width == 0); + assert_valid_variables(variable_index); + + for (size_t i = 0; i < variable_index.size(); i += gate_width) { + + w_l.emplace_back(variable_index[i]); + w_r.emplace_back(variable_index[i + 1]); + w_o.emplace_back(variable_index[i + 2]); + w_4.emplace_back(variable_index[i + 3]); + ++n; + q_m.emplace_back(0); + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + q_4.emplace_back(0); + q_fixed_base.emplace_back(0); + q_sort.emplace_back(1); + q_elliptic.emplace_back(0); + q_lookup_type.emplace_back(0); + q_aux.emplace_back(0); + } + // dummy gate needed because of sort widget's check of next row + w_l.emplace_back(variable_index[variable_index.size() - 1]); + w_r.emplace_back(zero_idx); + w_o.emplace_back(zero_idx); + w_4.emplace_back(zero_idx); + ++n; + q_m.emplace_back(0); + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + q_4.emplace_back(0); + q_fixed_base.emplace_back(0); + q_sort.emplace_back(0); + q_elliptic.emplace_back(0); + q_lookup_type.emplace_back(0); + q_aux.emplace_back(0); +} + +// useful to put variables in the witness that aren't already used - e.g. the dummy variables of the range constraint in +// multiples of three +void UltraComposer::create_dummy_constraints(const std::vector& variable_index) +{ + ULTRA_SELECTOR_REFS + std::vector padded_list = variable_index; + constexpr size_t gate_width = ultra_settings::program_width; + const uint64_t padding = (gate_width - (padded_list.size() % gate_width)) % gate_width; + for (uint64_t i = 0; i < padding; ++i) { + padded_list.emplace_back(zero_idx); + } + assert_valid_variables(variable_index); + assert_valid_variables(padded_list); + + for (size_t i = 0; i < padded_list.size(); i += gate_width) { + w_l.emplace_back(padded_list[i]); + w_r.emplace_back(padded_list[i + 1]); + w_o.emplace_back(padded_list[i + 2]); + w_4.emplace_back(padded_list[i + 3]); + ++n; + q_m.emplace_back(0); + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + q_4.emplace_back(0); + q_fixed_base.emplace_back(0); + q_sort.emplace_back(0); + q_elliptic.emplace_back(0); + q_lookup_type.emplace_back(0); + q_aux.emplace_back(0); + } +} + +// Check for a sequence of variables that neighboring differences are at most 3 (used for batched range checks) +void UltraComposer::create_sort_constraint_with_edges(const std::vector& variable_index, + const fr& start, + const fr& end) +{ + ULTRA_SELECTOR_REFS + // Convenient to assume size is at least 8 (gate_width = 4) for separate gates for start and end conditions + constexpr size_t gate_width = ultra_settings::program_width; + ASSERT(variable_index.size() % gate_width == 0 && variable_index.size() > gate_width); + assert_valid_variables(variable_index); + + // enforce range checks of first row and starting at start + w_l.emplace_back(variable_index[0]); + w_r.emplace_back(variable_index[1]); + w_o.emplace_back(variable_index[2]); + w_4.emplace_back(variable_index[3]); + ++n; + q_m.emplace_back(0); + q_1.emplace_back(1); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(-start); + q_arith.emplace_back(1); + q_4.emplace_back(0); + q_fixed_base.emplace_back(0); + q_sort.emplace_back(1); + q_elliptic.emplace_back(0); + q_lookup_type.emplace_back(0); + q_aux.emplace_back(0); + // enforce range check for middle rows + for (size_t i = gate_width; i < variable_index.size() - gate_width; i += gate_width) { + + w_l.emplace_back(variable_index[i]); + w_r.emplace_back(variable_index[i + 1]); + w_o.emplace_back(variable_index[i + 2]); + w_4.emplace_back(variable_index[i + 3]); + ++n; + q_m.emplace_back(0); + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + q_4.emplace_back(0); + q_fixed_base.emplace_back(0); + q_sort.emplace_back(1); + q_elliptic.emplace_back(0); + q_lookup_type.emplace_back(0); + q_aux.emplace_back(0); + } + // enforce range checks of last row and ending at end + if (variable_index.size() > gate_width) { + w_l.emplace_back(variable_index[variable_index.size() - 4]); + w_r.emplace_back(variable_index[variable_index.size() - 3]); + w_o.emplace_back(variable_index[variable_index.size() - 2]); + w_4.emplace_back(variable_index[variable_index.size() - 1]); + ++n; + q_m.emplace_back(0); + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(0); + q_arith.emplace_back(0); + q_4.emplace_back(0); + q_fixed_base.emplace_back(0); + q_sort.emplace_back(1); + q_elliptic.emplace_back(0); + q_lookup_type.emplace_back(0); + q_aux.emplace_back(0); + } + + // dummy gate needed because of sort widget's check of next row + // use this gate to check end condition + w_l.emplace_back(variable_index[variable_index.size() - 1]); + w_r.emplace_back(zero_idx); + w_o.emplace_back(zero_idx); + w_4.emplace_back(zero_idx); + ++n; + q_m.emplace_back(0); + q_1.emplace_back(1); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_c.emplace_back(-end); + q_arith.emplace_back(1); + q_4.emplace_back(0); + q_fixed_base.emplace_back(0); + q_sort.emplace_back(0); + q_elliptic.emplace_back(0); + q_lookup_type.emplace_back(0); + q_aux.emplace_back(0); +} + +// range constraint a value by decomposing it into limbs whose size should be the default range constraint size +std::vector UltraComposer::decompose_into_default_range_better_for_oddlimbnum(const uint32_t variable_index, + const size_t num_bits, + std::string const& msg) +{ + std::vector sums; + const size_t limb_num = (size_t)num_bits / DEFAULT_PLOOKUP_RANGE_BITNUM; + const size_t last_limb_size = num_bits - (limb_num * DEFAULT_PLOOKUP_RANGE_BITNUM); + if (limb_num < 3) { + std::cerr + << "number of bits in range must be an integer multipe of DEFAULT_PLOOKUP_RANGE_BITNUM of size at least 3" + << std::endl; + return sums; + } + + const uint256_t val = (uint256_t)(get_variable(variable_index)); + // check witness value is indeed in range (commented out cause interferes with negative tests) + // ASSERT(val < ((uint256_t)1 << num_bits) - 1); // Q:ask Zac what happens with wrapping when converting fr to + // uint256 + // ASSERT(limb_num % 3 == 0); // TODO: write version of method that doesn't need this + std::vector val_limbs; + std::vector val_slices; + for (size_t i = 0; i < limb_num; i++) { + val_slices.emplace_back( + barretenberg::fr(val.slice(DEFAULT_PLOOKUP_RANGE_BITNUM * i, DEFAULT_PLOOKUP_RANGE_BITNUM * (i + 1) - 1))); + val_limbs.emplace_back(add_variable(val_slices[i])); + create_new_range_constraint(val_limbs[i], DEFAULT_PLOOKUP_RANGE_SIZE); + } + + uint64_t last_limb_range = ((uint64_t)1 << last_limb_size) - 1; + fr last_slice(0); + uint32_t last_limb(zero_idx); + size_t total_limb_num = limb_num; + if (last_limb_size > 0) { + val_slices.emplace_back(fr(val.slice(num_bits - last_limb_size, num_bits))); + val_limbs.emplace_back(add_variable(last_slice)); + create_new_range_constraint(last_limb, last_limb_range); + total_limb_num++; + } + // pad slices and limbs in case they are not 2 mod 3 + if (total_limb_num % 3 == 1) { + val_limbs.emplace_back(zero_idx); // TODO: check this is zero + val_slices.emplace_back(0); + total_limb_num++; + } + fr shift = fr(1 << DEFAULT_PLOOKUP_RANGE_BITNUM); + fr second_shift = shift * shift; + sums.emplace_back(add_variable(val_slices[0] + shift * val_slices[1] + second_shift * val_slices[2])); + create_big_add_gate({ val_limbs[0], val_limbs[1], val_limbs[2], sums[0], 1, shift, second_shift, -1, 0 }); + fr cur_shift = (shift * second_shift); + fr cur_second_shift = cur_shift * shift; + for (size_t i = 3; i < total_limb_num; i = i + 2) { + sums.emplace_back(add_variable(get_variable(sums[sums.size() - 1]) + cur_shift * val_slices[i] + + cur_second_shift * val_slices[i + 1])); + create_big_add_gate({ sums[sums.size() - 2], + val_limbs[i], + val_limbs[i + 1], + sums[sums.size() - 1], + 1, + cur_shift, + cur_second_shift, + -1, + 0 }); + cur_shift *= second_shift; + cur_second_shift *= second_shift; + } + assert_equal(sums[sums.size() - 1], variable_index, msg); + return sums; +} + +/** + * @brief Enable the auxilary gate of particular type + * + * @details If we have several operations being performed do not require parametrization + * (if we put each of them into a separate widget they would not require any selectors other than the ones enabling the + * operation itself, for example q_special*(w_l-2*w_r)), we can group them all into one widget, by using a special + * selector q_aux for all of them and enabling each in particular, depending on the combination of standard selector + * values. So you can do: + * q_aux * (q_1 * q_2 * statement_1 + q_3 * q_4 * statement_2). q_1=q_2=1 would activate statement_1, while q_3=q_4=1 + * would activate statement_2 + * @param type + */ +void UltraComposer::apply_aux_selectors(const AUX_SELECTORS type) +{ + ULTRA_SELECTOR_REFS; + q_arith.emplace_back(0); + q_fixed_base.emplace_back(0); + q_aux.emplace_back(type == AUX_SELECTORS::NONE ? 0 : 1); + q_c.emplace_back(0); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + switch (type) { + case AUX_SELECTORS::LIMB_ACCUMULATE_1: { + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(1); + q_4.emplace_back(1); + q_m.emplace_back(0); + break; + } + case AUX_SELECTORS::LIMB_ACCUMULATE_2: { + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(1); + q_4.emplace_back(0); + q_m.emplace_back(1); + break; + } + case AUX_SELECTORS::NON_NATIVE_FIELD_1: { + q_1.emplace_back(0); + q_2.emplace_back(1); + q_3.emplace_back(1); + q_4.emplace_back(0); + q_m.emplace_back(0); + break; + } + case AUX_SELECTORS::NON_NATIVE_FIELD_2: { + q_1.emplace_back(0); + q_2.emplace_back(1); + q_3.emplace_back(0); + q_4.emplace_back(1); + q_m.emplace_back(0); + break; + } + case AUX_SELECTORS::NON_NATIVE_FIELD_3: { + q_1.emplace_back(0); + q_2.emplace_back(1); + q_3.emplace_back(0); + q_4.emplace_back(0); + q_m.emplace_back(1); + break; + } + case AUX_SELECTORS::CONSISTENT_SORTED_MEMORY_READ: { + // Memory read gate used with the sorted list of memory reads. + // Apply sorted memory read checks with the following additional check: + // 1. Assert that if index field across two gates does not change, the value field does not change. + // Used for ROM reads and RAM reads across write/read boundaries + q_1.emplace_back(1); + q_2.emplace_back(1); + q_3.emplace_back(0); + q_4.emplace_back(0); + q_m.emplace_back(0); + break; + } + case AUX_SELECTORS::SORTED_MEMORY_READ: { + // Memory read gate used with the sorted list of memory reads. + // 1. Validate adjacent index values across 2 gates increases by 0 or 1 + // 2. Validate record witness computation (r = index + \kappa timestamp * \kappa^2 * value) + // Used for ROM reads and RAM reads across read/write boundaries + q_1.emplace_back(1); + q_2.emplace_back(0); + q_3.emplace_back(1); + q_4.emplace_back(0); + q_m.emplace_back(0); + break; + } + case AUX_SELECTORS::MEMORY_TIMESTAMP_CORRECTNESS: { + q_1.emplace_back(1); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_4.emplace_back(1); + q_m.emplace_back(0); + break; + } + case AUX_SELECTORS::MEMORY_READ: { + // Memory read gate for reading/writing memory cells. + // Validates record witness computation (r = index + \kappa timestamp * \kappa^2 * value) + q_1.emplace_back(1); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_4.emplace_back(0); + q_m.emplace_back(1); // validate record witness is correctly computed + break; + } + default: { + q_1.emplace_back(0); + q_2.emplace_back(0); + q_3.emplace_back(0); + q_4.emplace_back(0); + q_m.emplace_back(0); + break; + } + } +} + +/** + * NON NATIVE FIELD METHODS + * + * Methods to efficiently apply constraints that evaluate non-native field multiplications + **/ + +/** + * Applies range constraints to two 70-bit limbs, splititng each into 5 14-bit sublimbs. + * We can efficiently chain together two 70-bit limb checks in 3 gates, using auxiliary gates + **/ +void UltraComposer::range_constrain_two_limbs(const uint32_t lo_idx, const uint32_t hi_idx) +{ + constexpr uint64_t SUBLIMB_SIZE = 1ULL << 14; + + const auto get_sublimbs = [&](const uint32_t& limb_idx) { + const uint256_t limb = get_variable(limb_idx); + constexpr uint256_t SUBLIMB_MASK = (uint256_t(1) << 14) - 1; + std::array sublimb_indices; + sublimb_indices[0] = add_variable(limb & SUBLIMB_MASK); + sublimb_indices[1] = add_variable((limb >> 14) & SUBLIMB_MASK); + sublimb_indices[2] = add_variable((limb >> 28) & SUBLIMB_MASK); + sublimb_indices[3] = add_variable((limb >> 42) & SUBLIMB_MASK); + sublimb_indices[4] = add_variable((limb >> 56) & SUBLIMB_MASK); + return sublimb_indices; + }; + + const std::array lo_sublimbs = get_sublimbs(lo_idx); + const std::array hi_sublimbs = get_sublimbs(hi_idx); + + w_l.emplace_back(lo_sublimbs[0]); + w_r.emplace_back(lo_sublimbs[1]); + w_o.emplace_back(lo_sublimbs[2]); + w_4.emplace_back(lo_idx); + + w_l.emplace_back(lo_sublimbs[3]); + w_r.emplace_back(lo_sublimbs[4]); + w_o.emplace_back(hi_sublimbs[0]); + w_4.emplace_back(hi_sublimbs[1]); + + w_l.emplace_back(hi_sublimbs[2]); + w_r.emplace_back(hi_sublimbs[3]); + w_o.emplace_back(hi_sublimbs[4]); + w_4.emplace_back(hi_idx); + + apply_aux_selectors(AUX_SELECTORS::LIMB_ACCUMULATE_1); + apply_aux_selectors(AUX_SELECTORS::LIMB_ACCUMULATE_2); + apply_aux_selectors(AUX_SELECTORS::NONE); + n += 3; + + create_new_range_constraint(lo_sublimbs[0], SUBLIMB_SIZE - 1); + create_new_range_constraint(lo_sublimbs[1], SUBLIMB_SIZE - 1); + create_new_range_constraint(lo_sublimbs[2], SUBLIMB_SIZE - 1); + create_new_range_constraint(lo_sublimbs[3], SUBLIMB_SIZE - 1); + create_new_range_constraint(lo_sublimbs[4], SUBLIMB_SIZE - 1); + + create_new_range_constraint(hi_sublimbs[0], SUBLIMB_SIZE - 1); + create_new_range_constraint(hi_sublimbs[1], SUBLIMB_SIZE - 1); + create_new_range_constraint(hi_sublimbs[2], SUBLIMB_SIZE - 1); + create_new_range_constraint(hi_sublimbs[3], SUBLIMB_SIZE - 1); + create_new_range_constraint(hi_sublimbs[4], SUBLIMB_SIZE - 1); +}; + +std::array UltraComposer::decompose_non_native_field_double_width_limb(const uint32_t limb_idx) +{ + constexpr barretenberg::fr LIMB_MASK = (uint256_t(1) << 68) - 1; + const uint256_t value = get_variable(limb_idx); + const uint256_t low = value & LIMB_MASK; + const uint256_t hi = value >> 68; + ASSERT(low + (hi << 68) == value); + + const uint32_t low_idx = add_variable(low); + const uint32_t hi_idx = add_variable(hi); + + range_constrain_two_limbs(low_idx, hi_idx); + + return std::array{ low_idx, hi_idx }; +} + +/** + * NON NATIVE FIELD MULTIPLICATION CUSTOM GATE SEQUENCE + * + * This method will evaluate the equation (a * b = q * p + r) + * Where a, b, q, r are all emulated non-native field elements that are each split across 4 distinct witness variables + * + * The non-native field modulus, p, is a circuit constant + * + * The return value are the witness indices of the two remainder limbs `lo_1, hi_2` + * + * N.B. this method does NOT evaluate the prime field component of non-native field multiplications + **/ +std::array UltraComposer::evaluate_non_native_field_multiplication( + const non_native_field_witnesses& input, + const bool range_constrain_quotient_and_remainder, + const bool range_constrain_outputs) +{ + + std::array a{ + get_variable(input.a[0]), + get_variable(input.a[1]), + get_variable(input.a[2]), + get_variable(input.a[3]), + }; + std::array b{ + get_variable(input.b[0]), + get_variable(input.b[1]), + get_variable(input.b[2]), + get_variable(input.b[3]), + }; + std::array q{ + get_variable(input.q[0]), + get_variable(input.q[1]), + get_variable(input.q[2]), + get_variable(input.q[3]), + }; + std::array r{ + get_variable(input.r[0]), + get_variable(input.r[1]), + get_variable(input.r[2]), + get_variable(input.r[3]), + }; + + constexpr barretenberg::fr LIMB_SHIFT = uint256_t(1) << 68; + constexpr barretenberg::fr LIMB_SHIFT_2 = uint256_t(1) << 136; + constexpr barretenberg::fr LIMB_SHIFT_3 = uint256_t(1) << 204; + constexpr barretenberg::fr LIMB_RSHIFT = barretenberg::fr(1) / barretenberg::fr(uint256_t(1) << 68); + constexpr barretenberg::fr LIMB_RSHIFT_2 = barretenberg::fr(1) / barretenberg::fr(uint256_t(1) << 136); + + barretenberg::fr lo_0 = a[0] * b[0] - r[0] + (a[1] * b[0] + a[0] * b[1]) * LIMB_SHIFT; + barretenberg::fr lo_1 = (lo_0 + q[0] * input.neg_modulus[0] + + (q[1] * input.neg_modulus[0] + q[0] * input.neg_modulus[1] - r[1]) * LIMB_SHIFT) * + LIMB_RSHIFT_2; + + barretenberg::fr hi_0 = a[2] * b[0] + a[0] * b[2] + (a[0] * b[3] + a[3] * b[0] - r[3]) * LIMB_SHIFT; + barretenberg::fr hi_1 = hi_0 + a[1] * b[1] - r[2] + (a[1] * b[2] + a[2] * b[1]) * LIMB_SHIFT; + barretenberg::fr hi_2 = (hi_1 + lo_1 + q[2] * input.neg_modulus[0] + + (q[3] * input.neg_modulus[0] + q[2] * input.neg_modulus[1]) * LIMB_SHIFT); + barretenberg::fr hi_3 = (hi_2 + (q[0] * input.neg_modulus[3] + q[1] * input.neg_modulus[2]) * LIMB_SHIFT + + (q[0] * input.neg_modulus[2] + q[1] * input.neg_modulus[1])) * + LIMB_RSHIFT_2; + + const uint32_t lo_0_idx = add_variable(lo_0); + const uint32_t lo_1_idx = add_variable(lo_1); + const uint32_t hi_0_idx = add_variable(hi_0); + const uint32_t hi_1_idx = add_variable(hi_1); + const uint32_t hi_2_idx = add_variable(hi_2); + const uint32_t hi_3_idx = 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); + 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); + range_constrain_two_limbs(input.q[0], input.q[1]); + range_constrain_two_limbs(input.q[2], input.q[3]); + } + + // product gate 1 + // (lo_0 + q_0(p_0 + p_1*2^b) + q_1(p_0*2^b) - (r_1)2^b)2^-2b - lo_1 = 0 + create_big_add_gate({ input.q[0], + input.q[1], + input.r[1], + lo_1_idx, + input.neg_modulus[0] + input.neg_modulus[1] * LIMB_SHIFT, + input.neg_modulus[0] * LIMB_SHIFT, + -LIMB_SHIFT, + -LIMB_SHIFT.sqr(), + 0 }, + true); + + w_l.emplace_back(input.a[1]); + w_r.emplace_back(input.b[1]); + w_o.emplace_back(input.r[0]); + w_4.emplace_back(lo_0_idx); + apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_1); + ++n; + w_l.emplace_back(input.a[0]); + w_r.emplace_back(input.b[0]); + w_o.emplace_back(input.a[3]); + w_4.emplace_back(input.b[3]); + apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_2); + ++n; + w_l.emplace_back(input.a[2]); + w_r.emplace_back(input.b[2]); + w_o.emplace_back(input.r[3]); + w_4.emplace_back(hi_0_idx); + apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_3); + ++n; + w_l.emplace_back(input.a[1]); + w_r.emplace_back(input.b[1]); + w_o.emplace_back(input.r[2]); + w_4.emplace_back(hi_1_idx); + apply_aux_selectors(AUX_SELECTORS::NONE); + ++n; + + /** + * product gate 6 + * + * hi_2 - hi_1 - lo_1 - q[2](p[1].2^b + p[0]) - q[3](p[0].2^b) = 0 + * + **/ + create_big_add_gate( + { + input.q[2], + input.q[3], + lo_1_idx, + hi_1_idx, + -input.neg_modulus[1] * LIMB_SHIFT - input.neg_modulus[0], + -input.neg_modulus[0] * LIMB_SHIFT, + -1, + -1, + 0, + }, + true); + + /** + * product gate 7 + * + * hi_3 - (hi_2 - q[0](p[3].2^b + p[2]) - q[1](p[2].2^b + p[1])).2^-2b + **/ + create_big_add_gate({ + hi_3_idx, + input.q[0], + input.q[1], + hi_2_idx, + -1, + input.neg_modulus[3] * LIMB_RSHIFT + input.neg_modulus[2] * LIMB_RSHIFT_2, + input.neg_modulus[2] * LIMB_RSHIFT + input.neg_modulus[1] * LIMB_RSHIFT_2, + LIMB_RSHIFT_2, + 0, + }); + + // Sometimes we may want to apply this step separately, after adding additional terms into lo_1 and hi_2 + // For example, if we want to evaluate a field multiplication combined with several field additions. + if (range_constrain_outputs) { + range_constrain_two_limbs(hi_3_idx, lo_1_idx); + } + + return std::array{ lo_1_idx, hi_3_idx }; +} + +/** + * Compute the limb-multiplication part of a non native field mul + * + * i.e. compute the low 204 and high 204 bit components of `a * b` where `a, b` are nnf elements composed of 4 68-bit + *limbs + * + **/ +std::array UltraComposer::evaluate_partial_non_native_field_multiplication( + const non_native_field_witnesses& input) +{ + + std::array a{ + get_variable(input.a[0]), + get_variable(input.a[1]), + get_variable(input.a[2]), + get_variable(input.a[3]), + }; + std::array b{ + get_variable(input.b[0]), + get_variable(input.b[1]), + get_variable(input.b[2]), + get_variable(input.b[3]), + }; + + constexpr barretenberg::fr LIMB_SHIFT = uint256_t(1) << 68; + + barretenberg::fr lo_0 = a[0] * b[0] + (a[1] * b[0] + a[0] * b[1]) * LIMB_SHIFT; + + barretenberg::fr hi_0 = a[2] * b[0] + a[0] * b[2] + (a[0] * b[3] + a[3] * b[0]) * LIMB_SHIFT; + barretenberg::fr hi_1 = hi_0 + a[1] * b[1] + (a[1] * b[2] + a[2] * b[1]) * LIMB_SHIFT; + + const uint32_t lo_0_idx = add_variable(lo_0); + const uint32_t hi_0_idx = add_variable(hi_0); + const uint32_t hi_1_idx = add_variable(hi_1); + + w_l.emplace_back(input.a[1]); + w_r.emplace_back(input.b[1]); + w_o.emplace_back(zero_idx); + w_4.emplace_back(lo_0_idx); + apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_1); + ++n; + w_l.emplace_back(input.a[0]); + w_r.emplace_back(input.b[0]); + w_o.emplace_back(input.a[3]); + w_4.emplace_back(input.b[3]); + apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_2); + ++n; + w_l.emplace_back(input.a[2]); + w_r.emplace_back(input.b[2]); + w_o.emplace_back(zero_idx); + w_4.emplace_back(hi_0_idx); + apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_3); + ++n; + w_l.emplace_back(input.a[1]); + w_r.emplace_back(input.b[1]); + w_o.emplace_back(zero_idx); + w_4.emplace_back(hi_1_idx); + apply_aux_selectors(AUX_SELECTORS::NONE); + ++n; + return std::array{ lo_0_idx, hi_1_idx }; +} + +/** + * Uses a sneaky extra mini-addition gate in `plookup_arithmetic_widget.hpp` to add two non-native + * field elements in 4 gates (would normally take 5) + **/ +std::array UltraComposer::evaluate_non_native_field_addition( + add_simple limb0, + add_simple limb1, + add_simple limb2, + add_simple limb3, + std::tuple limbp) +{ + const auto& x_0 = std::get<0>(limb0).first; + const auto& x_1 = std::get<0>(limb1).first; + const auto& x_2 = std::get<0>(limb2).first; + const auto& x_3 = std::get<0>(limb3).first; + const auto& x_p = std::get<0>(limbp); + + const auto& x_mulconst0 = std::get<0>(limb0).second; + const auto& x_mulconst1 = std::get<0>(limb1).second; + const auto& x_mulconst2 = std::get<0>(limb2).second; + const auto& x_mulconst3 = std::get<0>(limb3).second; + + const auto& y_0 = std::get<1>(limb0).first; + const auto& y_1 = std::get<1>(limb1).first; + const auto& y_2 = std::get<1>(limb2).first; + const auto& y_3 = std::get<1>(limb3).first; + const auto& y_p = std::get<1>(limbp); + + const auto& y_mulconst0 = std::get<1>(limb0).second; + const auto& y_mulconst1 = std::get<1>(limb1).second; + const auto& y_mulconst2 = std::get<1>(limb2).second; + const auto& y_mulconst3 = std::get<1>(limb3).second; + + // constant additive terms + const auto& addconst0 = std::get<2>(limb0); + const auto& addconst1 = std::get<2>(limb1); + const auto& addconst2 = std::get<2>(limb2); + const auto& addconst3 = std::get<2>(limb3); + const auto& addconstp = std::get<2>(limbp); + + // get value of result limbs + const auto z_0value = get_variable(x_0) * x_mulconst0 + get_variable(y_0) * y_mulconst0 + addconst0; + const auto z_1value = get_variable(x_1) * x_mulconst1 + get_variable(y_1) * y_mulconst1 + addconst1; + const auto z_2value = get_variable(x_2) * x_mulconst2 + get_variable(y_2) * y_mulconst2 + addconst2; + const auto z_3value = get_variable(x_3) * x_mulconst3 + get_variable(y_3) * y_mulconst3 + addconst3; + const auto z_pvalue = get_variable(x_p) + get_variable(y_p) + addconstp; + + const auto z_0 = add_variable(z_0value); + const auto z_1 = add_variable(z_1value); + const auto z_2 = add_variable(z_2value); + const auto z_3 = add_variable(z_3value); + const auto z_p = add_variable(z_pvalue); + + ULTRA_SELECTOR_REFS + + /** + * we want the following layout in program memory + * (x - y = z) + * + * | 1 | 2 | 3 | 4 | + * |-----|-----|-----|-----| + * | y.p | x.0 | y.0 | x.p | (b.p + c.p - a.p = 0) AND (a.0 - b.0 - c.0 = 0) + * | z.p | x.1 | y.1 | z.0 | (a.1 - b.1 - c.1 = 0) + * | x.2 | y.2 | z.2 | z.1 | (a.2 - b.2 - c.2 = 0) + * | x.3 | y.3 | z.3 | --- | (a.3 - b.3 - c.3 = 0) + * + * By setting `q_arith` to `3`, we can validate `x_p + y_p + q_m = z_p` + **/ + // GATE 1 + w_l.emplace_back(y_p); + w_r.emplace_back(x_0); + w_o.emplace_back(y_0); + w_4.emplace_back(x_p); + w_l.emplace_back(z_p); + w_r.emplace_back(x_1); + w_o.emplace_back(y_1); // | 1 | 2 | 3 | 4 | + w_4.emplace_back(z_0); // |-----|-----|-----|-----| + w_l.emplace_back(x_2); // | y.p | x.0 | y.0 | z.p | (b.p + b.p - c.p = 0) AND (a.0 + b.0 - c.0 = 0) + w_r.emplace_back(y_2); // | x.p | x.1 | y.1 | z.0 | (a.1 + b.1 - c.1 = 0) + w_o.emplace_back(z_2); // | x.2 | y.2 | z.2 | z.1 | (a.2 + b.2 - c.2 = 0) + w_4.emplace_back(z_1); // | x.3 | y.3 | z.3 | --- | (a.3 + b.3 - c.3 = 0) + w_l.emplace_back(x_3); + w_r.emplace_back(y_3); + w_o.emplace_back(z_3); + w_4.emplace_back(zero_idx); + + q_m.emplace_back(addconstp); + q_1.emplace_back(0); + q_2.emplace_back(-x_mulconst0 * + 2); // scale constants by 2. If q_arith = 3 then w_4_omega value (z0) gets scaled by 2x + q_3.emplace_back(-y_mulconst0 * 2); // z_0 - (x_0 * -xmulconst0) - (y_0 * ymulconst0) = 0 => z_0 = x_0 + y_0 + q_4.emplace_back(0); + q_c.emplace_back(-addconst0 * 2); + q_arith.emplace_back(3); + + q_m.emplace_back(0); + q_1.emplace_back(0); + q_2.emplace_back(-x_mulconst1); + q_3.emplace_back(-y_mulconst1); + q_4.emplace_back(0); + q_c.emplace_back(-addconst1); + q_arith.emplace_back(2); + + q_m.emplace_back(0); + q_1.emplace_back(-x_mulconst2); + q_2.emplace_back(-y_mulconst2); + q_3.emplace_back(1); + q_4.emplace_back(0); + q_c.emplace_back(-addconst2); + q_arith.emplace_back(1); + + q_m.emplace_back(0); + q_1.emplace_back(-x_mulconst3); + q_2.emplace_back(-y_mulconst3); + q_3.emplace_back(1); + q_4.emplace_back(0); + q_c.emplace_back(-addconst3); + q_arith.emplace_back(1); + + for (size_t i = 0; i < 4; ++i) { + q_fixed_base.emplace_back(0); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + } + + n += 4; + return std::array{ + z_0, z_1, z_2, z_3, z_p, + }; +} + +std::array UltraComposer::evaluate_non_native_field_subtraction( + add_simple limb0, + add_simple limb1, + add_simple limb2, + add_simple limb3, + std::tuple limbp) +{ + const auto& x_0 = std::get<0>(limb0).first; + const auto& x_1 = std::get<0>(limb1).first; + const auto& x_2 = std::get<0>(limb2).first; + const auto& x_3 = std::get<0>(limb3).first; + const auto& x_p = std::get<0>(limbp); + + const auto& x_mulconst0 = std::get<0>(limb0).second; + const auto& x_mulconst1 = std::get<0>(limb1).second; + const auto& x_mulconst2 = std::get<0>(limb2).second; + const auto& x_mulconst3 = std::get<0>(limb3).second; + + const auto& y_0 = std::get<1>(limb0).first; + const auto& y_1 = std::get<1>(limb1).first; + const auto& y_2 = std::get<1>(limb2).first; + const auto& y_3 = std::get<1>(limb3).first; + const auto& y_p = std::get<1>(limbp); + + const auto& y_mulconst0 = std::get<1>(limb0).second; + const auto& y_mulconst1 = std::get<1>(limb1).second; + const auto& y_mulconst2 = std::get<1>(limb2).second; + const auto& y_mulconst3 = std::get<1>(limb3).second; + + // constant additive terms + const auto& addconst0 = std::get<2>(limb0); + const auto& addconst1 = std::get<2>(limb1); + const auto& addconst2 = std::get<2>(limb2); + const auto& addconst3 = std::get<2>(limb3); + const auto& addconstp = std::get<2>(limbp); + + // get value of result limbs + const auto z_0value = get_variable(x_0) * x_mulconst0 - get_variable(y_0) * y_mulconst0 + addconst0; + const auto z_1value = get_variable(x_1) * x_mulconst1 - get_variable(y_1) * y_mulconst1 + addconst1; + const auto z_2value = get_variable(x_2) * x_mulconst2 - get_variable(y_2) * y_mulconst2 + addconst2; + const auto z_3value = get_variable(x_3) * x_mulconst3 - get_variable(y_3) * y_mulconst3 + addconst3; + const auto z_pvalue = get_variable(x_p) - get_variable(y_p) + addconstp; + + const auto z_0 = add_variable(z_0value); + const auto z_1 = add_variable(z_1value); + const auto z_2 = add_variable(z_2value); + const auto z_3 = add_variable(z_3value); + const auto z_p = add_variable(z_pvalue); + + ULTRA_SELECTOR_REFS + + /** + * we want the following layout in program memory + * (x - y = z) + * + * | 1 | 2 | 3 | 4 | + * |-----|-----|-----|-----| + * | y.p | x.0 | y.0 | z.p | (b.p + c.p - a.p = 0) AND (a.0 - b.0 - c.0 = 0) + * | x.p | x.1 | y.1 | z.0 | (a.1 - b.1 - c.1 = 0) + * | x.2 | y.2 | z.2 | z.1 | (a.2 - b.2 - c.2 = 0) + * | x.3 | y.3 | z.3 | --- | (a.3 - b.3 - c.3 = 0) + * + **/ + // GATE 1 + w_l.emplace_back(y_p); + w_r.emplace_back(x_0); + w_o.emplace_back(y_0); + w_4.emplace_back(z_p); + w_l.emplace_back(x_p); + w_r.emplace_back(x_1); + w_o.emplace_back(y_1); // | 1 | 2 | 3 | 4 | + w_4.emplace_back(z_0); // |-----|-----|-----|-----| + w_l.emplace_back(x_2); // | y.p | x.0 | y.0 | z.p | (b.p + c.p - a.p = 0) AND (a.0 - b.0 - c.0 = 0) + w_r.emplace_back(y_2); // | x.p | x.1 | y.1 | z.0 | (a.1 - b.1 - c.1 = 0) + w_o.emplace_back(z_2); // | x.2 | y.2 | z.2 | z.1 | (a.2 - b.2 - c.2 = 0) + w_4.emplace_back(z_1); // | x.3 | y.3 | z.3 | --- | (a.3 - b.3 - c.3 = 0) + w_l.emplace_back(x_3); + w_r.emplace_back(y_3); + w_o.emplace_back(z_3); + w_4.emplace_back(zero_idx); + + q_m.emplace_back(-addconstp); + q_1.emplace_back(0); + q_2.emplace_back(-x_mulconst0 * 2); + q_3.emplace_back(y_mulconst0 * 2); // z_0 + (x_0 * -xmulconst0) + (y_0 * ymulconst0) = 0 => z_0 = x_0 - y_0 + q_4.emplace_back(0); + q_c.emplace_back(-addconst0 * 2); + q_arith.emplace_back(3); + + q_m.emplace_back(0); + q_1.emplace_back(0); + q_2.emplace_back(-x_mulconst1); + q_3.emplace_back(y_mulconst1); + q_4.emplace_back(0); + q_c.emplace_back(-addconst1); + q_arith.emplace_back(2); + + q_m.emplace_back(0); + q_1.emplace_back(-x_mulconst2); + q_2.emplace_back(y_mulconst2); + q_3.emplace_back(1); + q_4.emplace_back(0); + q_c.emplace_back(-addconst2); + q_arith.emplace_back(1); + + q_m.emplace_back(0); + q_1.emplace_back(-x_mulconst3); + q_2.emplace_back(y_mulconst3); + q_3.emplace_back(1); + q_4.emplace_back(0); + q_c.emplace_back(-addconst3); + q_arith.emplace_back(1); + + for (size_t i = 0; i < 4; ++i) { + q_fixed_base.emplace_back(0); + q_sort.emplace_back(0); + q_lookup_type.emplace_back(0); + q_elliptic.emplace_back(0); + q_aux.emplace_back(0); + } + + n += 4; + return std::array{ + z_0, z_1, z_2, z_3, z_p, + }; +} + +void UltraComposer::create_memory_gate(MemoryRecord& record) +{ + // Record wire value can't yet be computed + record.record_witness = add_variable(0); + apply_aux_selectors(AUX_SELECTORS::MEMORY_READ); + w_l.emplace_back(record.index_witness); + w_r.emplace_back(record.timestamp_witness); + w_o.emplace_back(record.value_witness); + w_4.emplace_back(record.record_witness); + record.gate_index = n; + ++n; +} + +void UltraComposer::create_sorted_memory_gate(MemoryRecord& record, const bool is_ram_transition_or_rom) +{ + record.record_witness = add_variable(0); + apply_aux_selectors(is_ram_transition_or_rom ? AUX_SELECTORS::CONSISTENT_SORTED_MEMORY_READ + : AUX_SELECTORS::SORTED_MEMORY_READ); + w_l.emplace_back(record.index_witness); + w_r.emplace_back(record.timestamp_witness); + w_o.emplace_back(record.value_witness); + w_4.emplace_back(record.record_witness); + + record.gate_index = n; + ++n; +} + +/** + * @brief Create a new memory region + * + * @details Creates a transcript object, where the inside memory state array is filled with "uninitialized memory" and + * and empty memory record array. Puts this object into the vector of ROM arrays. + * + * @param array_size The size of region in elements + * @return size_t The index of the element + */ +size_t UltraComposer::create_ROM_array(const size_t array_size) +{ + MemoryTranscript new_transcript; + for (size_t i = 0; i < array_size; ++i) { + new_transcript.state.emplace_back( + std::array{ UNINITIALIZED_MEMORY_RECORD, UNINITIALIZED_MEMORY_RECORD }); + } + rom_arrays.emplace_back(new_transcript); + return rom_arrays.size() - 1; +} + +/** + * Initialize a ROM cell to equal `value_witness` + * `index_value` is a RAW VALUE that describes the cell index. It is NOT a witness + * When intializing ROM arrays, it is important that the index of the cell is known when compiling the circuit. + * This ensures that, for a given circuit, we know with 100% certainty that EVERY rom cell is initialized + **/ + +/** + * @brief Initialize a rom cell to equal `value_witness` + * + * @param rom_id The index of the ROM array, which cell we are initializing + * @param index_value The index of the cell within the array (an actual index, not a witness index) + * @param value_witness The index of the witness with the value that should be in the + */ +void UltraComposer::set_ROM_element(const size_t rom_id, const size_t index_value, const uint32_t value_witness) +{ + ASSERT(rom_arrays.size() > rom_id); + auto& rom_array = rom_arrays[rom_id]; + const uint32_t index_witness = (index_value == 0) ? zero_idx : put_constant_variable((uint64_t)index_value); + ASSERT(rom_array.state.size() > index_value); + ASSERT(rom_array.state[index_value][0] == UNINITIALIZED_MEMORY_RECORD); + /** + * The structure MemoryRecord contains the following members in this order: + * uint32_t index_witness; + * uint32_t timestamp_witness; + * uint32_t value_witness; + * uint32_t index; + * uint32_t timestamp; + * uint32_t record_witness; + * size_t gate_index; + * The second initialization value here is the witness, because in ROM it doesn't matter. We will decouple this + * logic later. + */ + MemoryRecord new_record{ + index_witness, value_witness, zero_idx, static_cast(index_value), 0, 0, 0, + }; + rom_array.state[index_value][0] = value_witness; + rom_array.state[index_value][1] = zero_idx; + create_memory_gate(new_record); + rom_array.records.emplace_back(new_record); +} + +void UltraComposer::set_ROM_element_pair(const size_t rom_id, + const size_t index_value, + const std::array& value_witnesses) +{ + ASSERT(rom_arrays.size() > rom_id); + auto& rom_array = rom_arrays[rom_id]; + const uint32_t index_witness = (index_value == 0) ? zero_idx : put_constant_variable((uint64_t)index_value); + ASSERT(rom_array.state.size() > index_value); + ASSERT(rom_array.state[index_value][0] == UNINITIALIZED_MEMORY_RECORD); + MemoryRecord new_record{ + index_witness, value_witnesses[0], value_witnesses[1], static_cast(index_value), 0, 0, 0, + }; + rom_array.state[index_value][0] = value_witnesses[0]; + rom_array.state[index_value][1] = value_witnesses[1]; + create_memory_gate(new_record); + rom_array.records.emplace_back(new_record); +} + +uint32_t UltraComposer::read_ROM_array(const size_t rom_id, const uint32_t index_witness) +{ + ASSERT(rom_arrays.size() > rom_id); + auto& rom_array = rom_arrays[rom_id]; + const uint32_t index = static_cast(uint256_t(get_variable(index_witness))); + ASSERT(rom_array.state.size() > index); + ASSERT(rom_array.state[index][0] != UNINITIALIZED_MEMORY_RECORD); + const auto value = get_variable(rom_array.state[index][0]); + const uint32_t value_witness = add_variable(value); + MemoryRecord new_record{ + index_witness, value_witness, zero_idx, index, 0, 0, 0, + }; + create_memory_gate(new_record); + rom_array.records.emplace_back(new_record); + + // create_read_gate + return value_witness; +} + +std::array UltraComposer::read_ROM_array_pair(const size_t rom_id, const uint32_t index_witness) +{ + std::array value_witnesses; + + const uint32_t index = static_cast(uint256_t(get_variable(index_witness))); + ASSERT(rom_arrays.size() > rom_id); + auto& rom_array = rom_arrays[rom_id]; + ASSERT(rom_array.state.size() > index); + ASSERT(rom_array.state[index][0] != UNINITIALIZED_MEMORY_RECORD); + ASSERT(rom_array.state[index][1] != UNINITIALIZED_MEMORY_RECORD); + const auto value1 = get_variable(rom_array.state[index][0]); + const auto value2 = get_variable(rom_array.state[index][1]); + value_witnesses[0] = add_variable(value1); + value_witnesses[1] = add_variable(value2); + MemoryRecord new_record{ + index_witness, value_witnesses[0], value_witnesses[1], index, 0, 0, 0, + }; + + create_memory_gate(new_record); + rom_array.records.emplace_back(new_record); + + // create_read_gate + return value_witnesses; +} + +void UltraComposer::process_ROM_array(const size_t rom_id, const size_t gate_offset_from_public_inputs) +{ + auto& rom_array = rom_arrays[rom_id]; + const auto read_tag = get_new_tag(); // current_tag + 1; + const auto sorted_list_tag = get_new_tag(); // current_tag + 2; + create_tag(read_tag, sorted_list_tag); + create_tag(sorted_list_tag, read_tag); + + // Make sure that every cell has been initialized + for (size_t i = 0; i < rom_array.state.size(); ++i) { + if (rom_array.state[i][0] == UNINITIALIZED_MEMORY_RECORD) { + set_ROM_element_pair(rom_id, static_cast(i), { zero_idx, zero_idx }); + } + } + +#ifdef NO_TBB + std::sort(rom_array.records.begin(), rom_array.records.end()); +#else + std::sort(std::execution::par_unseq, rom_array.records.begin(), rom_array.records.end()); +#endif + + for (const auto& record : rom_array.records) { + const auto index = record.index; + const auto value1 = get_variable(record.timestamp_witness); + const auto value2 = get_variable(record.value_witness); + const auto index_witness = add_variable(fr((uint64_t)index)); + const auto value1_witness = add_variable(value1); + const auto value2_witness = add_variable(value2); + MemoryRecord sorted_record{ + index_witness, value1_witness, value2_witness, index, 0, 0, 0, + }; + create_sorted_memory_gate(sorted_record, true); + assign_tag(record.record_witness, read_tag); + assign_tag(sorted_record.record_witness, sorted_list_tag); + + memory_records.push_back(static_cast(sorted_record.gate_index + gate_offset_from_public_inputs)); + memory_records.push_back(static_cast(record.gate_index + gate_offset_from_public_inputs)); + } + // One of the checks we run on the sorted list, is to validate the difference between + // the index field across two gates is either 0 or 1. + // If we add a dummy gate at the end of the sorted list, where we force the first wire to + // equal `m + 1`, where `m` is the maximum allowed index in the sorted list, + // we have validated that all ROM reads are correctly constrained + fr max_index_value((uint64_t)rom_array.state.size()); + uint32_t max_index = add_variable(max_index_value); + create_big_add_gate({ + max_index, + zero_idx, + zero_idx, + zero_idx, + 1, + 0, + 0, + 0, + -max_index_value, + }); + // N.B. If the above check holds, we know the sorted list begins with an index value of 0, + // because the first cell is explicitly initialized using zero_idx as the index field. +} + +void UltraComposer::process_ROM_arrays(const size_t gate_offset_from_public_inputs) +{ + for (size_t i = 0; i < rom_arrays.size(); ++i) { + process_ROM_array(i, gate_offset_from_public_inputs); + } +} +} // namespace waffle diff --git a/cpp/src/aztec/plonk/composer/ultra_composer.hpp b/cpp/src/aztec/plonk/composer/ultra_composer.hpp new file mode 100644 index 0000000000..b899e6b91c --- /dev/null +++ b/cpp/src/aztec/plonk/composer/ultra_composer.hpp @@ -0,0 +1,585 @@ +#pragma once +#include "composer_base.hpp" +#include "plookup_tables/plookup_tables.hpp" +#include "../proof_system/types/polynomial_manifest.hpp" + +namespace waffle { + +class UltraComposer : public ComposerBase { + + public: + static constexpr ComposerType type = ComposerType::PLOOKUP; + static constexpr MerkleHashType merkle_hash_type = MerkleHashType::FIXED_BASE_PEDERSEN; + static constexpr size_t NUM_RESERVED_GATES = 4; // This must be >= num_roots_cut_out_of_vanishing_polynomial + // See the comment in plonk/proof_system/prover/prover.cpp + // ProverBase::compute_quotient_commitments() for why 4 exactly. + static constexpr size_t UINT_LOG2_BASE = 6; // DOCTODO: explain what this is, or rename. + // The plookup range proof requires work linear in range size, thus cannot be used directly for + // large ranges such as 2^64. For such ranges the element will be decomposed into smaller + // chuncks according to the parameter below + static constexpr size_t DEFAULT_PLOOKUP_RANGE_BITNUM = 9; + static constexpr size_t DEFAULT_PLOOKUP_RANGE_STEP_SIZE = 3; + static constexpr size_t DEFAULT_PLOOKUP_RANGE_SIZE = (1 << DEFAULT_PLOOKUP_RANGE_BITNUM) - 1; + static constexpr uint32_t UNINITIALIZED_MEMORY_RECORD = UINT32_MAX; + + 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; + barretenberg::fr modulus; + }; + + enum AUX_SELECTORS { + NONE, + LIMB_ACCUMULATE_1, + LIMB_ACCUMULATE_2, + NON_NATIVE_FIELD_1, + NON_NATIVE_FIELD_2, + NON_NATIVE_FIELD_3, + CONSISTENT_SORTED_MEMORY_READ, + SORTED_MEMORY_READ, + MEMORY_TIMESTAMP_CORRECTNESS, + MEMORY_READ, + }; + + struct RangeList { + uint64_t target_range; + uint32_t range_tag; + uint32_t tau_tag; + std::vector variable_indices; + }; + + /** + * @brief A memory record that can be ordered + * + * + */ + struct MemoryRecord { + uint32_t index_witness; + uint32_t timestamp_witness; + uint32_t value_witness; + uint32_t index; + uint32_t timestamp; + uint32_t record_witness; + size_t gate_index; + bool operator<(const MemoryRecord& other) const + { + bool index_test = (index) < (other.index); + return index_test || (index == other.index && timestamp < other.timestamp); + } + }; + + /** + * @brief Each rom array is an instance of memory transcript. It saves values and indexes for a particular memory + * array + * + * + */ + struct MemoryTranscript { + // Contains the value of each index of the array + std::vector> state; + + // A vector of records, each of which contains: + // + The constant witness with the index + // + The value in the memory slot + // + The actual index value + std::vector records; + }; + + enum UltraSelectors { QM, QC, Q1, Q2, Q3, Q4, QARITH, QFIXED, QSORT, QELLIPTIC, QAUX, QLOOKUPTYPE, NUM }; + + UltraComposer(); + UltraComposer(std::string const& crs_path, const size_t size_hint = 0); + UltraComposer(std::shared_ptr const& crs_factory, const size_t size_hint = 0); + UltraComposer(std::shared_ptr const& p_key, + std::shared_ptr const& v_key, + size_t size_hint = 0); + UltraComposer(UltraComposer&& other) = default; + UltraComposer& operator=(UltraComposer&& other) = default; + ~UltraComposer() {} + + std::shared_ptr compute_proving_key() override; + std::shared_ptr compute_verification_key() override; + void compute_witness() override; + + UltraProver create_prover(); + UltraVerifier create_verifier(); + + UnrolledUltraProver create_unrolled_prover(); + UnrolledUltraVerifier create_unrolled_verifier(); + + UnrolledUltraToStandardProver create_unrolled_ultra_to_standard_prover(); + UnrolledUltraToStandardVerifier create_unrolled_ultra_to_standard_verifier(); + + void create_add_gate(const add_triple& in) override; + + void create_big_add_gate(const add_quad& in, const bool use_next_gate_w_4 = false); + void create_big_add_gate_with_bit_extraction(const add_quad& in); + void create_big_mul_gate(const mul_quad& in); + void create_balanced_add_gate(const add_quad& in); + + void create_mul_gate(const mul_triple& in) override; + void create_bool_gate(const uint32_t a) override; + void create_poly_gate(const poly_triple& in) override; + void create_fixed_group_add_gate(const fixed_group_add_quad& in); + void create_fixed_group_add_gate_with_init(const fixed_group_add_quad& in, const fixed_group_init_quad& init); + void create_fixed_group_add_gate_final(const add_quad& in); + + void create_ecc_add_gate(const ecc_add_gate& in); + + void fix_witness(const uint32_t witness_index, const barretenberg::fr& witness_value); + + void add_recursive_proof(const std::vector& proof_output_witness_indices) + { + if (contains_recursive_proof) { + failure("added recursive proof when one already exists"); + } + contains_recursive_proof = true; + + for (const auto& idx : proof_output_witness_indices) { + set_public_input(idx); + recursive_proof_public_input_indices.push_back((uint32_t)(public_inputs.size() - 1)); + } + } + + void create_new_range_constraint(const uint32_t variable_index, const uint64_t target_range); + void create_range_constraint(const uint32_t variable_index, const size_t num_bits, std::string const&) + { + if (num_bits <= DEFAULT_PLOOKUP_RANGE_BITNUM) { + create_new_range_constraint(variable_index, 1ULL << num_bits); + } else { + decompose_into_default_range(variable_index, num_bits); + } + } + + accumulator_triple create_logic_constraint(const uint32_t a, + const uint32_t b, + const size_t num_bits, + bool is_xor_gate); + accumulator_triple create_and_constraint(const uint32_t a, const uint32_t b, const size_t num_bits); + accumulator_triple create_xor_constraint(const uint32_t a, const uint32_t b, const size_t num_bits); + + uint32_t put_constant_variable(const barretenberg::fr& variable); + + size_t get_num_constant_gates() const override { return 0; } + + /** + * @brief Get the final number of gates in a circuit, which consists of the sum of: + * 1) Current number number of actual gates + * 2) Number of public inputs, as we'll need to add a gate for each of them + * 3) Number of Rom array-associated gates + * 4) NUmber of range-list associated gates + * + * @return size_t + */ + virtual size_t get_num_gates() const override + { + size_t count = n; + size_t rangecount = 0; + size_t romcount = 0; + for (size_t i = 0; i < rom_arrays.size(); ++i) { + for (size_t j = 0; j < rom_arrays[i].state.size(); ++j) { + if (rom_arrays[i].state[j][0] == UNINITIALIZED_MEMORY_RECORD) { + romcount += 2; + } + } + romcount += (rom_arrays[i].records.size()); + romcount += 1; // we add an addition gate after procesing a rom array + } + + constexpr size_t gate_width = ultra_settings::program_width; + for (const auto& list : range_lists) { + auto list_size = list.second.variable_indices.size(); + size_t padding = (gate_width - (list.second.variable_indices.size() % gate_width)) % gate_width; + if (list.second.variable_indices.size() == gate_width) + padding += gate_width; + list_size += padding; + rangecount += (list_size / gate_width); + rangecount += 1; // we need to add 1 extra addition gates for every distinct range list + } + return count + romcount + rangecount; + } + + virtual void print_num_gates() const override + { + size_t count = n; + size_t rangecount = 0; + size_t romcount = 0; + for (size_t i = 0; i < rom_arrays.size(); ++i) { + for (size_t j = 0; j < rom_arrays[i].state.size(); ++j) { + if (rom_arrays[i].state[j][0] == UNINITIALIZED_MEMORY_RECORD) { + romcount += 2; + } + } + romcount += (rom_arrays[i].records.size()); + romcount += 1; // we add an addition gate after procesing a rom array + } + + constexpr size_t gate_width = ultra_settings::program_width; + for (const auto& list : range_lists) { + auto list_size = list.second.variable_indices.size(); + size_t padding = (gate_width - (list.second.variable_indices.size() % gate_width)) % gate_width; + if (list.second.variable_indices.size() == gate_width) + padding += gate_width; + list_size += padding; + rangecount += (list_size / gate_width); + rangecount += 1; // we need to add 1 extra addition gates for every distinct range list + } + size_t total = count + romcount + rangecount; + std::cout << "gates = " << total << " (arith " << count << ", rom " << romcount << ", range " << rangecount + << "), pubinp = " << public_inputs.size() << std::endl; + } + + void assert_equal_constant(const uint32_t a_idx, + const barretenberg::fr& b, + std::string const& msg = "assert equal constant") + { + if (variables[a_idx] != b && !failed()) { + failure(msg); + } + auto b_idx = put_constant_variable(b); + assert_equal(a_idx, b_idx, msg); + } + + /** + * Plookup Methods + **/ + void add_table_column_selector_poly_to_proving_key(polynomial& small, const std::string& tag); + void initialize_precomputed_table( + const plookup::BasicTableId id, + bool (*generator)(std::vector&, + std::vector&, + std::vector&), + std::array (*get_values_from_key)(const std::array)); + + plookup::BasicTable& get_table(const plookup::BasicTableId id); + plookup::MultiTable& create_table(const plookup::MultiTableId id); + + plookup::ReadData create_gates_from_plookup_accumulators( + const plookup::MultiTableId& id, + const plookup::ReadData& read_values, + const uint32_t key_a_index, + std::optional key_b_index = std::nullopt); + + /** + * Generalized Permutation Methods + **/ + std::vector decompose_into_default_range(const uint32_t variable_index, + const size_t num_bits, + const size_t target_range_bitnum = DEFAULT_PLOOKUP_RANGE_BITNUM, + std::string const& msg = "decompose_into_default_range"); + std::vector decompose_into_default_range_better_for_oddlimbnum( + const uint32_t variable_index, + const size_t num_bits, + std::string const& msg = "decompose_into_default_range_better_for_oddlimbnum"); + void create_dummy_constraints(const std::vector& variable_index); + void create_sort_constraint(const std::vector& variable_index); + void create_sort_constraint_with_edges(const std::vector& variable_index, + const barretenberg::fr&, + const barretenberg::fr&); + void assign_tag(const uint32_t variable_index, const uint32_t tag) + { + ASSERT(tag <= current_tag); + ASSERT(real_variable_tags[real_variable_index[variable_index]] == DUMMY_TAG); + real_variable_tags[real_variable_index[variable_index]] = tag; + } + + uint32_t create_tag(const uint32_t tag_index, const uint32_t tau_index) + { + tau.insert({ tag_index, tau_index }); + current_tag++; // Why exactly? + return current_tag; + } + + uint32_t get_new_tag() + { + current_tag++; + return current_tag; + } + + RangeList create_range_list(const uint64_t target_range); + void process_range_list(const RangeList& list); + void process_range_lists(); + + /** + * Custom Gate Selectors + **/ + void apply_aux_selectors(const AUX_SELECTORS type); + + /** + * Non Native Field Arithmetic + **/ + void range_constrain_two_limbs(const uint32_t lo_idx, const uint32_t hi_idx); + std::array decompose_non_native_field_double_width_limb(const uint32_t limb_idx); + std::array evaluate_non_native_field_multiplication( + const non_native_field_witnesses& input, + const bool range_constrain_quotient_and_remainder = true, + const bool range_constrain_remainders = true); + std::array evaluate_partial_non_native_field_multiplication(const non_native_field_witnesses& input); + typedef std::pair scaled_witness; + typedef std::tuple add_simple; + std::array evaluate_non_native_field_subtraction( + add_simple limb0, + add_simple limb1, + add_simple limb2, + add_simple limb3, + std::tuple limbp); + std::array evaluate_non_native_field_addition(add_simple limb0, + add_simple limb1, + add_simple limb2, + add_simple limb3, + std::tuple limbp); + + /** + * Memory + **/ + + // size_t create_RAM_array(const size_t array_size); + size_t create_ROM_array(const size_t array_size); + + void set_ROM_element(const size_t rom_id, const size_t index_value, const uint32_t value_witness); + void set_ROM_element_pair(const size_t rom_id, + const size_t index_value, + const std::array& value_witnesses); + uint32_t read_ROM_array(const size_t rom_id, const uint32_t index_witness); + std::array read_ROM_array_pair(const size_t rom_id, const uint32_t index_witness); + void create_memory_gate(MemoryRecord& record); + void create_sorted_memory_gate(MemoryRecord& record, const bool is_ram_transition_or_rom = false); + void process_ROM_array(const size_t rom_id, const size_t gate_offset_from_public_inputs); + void process_ROM_arrays(const size_t gate_offset_from_public_inputs); + + /** + * Member Variables + **/ + + uint32_t zero_idx = 0; + bool circuit_finalised = false; + + // This variable controls the amount with which the lookup table and witness values need to be shifted + // above to make room for adding randomness into the permutation and witness polynomials in the plookup widget. + // This must be (num_roots_cut_out_of_the_vanishing_polynomial - 1), since the variable num_roots_cut_out_of_ + // vanishing_polynomial cannot be trivially fetched here, I am directly setting this to 4 - 1 = 3. + static constexpr size_t s_randomness = 3; + + // these are variables that we have used a gate on, to enforce that they are equal to a defined value + std::map constant_variable_indices; + + std::vector lookup_tables; + std::vector lookup_multi_tables; + std::map range_lists; // DOCTODO: explain this. + + std::vector ram_arrays; // DOCTODO: explain this. + std::vector rom_arrays; // DOCTODO: explain this. + std::vector memory_records; // Used for ROM. + + std::vector recursive_proof_public_input_indices; + bool contains_recursive_proof = false; + + /** + * Program Manifests + **/ + + static transcript::Manifest create_manifest(const size_t num_public_inputs) + { + // add public inputs.... + constexpr size_t g1_size = 64; + constexpr size_t fr_size = 32; + const size_t public_input_size = fr_size * num_public_inputs; + const transcript::Manifest output = transcript::Manifest( + + { transcript::Manifest::RoundManifest( + { { "circuit_size", 4, true }, { "public_input_size", 4, true } }, "init", 1), + + transcript::Manifest::RoundManifest({ { "public_inputs", public_input_size, false }, + { "W_1", g1_size, false }, + { "W_2", g1_size, false }, + { "W_3", g1_size, false } }, + "eta", + 1), + + transcript::Manifest::RoundManifest({ { "W_4", g1_size, false }, { "S", g1_size, false } }, "beta", 2), + + transcript::Manifest::RoundManifest( + { { "Z_PERM", g1_size, false }, { "Z_LOOKUP", g1_size, false } }, "alpha", 1), + + transcript::Manifest::RoundManifest({ { "T_1", g1_size, false }, + { "T_2", g1_size, false }, + { "T_3", g1_size, false }, + { "T_4", g1_size, false } }, + "z", + 1), + + transcript::Manifest::RoundManifest( + { + { "w_1", fr_size, false, 0 }, + { "w_2", fr_size, false, 1 }, + { "w_3", fr_size, false, 2 }, + { "w_4", fr_size, false, 3 }, + { "sigma_1", fr_size, false, 4 }, + { "sigma_2", fr_size, false, 5 }, + { "sigma_3", fr_size, false, 6 }, + { "q_arith", fr_size, false, 7 }, + { "q_fixed_base", fr_size, false, 27 }, + { "q_aux", fr_size, false, 26 }, + { "q_1", fr_size, false, 25 }, + { "q_2", fr_size, false, 8 }, + { "q_3", fr_size, false, 9 }, + { "q_4", fr_size, false, 10 }, + { "q_m", fr_size, false, 11 }, + { "q_c", fr_size, false, 12 }, + { "table_value_1", fr_size, false, 13 }, + { "table_value_2", fr_size, false, 14 }, + { "table_value_3", fr_size, false, 15 }, + { "table_value_4", fr_size, false, 16 }, + { "table_type", fr_size, false, 17 }, + { "s", fr_size, false, 18 }, + { "z_lookup", fr_size, false, 19 }, + { "id_1", fr_size, false, 21 }, + { "id_2", fr_size, false, 22 }, + { "id_3", fr_size, false, 23 }, + { "id_4", fr_size, false, 24 }, + { "z_perm_omega", fr_size, false, -1 }, + { "w_1_omega", fr_size, false, 0 }, + { "w_2_omega", fr_size, false, 1 }, + { "w_3_omega", fr_size, false, 2 }, + { "w_4_omega", fr_size, false, 3 }, + { "table_value_1_omega", fr_size, false, 4 }, + { "table_value_2_omega", fr_size, false, 5 }, + { "table_value_3_omega", fr_size, false, 6 }, + { "table_value_4_omega", fr_size, false, 7 }, + { "s_omega", fr_size, false, 8 }, + { "z_lookup_omega", fr_size, false, 9 }, + }, + "nu", + ULTRA_UNROLLED_MANIFEST_SIZE - 3, + true), + + transcript::Manifest::RoundManifest( + { { "PI_Z", g1_size, false }, { "PI_Z_OMEGA", g1_size, false } }, "separator", 1) }); + + return output; + } + + // @note 'unrolled' means "don't use linearisation techniques from the plonk paper". + static transcript::Manifest create_unrolled_manifest(const size_t num_public_inputs) + { + // add public inputs.... + constexpr size_t g1_size = 64; + constexpr size_t fr_size = 32; + const size_t public_input_size = fr_size * num_public_inputs; + const transcript::Manifest output = transcript::Manifest( + + { transcript::Manifest::RoundManifest( + { // { name, num_bytes, derived_by_verifier } + { "circuit_size", 4, true }, + { "public_input_size", 4, true } }, + "init", // challenge_name + 1 // num_challenges_in + ), + + transcript::Manifest::RoundManifest( + { // { name, num_bytes, derived_by_verifier } + { "public_inputs", public_input_size, false }, + { "W_1", g1_size, false }, + { "W_2", g1_size, false }, + { "W_3", g1_size, false } }, + "eta", // challenge_name + 1 // num_challenges_in + ), + + transcript::Manifest::RoundManifest( + { // { name, num_bytes, derived_by_verifier } + { "W_4", g1_size, false }, + { "S", g1_size, false } }, + "beta", // challenge_name + 2 // num_challenges_in + ), + + transcript::Manifest::RoundManifest( + { // { name, num_bytes, derived_by_verifier } + { "Z_PERM", g1_size, false }, + { "Z_LOOKUP", g1_size, false } }, + "alpha", // challenge_name + 1 // num_challenges_in + ), + + transcript::Manifest::RoundManifest( + { // { name, num_bytes, derived_by_verifier } + { "T_1", g1_size, false }, + { "T_2", g1_size, false }, + { "T_3", g1_size, false }, + { "T_4", g1_size, false } }, + "z", // challenge_name + 1 // num_challenges_in + ), + + // N.B. THE SHFITED EVALS (_omega) MUST HAVE THE SAME CHALLENGE INDEX AS THE NON SHIFTED VALUES + transcript::Manifest::RoundManifest( + { + // { name, num_bytes, derived_by_verifier, challenge_map_index } + // * denotes values which aren't included in the non-unrolled manifest. + { "t", fr_size, true, -1 }, // * + { "w_1", fr_size, false, 0 }, + { "w_2", fr_size, false, 1 }, + { "w_3", fr_size, false, 2 }, + { "w_4", fr_size, false, 3 }, + { "s", fr_size, false, 4 }, + { "z_perm", fr_size, false, 5 }, // * + { "z_lookup", fr_size, false, 6 }, + { "q_1", fr_size, false, 7 }, + { "q_2", fr_size, false, 8 }, + { "q_3", fr_size, false, 9 }, + { "q_4", fr_size, false, 10 }, + { "q_m", fr_size, false, 11 }, + { "q_c", fr_size, false, 12 }, + { "q_arith", fr_size, false, 13 }, + { "q_sort", fr_size, false, 14 }, // * + { "q_elliptic", fr_size, false, 15 }, // * + { "q_aux", fr_size, false, 16 }, + { "q_fixed_base", fr_size, false, 30 }, + { "sigma_1", fr_size, false, 17 }, + { "sigma_2", fr_size, false, 18 }, + { "sigma_3", fr_size, false, 19 }, + { "sigma_4", fr_size, false, 20 }, + { "table_value_1", fr_size, false, 21 }, + { "table_value_2", fr_size, false, 22 }, + { "table_value_3", fr_size, false, 23 }, + { "table_value_4", fr_size, false, 24 }, + { "table_type", fr_size, false, 25 }, + { "id_1", fr_size, false, 26 }, + { "id_2", fr_size, false, 27 }, + { "id_3", fr_size, false, 28 }, + { "id_4", fr_size, false, 29 }, + { "w_1_omega", fr_size, false, 0 }, + { "w_2_omega", fr_size, false, 1 }, + { "w_3_omega", fr_size, false, 2 }, + { "w_4_omega", fr_size, false, 3 }, + { "s_omega", fr_size, false, 4 }, + { "z_perm_omega", fr_size, false, 5 }, + { "z_lookup_omega", fr_size, false, 6 }, + { "table_value_1_omega", fr_size, false, 21 }, + { "table_value_2_omega", fr_size, false, 22 }, + { "table_value_3_omega", fr_size, false, 23 }, + { "table_value_4_omega", fr_size, false, 24 }, + }, + "nu", // challenge_name + ULTRA_UNROLLED_MANIFEST_SIZE, // num_challenges_in + true // map_challenges_in + ), + + transcript::Manifest::RoundManifest( + { // { name, num_bytes, derived_by_verifier, challenge_map_index } + { "PI_Z", g1_size, false }, + { "PI_Z_OMEGA", g1_size, false } }, + "separator", // challenge_name + 3 // num_challenges_in + ) }); + + return output; + } +}; +} // namespace waffle diff --git a/cpp/src/aztec/plonk/composer/plookup_composer.test.cpp b/cpp/src/aztec/plonk/composer/ultra_composer.test.cpp similarity index 64% rename from cpp/src/aztec/plonk/composer/plookup_composer.test.cpp rename to cpp/src/aztec/plonk/composer/ultra_composer.test.cpp index 7a33caf680..c7618744bf 100644 --- a/cpp/src/aztec/plonk/composer/plookup_composer.test.cpp +++ b/cpp/src/aztec/plonk/composer/ultra_composer.test.cpp @@ -1,18 +1,25 @@ -#include "plookup_composer.hpp" +#include +#include "ultra_composer.hpp" #include #include #include +#include #include "../proof_system/widgets/transition_widgets/create_dummy_transcript.hpp" #include "../proof_system/widgets/random_widgets/plookup_widget.hpp" #include "./plookup_tables/sha256.hpp" using namespace barretenberg; -using namespace crypto::pedersen; + +namespace ultra_composer_tests { namespace { auto& engine = numeric::random::get_debug_engine(); } -std::vector add_variables(waffle::PlookupComposer& composer, std::vector variables) + +using plookup::ColumnIdx; +using plookup::MultiTableId; + +std::vector add_variables(waffle::UltraComposer& composer, std::vector variables) { std::vector res; for (size_t i = 0; i < variables.size(); i++) { @@ -20,26 +27,34 @@ std::vector add_variables(waffle::PlookupComposer& composer, std::vect } return res; } - -TEST(plookup_composer, read_sequence_from_multi_table) +TEST(ultra_composer, create_gates_from_plookup_accumulators) { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); - barretenberg::fr input_value = engine.get_random_uint256() & 0xffffffffULL; - const auto input_index = composer.add_variable(input_value); + barretenberg::fr input_value = fr::random_element(); + const fr input_hi = uint256_t(input_value).slice(126, 256); + const fr input_lo = uint256_t(input_value).slice(0, 126); + const auto input_hi_index = composer.add_variable(input_hi); + const auto input_lo_index = composer.add_variable(input_lo); - const auto sequence_data = - waffle::plookup::get_table_values(waffle::PlookupMultiTableId::PEDERSEN_LEFT, input_value); + const auto sequence_data_hi = plookup::get_lookup_accumulators(MultiTableId::PEDERSEN_LEFT_HI, input_hi); + const auto sequence_data_lo = plookup::get_lookup_accumulators(MultiTableId::PEDERSEN_LEFT_LO, input_lo); - const auto sequence_indices = - composer.read_sequence_from_multi_table(waffle::PlookupMultiTableId::PEDERSEN_LEFT, sequence_data, input_index); + const auto lookup_witnesses_hi = composer.create_gates_from_plookup_accumulators( + MultiTableId::PEDERSEN_LEFT_HI, sequence_data_hi, input_hi_index); + const auto lookup_witnesses_lo = composer.create_gates_from_plookup_accumulators( + MultiTableId::PEDERSEN_LEFT_LO, sequence_data_lo, input_lo_index); std::vector expected_x; std::vector expected_y; - const size_t num_lookups = (256 + sidon::BITS_PER_TABLE - 1) / sidon::BITS_PER_TABLE; + const size_t num_lookups_hi = + (128 + crypto::pedersen::lookup::BITS_PER_TABLE) / crypto::pedersen::lookup::BITS_PER_TABLE; + const size_t num_lookups_lo = 126 / crypto::pedersen::lookup::BITS_PER_TABLE; + const size_t num_lookups = num_lookups_hi + num_lookups_lo; - EXPECT_EQ(num_lookups, sequence_indices[0].size()); + EXPECT_EQ(num_lookups_hi, lookup_witnesses_hi[ColumnIdx::C1].size()); + EXPECT_EQ(num_lookups_lo, lookup_witnesses_lo[ColumnIdx::C1].size()); std::vector expected_scalars; expected_x.resize(num_lookups); @@ -47,41 +62,48 @@ TEST(plookup_composer, read_sequence_from_multi_table) expected_scalars.resize(num_lookups); { - const size_t num_rounds = (num_lookups + 2) / 3; + const size_t num_rounds = (num_lookups + 1) / 2; uint256_t bits(input_value); - const auto mask = sidon::PEDERSEN_TABLE_SIZE - 1; + const auto mask = crypto::pedersen::lookup::PEDERSEN_TABLE_SIZE - 1; for (size_t i = 0; i < num_rounds; ++i) { - const auto& table = sidon::get_table(i); - const size_t index = i * 3; + const auto& table = crypto::pedersen::lookup::get_table(i); + const size_t index = i * 2; - uint64_t slice_a = ((bits >> (index * 10)) & mask).data[0]; + uint64_t slice_a = ((bits >> (index * 9)) & mask).data[0]; expected_x[index] = (table[(size_t)slice_a].x); expected_y[index] = (table[(size_t)slice_a].y); expected_scalars[index] = slice_a; - uint64_t slice_b = ((bits >> ((index + 1) * 10)) & mask).data[0]; - expected_x[index + 1] = (table[(size_t)slice_b].x); - expected_y[index + 1] = (table[(size_t)slice_b].y); - expected_scalars[index + 1] = slice_b; - - if (i < 8) { - uint64_t slice_c = ((bits >> ((index + 2) * 10)) & mask).data[0]; - expected_x[index + 2] = (table[(size_t)slice_c].x); - expected_y[index + 2] = (table[(size_t)slice_c].y); - expected_scalars[index + 2] = slice_c; + if (i < 14) { + uint64_t slice_b = ((bits >> ((index + 1) * 9)) & mask).data[0]; + expected_x[index + 1] = (table[(size_t)slice_b].x); + expected_y[index + 1] = (table[(size_t)slice_b].y); + expected_scalars[index + 1] = slice_b; } } } for (size_t i = num_lookups - 2; i < num_lookups; --i) { - expected_scalars[i] += (expected_scalars[i + 1] * sidon::PEDERSEN_TABLE_SIZE); + expected_scalars[i] += (expected_scalars[i + 1] * crypto::pedersen::lookup::PEDERSEN_TABLE_SIZE); } - for (size_t i = 0; i < num_lookups; ++i) { - EXPECT_EQ(composer.get_variable(sequence_indices[0][i]), expected_scalars[i]); - EXPECT_EQ(composer.get_variable(sequence_indices[1][i]), expected_x[i]); - EXPECT_EQ(composer.get_variable(sequence_indices[2][i]), expected_y[i]); + + size_t hi_shift = 126; + const fr hi_cumulative = composer.get_variable(lookup_witnesses_hi[ColumnIdx::C1][0]); + for (size_t i = 0; i < num_lookups_lo; ++i) { + const fr hi_mult = fr(uint256_t(1) << hi_shift); + EXPECT_EQ(composer.get_variable(lookup_witnesses_lo[ColumnIdx::C1][i]) + (hi_cumulative * hi_mult), + expected_scalars[i]); + EXPECT_EQ(composer.get_variable(lookup_witnesses_lo[ColumnIdx::C2][i]), expected_x[i]); + EXPECT_EQ(composer.get_variable(lookup_witnesses_lo[ColumnIdx::C3][i]), expected_y[i]); + hi_shift -= crypto::pedersen::lookup::BITS_PER_TABLE; + } + + for (size_t i = 0; i < num_lookups_hi; ++i) { + EXPECT_EQ(composer.get_variable(lookup_witnesses_hi[ColumnIdx::C1][i]), expected_scalars[i + num_lookups_lo]); + EXPECT_EQ(composer.get_variable(lookup_witnesses_hi[ColumnIdx::C2][i]), expected_x[i + num_lookups_lo]); + EXPECT_EQ(composer.get_variable(lookup_witnesses_hi[ColumnIdx::C3][i]), expected_y[i + num_lookups_lo]); } auto prover = composer.create_prover(); @@ -93,9 +115,9 @@ TEST(plookup_composer, read_sequence_from_multi_table) EXPECT_EQ(result, true); } -TEST(plookup_composer, test_no_lookup_proof) +TEST(ultra_composer, test_no_lookup_proof) { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); for (size_t i = 0; i < 16; ++i) { for (size_t j = 0; j < 16; ++j) { @@ -121,14 +143,15 @@ TEST(plookup_composer, test_no_lookup_proof) EXPECT_EQ(result, true); } -TEST(plookup_composer, test_elliptic_gate) +TEST(ultra_composer, test_elliptic_gate) { typedef grumpkin::g1::affine_element affine_element; typedef grumpkin::g1::element element; - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); + + affine_element p1 = crypto::pedersen::get_generator_data({ 0, 0 }).generator; - affine_element p1 = get_generator_data(DEFAULT_GEN_1).generator; - affine_element p2 = get_generator_data(DEFAULT_GEN_2).generator; + affine_element p2 = crypto::pedersen::get_generator_data({ 0, 1 }).generator; affine_element p3(element(p1) + element(p2)); uint32_t x1 = composer.add_variable(p1.x); @@ -141,19 +164,20 @@ TEST(plookup_composer, test_elliptic_gate) waffle::ecc_add_gate gate{ x1, y1, x2, y2, x3, y3, 1, 1 }; composer.create_ecc_add_gate(gate); + grumpkin::fq beta = grumpkin::fq::cube_root_of_unity(); affine_element p2_endo = p2; - p2_endo.x *= grumpkin::fq::beta(); + p2_endo.x *= beta; p3 = affine_element(element(p1) + element(p2_endo)); x3 = composer.add_variable(p3.x); y3 = composer.add_variable(p3.y); - gate = waffle::ecc_add_gate{ x1, y1, x2, y2, x3, y3, grumpkin::fq::beta(), 1 }; + gate = waffle::ecc_add_gate{ x1, y1, x2, y2, x3, y3, beta, 1 }; composer.create_ecc_add_gate(gate); - p2_endo.x *= grumpkin::fq::beta(); + p2_endo.x *= beta; p3 = affine_element(element(p1) - element(p2_endo)); x3 = composer.add_variable(p3.x); y3 = composer.add_variable(p3.y); - gate = waffle::ecc_add_gate{ x1, y1, x2, y2, x3, y3, grumpkin::fq::beta().sqr(), -1 }; + gate = waffle::ecc_add_gate{ x1, y1, x2, y2, x3, y3, beta.sqr(), -1 }; composer.create_ecc_add_gate(gate); auto prover = composer.create_prover(); @@ -166,9 +190,9 @@ TEST(plookup_composer, test_elliptic_gate) EXPECT_EQ(result, true); } -TEST(plookup_composer, non_trivial_tag_permutation) +TEST(ultra_composer, non_trivial_tag_permutation) { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); fr a = fr::random_element(); fr b = -a; @@ -199,9 +223,9 @@ TEST(plookup_composer, non_trivial_tag_permutation) bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); EXPECT_EQ(result, true); } -TEST(plookup_composer, non_trivial_tag_permutation_and_cycles) +TEST(ultra_composer, non_trivial_tag_permutation_and_cycles) { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); fr a = fr::random_element(); fr c = -a; @@ -243,9 +267,9 @@ TEST(plookup_composer, non_trivial_tag_permutation_and_cycles) EXPECT_EQ(result, true); } -TEST(plookup_composer, bad_tag_permutation) +TEST(ultra_composer, bad_tag_permutation) { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); fr a = fr::random_element(); fr b = -a; @@ -273,9 +297,9 @@ TEST(plookup_composer, bad_tag_permutation) } // same as above but with turbocomposer to check reason of failue is really tag mismatch -TEST(plookup_composer, bad_tag_turbo_permutation) +TEST(ultra_composer, bad_tag_turbo_permutation) { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); fr a = fr::random_element(); fr b = -a; @@ -299,9 +323,9 @@ TEST(plookup_composer, bad_tag_turbo_permutation) EXPECT_EQ(result, true); } -TEST(plookup_composer, sort_widget) +TEST(ultra_composer, sort_widget) { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); fr a = fr::one(); fr b = fr(2); fr c = fr(3); @@ -321,7 +345,7 @@ TEST(plookup_composer, sort_widget) EXPECT_EQ(result, true); } -TEST(plookup_composer, sort_with_edges_gate) +TEST(ultra_composer, sort_with_edges_gate) { fr a = fr::one(); @@ -334,7 +358,7 @@ TEST(plookup_composer, sort_with_edges_gate) fr h = fr(8); { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); auto a_idx = composer.add_variable(a); auto b_idx = composer.add_variable(b); auto c_idx = composer.add_variable(c); @@ -354,7 +378,7 @@ TEST(plookup_composer, sort_with_edges_gate) } { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); auto a_idx = composer.add_variable(a); auto b_idx = composer.add_variable(b); auto c_idx = composer.add_variable(c); @@ -373,7 +397,7 @@ TEST(plookup_composer, sort_with_edges_gate) EXPECT_EQ(result, false); } { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); auto a_idx = composer.add_variable(a); auto b_idx = composer.add_variable(b); auto c_idx = composer.add_variable(c); @@ -392,7 +416,7 @@ TEST(plookup_composer, sort_with_edges_gate) EXPECT_EQ(result, false); } { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); auto a_idx = composer.add_variable(a); auto c_idx = composer.add_variable(c); auto d_idx = composer.add_variable(d); @@ -411,7 +435,7 @@ TEST(plookup_composer, sort_with_edges_gate) EXPECT_EQ(result, false); } { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); auto idx = add_variables(composer, { 1, 2, 5, 6, 7, 10, 11, 13, 16, 17, 20, 22, 22, 25, 26, 29, 29, 32, 32, 33, 35, 38, 39, 39, 42, 42, 43, 45 }); composer.create_sort_constraint_with_edges(idx, 1, 45); @@ -424,7 +448,7 @@ TEST(plookup_composer, sort_with_edges_gate) EXPECT_EQ(result, true); } { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); auto idx = add_variables(composer, { 1, 2, 5, 6, 7, 10, 11, 13, 16, 17, 20, 22, 22, 25, 26, 29, 29, 32, 32, 33, 35, 38, 39, 39, 42, 42, 43, 45 }); @@ -437,17 +461,17 @@ TEST(plookup_composer, sort_with_edges_gate) EXPECT_EQ(result, false); } } -TEST(plookup_composer, range_constraint) + +TEST(ultra_composer, range_constraint) { { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); auto indices = add_variables(composer, { 1, 2, 3, 4, 5, 6, 7, 8 }); for (size_t i = 0; i < indices.size(); i++) { composer.create_new_range_constraint(indices[i], 8); } // auto ind = {a_idx,b_idx,c_idx,d_idx,e_idx,f_idx,g_idx,h_idx}; composer.create_sort_constraint(indices); - composer.process_range_lists(); auto prover = composer.create_prover(); auto verifier = composer.create_verifier(); @@ -457,14 +481,13 @@ TEST(plookup_composer, range_constraint) EXPECT_EQ(result, true); } { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); auto indices = add_variables(composer, { 3 }); for (size_t i = 0; i < indices.size(); i++) { composer.create_new_range_constraint(indices[i], 3); } // auto ind = {a_idx,b_idx,c_idx,d_idx,e_idx,f_idx,g_idx,h_idx}; composer.create_dummy_constraints(indices); - composer.process_range_lists(); auto prover = composer.create_prover(); auto verifier = composer.create_verifier(); @@ -474,13 +497,12 @@ TEST(plookup_composer, range_constraint) EXPECT_EQ(result, true); } { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); auto indices = add_variables(composer, { 1, 2, 3, 4, 5, 6, 8, 25 }); for (size_t i = 0; i < indices.size(); i++) { composer.create_new_range_constraint(indices[i], 8); } composer.create_sort_constraint(indices); - composer.process_range_lists(); auto prover = composer.create_prover(); auto verifier = composer.create_verifier(); @@ -490,14 +512,13 @@ TEST(plookup_composer, range_constraint) EXPECT_EQ(result, false); } { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); auto indices = add_variables(composer, { 1, 2, 3, 4, 5, 6, 10, 8, 15, 11, 32, 21, 42, 79, 16, 10, 3, 26, 19, 51 }); for (size_t i = 0; i < indices.size(); i++) { composer.create_new_range_constraint(indices[i], 128); } composer.create_dummy_constraints(indices); - composer.process_range_lists(); auto prover = composer.create_prover(); auto verifier = composer.create_verifier(); @@ -507,14 +528,29 @@ TEST(plookup_composer, range_constraint) EXPECT_EQ(result, true); } { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); auto indices = add_variables(composer, { 1, 2, 3, 80, 5, 6, 29, 8, 15, 11, 32, 21, 42, 79, 16, 10, 3, 26, 13, 14 }); for (size_t i = 0; i < indices.size(); i++) { composer.create_new_range_constraint(indices[i], 79); } composer.create_dummy_constraints(indices); - composer.process_range_lists(); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + waffle::plonk_proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, false); + } + { + waffle::UltraComposer composer = waffle::UltraComposer(); + auto indices = + add_variables(composer, { 1, 0, 3, 80, 5, 6, 29, 8, 15, 11, 32, 21, 42, 79, 16, 10, 3, 26, 13, 14 }); + for (size_t i = 0; i < indices.size(); i++) { + composer.create_new_range_constraint(indices[i], 79); + } + composer.create_dummy_constraints(indices); auto prover = composer.create_prover(); auto verifier = composer.create_verifier(); @@ -524,16 +560,15 @@ TEST(plookup_composer, range_constraint) EXPECT_EQ(result, false); } } -TEST(plookup_composer, range_with_gates) + +TEST(ultra_composer, range_with_gates) { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); auto idx = add_variables(composer, { 1, 2, 3, 4, 5, 6, 7, 8 }); for (size_t i = 0; i < idx.size(); i++) { composer.create_new_range_constraint(idx[i], 8); } - // auto ind = {a_idx,b_idx,c_idx,d_idx,e_idx,f_idx,g_idx,h_idx}; - composer.process_range_lists(); composer.create_add_gate({ idx[0], idx[1], composer.zero_idx, fr::one(), fr::one(), fr::zero(), -3 }); composer.create_add_gate({ idx[2], idx[3], composer.zero_idx, fr::one(), fr::one(), fr::zero(), -7 }); @@ -546,11 +581,11 @@ TEST(plookup_composer, range_with_gates) bool result = verifier.verify_proof(proof); EXPECT_EQ(result, true); } -TEST(plookup_composer, sort_widget_complex) +TEST(ultra_composer, sort_widget_complex) { { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); std::vector a = { 1, 3, 4, 7, 7, 8, 11, 14, 15, 15, 18, 19, 21, 21, 24, 25, 26, 27, 30, 32 }; std::vector ind; for (size_t i = 0; i < a.size(); i++) @@ -566,7 +601,7 @@ TEST(plookup_composer, sort_widget_complex) } { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); std::vector a = { 1, 3, 4, 7, 7, 8, 16, 14, 15, 15, 18, 19, 21, 21, 24, 25, 26, 27, 30, 32 }; std::vector ind; for (size_t i = 0; i < a.size(); i++) @@ -581,9 +616,9 @@ TEST(plookup_composer, sort_widget_complex) EXPECT_EQ(result, false); } } -TEST(plookup_composer, sort_widget_neg) +TEST(ultra_composer, sort_widget_neg) { - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); fr a = fr::one(); fr b = fr(2); fr c = fr(3); @@ -599,97 +634,132 @@ TEST(plookup_composer, sort_widget_neg) waffle::plonk_proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); + bool result = verifier.verify_proof(proof); EXPECT_EQ(result, false); } -TEST(plookup_composer, composed_range_constraint) +TEST(ultra_composer, composed_range_constraint) { + waffle::UltraComposer composer = waffle::UltraComposer(); + auto c = fr::random_element(); + auto d = uint256_t(c).slice(0, 133); + auto e = fr(d); + auto a_idx = composer.add_variable(fr(e)); + composer.create_add_gate({ a_idx, composer.zero_idx, composer.zero_idx, 1, 0, 0, -fr(e) }); + composer.decompose_into_default_range(a_idx, 134); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); - // { - // waffle::PlookupComposer composer = waffle::PlookupComposer(); - // uint256_t a = 1; - // auto b = a << 52; - - // auto a_idx = composer.add_variable(fr(b)); - // composer.create_add_gate({ a_idx, composer.zero_idx, composer.zero_idx, 1, 0, 0,-fr(b) }); - // composer.decompose_into_default_range(a_idx, 68); - - // composer.process_range_lists(); - // auto prover = composer.create_prover(); - // auto verifier = composer.create_verifier(); - - // waffle::plonk_proof proof = prover.construct_proof(); - - // bool result = verifier.verify_proof(proof); - // EXPECT_EQ(result, true); - // } - // { - // waffle::PlookupComposer composer = waffle::PlookupComposer(); - // uint256_t a = 1; - // auto b = a << 75; - - // auto a_idx = composer.add_variable(fr(b)); - // composer.create_add_gate({ a_idx, composer.zero_idx, composer.zero_idx, 1, 0, 0,-fr(b) }); - // composer.decompose_into_default_range(a_idx, 68); - - // composer.process_range_lists(); - // auto prover = composer.create_prover(); - // auto verifier = composer.create_verifier(); - - // waffle::plonk_proof proof = prover.construct_proof(); - - // bool result = verifier.verify_proof(proof); - // EXPECT_EQ(result, false); - // } - // { - // waffle::PlookupComposer composer = waffle::PlookupComposer(); - // uint256_t a = 1; - // auto b = a << 35; - - // auto a_idx = composer.add_variable(fr(b)); - // composer.create_add_gate({ a_idx, composer.zero_idx, composer.zero_idx, 1, 0, 0,-fr(b) }); - // composer.decompose_into_default_range(a_idx, 51); - - // composer.process_range_lists(); - // auto prover = composer.create_prover(); - // auto verifier = composer.create_verifier(); - - // waffle::plonk_proof proof = prover.construct_proof(); - - // bool result = verifier.verify_proof(proof); - // EXPECT_EQ(result, true); - // } - // { - // waffle::PlookupComposer composer = waffle::PlookupComposer(); - // uint256_t a = 1; - // uint256_t b = a << 53; - // auto a_idx = composer.add_variable(fr(b)); - // composer.create_add_gate({ a_idx, composer.zero_idx, composer.zero_idx, 1, 0, 0,-fr(b) }); - // composer.decompose_into_default_range(a_idx, 51); - // composer.process_range_lists(); - // auto prover = composer.create_prover(); - // auto verifier = composer.create_verifier(); - - // waffle::plonk_proof proof = prover.construct_proof(); - - // bool result = verifier.verify_proof(proof); - // EXPECT_EQ(result, false); - // } - { - waffle::PlookupComposer composer = waffle::PlookupComposer(); - auto c = fr::random_element(); - auto d = uint256_t(c).slice(0, 133); - auto e = fr(d); //.slice(0,100)) - auto a_idx = composer.add_variable(fr(e)); - composer.create_add_gate({ a_idx, composer.zero_idx, composer.zero_idx, 1, 0, 0, -fr(e) }); - composer.decompose_into_default_range(a_idx, 134); - composer.process_range_lists(); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); - waffle::plonk_proof proof = prover.construct_proof(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); +} - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); +TEST(ultra_composer, non_native_field_multiplication) +{ + waffle::UltraComposer composer = waffle::UltraComposer(); + + fq a = fq::random_element(); + fq b = fq::random_element(); + uint256_t modulus = fq::modulus; + + uint1024_t a_big = uint512_t(uint256_t(a)); + uint1024_t b_big = uint512_t(uint256_t(b)); + uint1024_t p_big = uint512_t(uint256_t(modulus)); + + uint1024_t q_big = (a_big * b_big) / p_big; + uint1024_t r_big = (a_big * b_big) % p_big; + + uint256_t q(q_big.lo.lo); + uint256_t r(r_big.lo.lo); + + const auto split_into_limbs = [&](const uint512_t& input) { + constexpr size_t NUM_BITS = 68; + 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; + limb_indices[0] = composer.add_variable(limbs[0]); + limb_indices[1] = composer.add_variable(limbs[1]); + limb_indices[2] = composer.add_variable(limbs[2]); + limb_indices[3] = composer.add_variable(limbs[3]); + limb_indices[4] = composer.add_variable(limbs[4]); + return limb_indices; + }; + const uint512_t BINARY_BASIS_MODULUS = uint512_t(1) << (68 * 4); + auto modulus_limbs = split_into_limbs(BINARY_BASIS_MODULUS - uint512_t(modulus)); + + const auto a_indices = get_limb_witness_indices(split_into_limbs(uint256_t(a))); + const auto b_indices = get_limb_witness_indices(split_into_limbs(uint256_t(b))); + const auto q_indices = get_limb_witness_indices(split_into_limbs(uint256_t(q))); + const auto r_indices = get_limb_witness_indices(split_into_limbs(uint256_t(r))); + + waffle::UltraComposer::non_native_field_witnesses inputs{ + a_indices, b_indices, q_indices, r_indices, modulus_limbs, fr(uint256_t(modulus)), + }; + composer.evaluate_non_native_field_multiplication(inputs); + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + + waffle::plonk_proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); +} + +TEST(ultra_composer, rom) +{ + waffle::UltraComposer composer = waffle::UltraComposer(); + + uint32_t rom_values[8]{ + composer.add_variable(fr::random_element()), composer.add_variable(fr::random_element()), + composer.add_variable(fr::random_element()), composer.add_variable(fr::random_element()), + composer.add_variable(fr::random_element()), composer.add_variable(fr::random_element()), + composer.add_variable(fr::random_element()), composer.add_variable(fr::random_element()), + }; + + size_t rom_id = composer.create_ROM_array(8); + + for (size_t i = 0; i < 8; ++i) { + composer.set_ROM_element(rom_id, i, rom_values[i]); } -} \ No newline at end of file + + uint32_t a_idx = composer.read_ROM_array(rom_id, composer.add_variable(5)); + EXPECT_EQ(a_idx != rom_values[5], true); + uint32_t b_idx = composer.read_ROM_array(rom_id, composer.add_variable(4)); + uint32_t c_idx = composer.read_ROM_array(rom_id, composer.add_variable(1)); + + const auto d_value = composer.get_variable(a_idx) + composer.get_variable(b_idx) + composer.get_variable(c_idx); + uint32_t d_idx = composer.add_variable(d_value); + + composer.create_big_add_gate({ + a_idx, + b_idx, + c_idx, + d_idx, + 1, + 1, + 1, + -1, + 0, + }); + + auto prover = composer.create_prover(); + std::cout << "prover n = " << composer.n << std::endl; + + auto verifier = composer.create_verifier(); + + waffle::plonk_proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); +} +} // namespace ultra_composer_tests \ No newline at end of file diff --git a/cpp/src/aztec/plonk/proof_system/commitment_scheme/commitment_scheme.test.cpp b/cpp/src/aztec/plonk/proof_system/commitment_scheme/commitment_scheme.test.cpp index 74e593e0f7..84bf1bd670 100644 --- a/cpp/src/aztec/plonk/proof_system/commitment_scheme/commitment_scheme.test.cpp +++ b/cpp/src/aztec/plonk/proof_system/commitment_scheme/commitment_scheme.test.cpp @@ -9,7 +9,7 @@ #include "../prover/work_queue.hpp" #include "../types/program_settings.hpp" #include "../../composer/composer_base.hpp" -#include +#include #include #include diff --git a/cpp/src/aztec/plonk/proof_system/commitment_scheme/kate_commitment_scheme.cpp b/cpp/src/aztec/plonk/proof_system/commitment_scheme/kate_commitment_scheme.cpp index e7bc469db5..e822e19e27 100644 --- a/cpp/src/aztec/plonk/proof_system/commitment_scheme/kate_commitment_scheme.cpp +++ b/cpp/src/aztec/plonk/proof_system/commitment_scheme/kate_commitment_scheme.cpp @@ -11,14 +11,17 @@ KateCommitmentScheme::KateCommitmentScheme() {} template -void KateCommitmentScheme::commit(fr* coefficients, std::string tag, fr item_constant, work_queue& queue) +void KateCommitmentScheme::commit(fr* coefficients, + std::string commitment_tag, + fr item_constant, + work_queue& queue) { queue.add_to_queue({ - work_queue::WorkType::SCALAR_MULTIPLICATION, - coefficients, - tag, - item_constant, - 0, + .work_type = work_queue::WorkType::SCALAR_MULTIPLICATION, + .mul_scalars = coefficients, + .tag = commitment_tag, + .constant = item_constant, + .index = 0, }); } @@ -262,6 +265,7 @@ void KateCommitmentScheme::batch_verify(const transcript::StandardTran // Note that we do not actually compute the scalar multiplications but just accumulate the scalars // and the group elements in different vectors. // + fr batch_eval(0); const auto& polynomial_manifest = input_key->polynomial_manifest; for (size_t i = 0; i < input_key->polynomial_manifest.size(); ++i) { @@ -425,9 +429,9 @@ void KateCommitmentScheme::add_opening_evaluations_to_transcript(trans template class KateCommitmentScheme; template class KateCommitmentScheme; -template class KateCommitmentScheme; +template class KateCommitmentScheme; +template class KateCommitmentScheme; template class KateCommitmentScheme; template class KateCommitmentScheme; -template class KateCommitmentScheme; - -} // namespace waffle +template class KateCommitmentScheme; +} // namespace waffle \ No newline at end of file diff --git a/cpp/src/aztec/plonk/proof_system/commitment_scheme/kate_commitment_scheme.hpp b/cpp/src/aztec/plonk/proof_system/commitment_scheme/kate_commitment_scheme.hpp index a163c51e16..985f2ba480 100644 --- a/cpp/src/aztec/plonk/proof_system/commitment_scheme/kate_commitment_scheme.hpp +++ b/cpp/src/aztec/plonk/proof_system/commitment_scheme/kate_commitment_scheme.hpp @@ -42,9 +42,10 @@ template class KateCommitmentScheme : public CommitmentSchem extern template class KateCommitmentScheme; extern template class KateCommitmentScheme; -extern template class KateCommitmentScheme; +extern template class KateCommitmentScheme; +extern template class KateCommitmentScheme; extern template class KateCommitmentScheme; extern template class KateCommitmentScheme; -extern template class KateCommitmentScheme; +extern template class KateCommitmentScheme; } // namespace waffle \ No newline at end of file diff --git a/cpp/src/aztec/plonk/proof_system/constants.hpp b/cpp/src/aztec/plonk/proof_system/constants.hpp index 067ba919dd..f695ff3572 100644 --- a/cpp/src/aztec/plonk/proof_system/constants.hpp +++ b/cpp/src/aztec/plonk/proof_system/constants.hpp @@ -3,6 +3,22 @@ namespace waffle { +enum ComposerType { + STANDARD, + TURBO, + PLOOKUP, +}; + +// This variable sets the composer (TURBO or ULTRA) of the entire stdlib and rollup modules. +// To switch to using a new composer, only changing this variable should activate the new composer +// throughout the stdlib and circuits. +static constexpr uint32_t SYSTEM_COMPOSER = ComposerType::TURBO; + +enum MerkleHashType { + FIXED_BASE_PEDERSEN, + LOOKUP_PEDERSEN, +}; + // limb size when simulating a non-native field using bigfield class // (needs to be a universal constant to be used by native verifier) static constexpr uint64_t NUM_LIMB_BITS_IN_FIELD_SIMULATION = 68; diff --git a/cpp/src/aztec/plonk/proof_system/prover/c_bind.cpp b/cpp/src/aztec/plonk/proof_system/prover/c_bind.cpp index 6a844550b8..35bb2b3074 100644 --- a/cpp/src/aztec/plonk/proof_system/prover/c_bind.cpp +++ b/cpp/src/aztec/plonk/proof_system/prover/c_bind.cpp @@ -30,97 +30,100 @@ WASM_EXPORT void* test_async_func(size_t size, int val) } } -WASM_EXPORT void prover_process_queue(waffle::TurboProver* prover) +typedef std::conditional_t + WasmProver; + +WASM_EXPORT void prover_process_queue(WasmProver* prover) { prover->queue.process_queue(); } -WASM_EXPORT size_t prover_get_circuit_size(waffle::TurboProver* prover) +WASM_EXPORT size_t prover_get_circuit_size(WasmProver* prover) { return prover->get_circuit_size(); } -WASM_EXPORT void prover_get_work_queue_item_info(waffle::TurboProver* prover, uint8_t* result) +WASM_EXPORT void prover_get_work_queue_item_info(WasmProver* prover, uint8_t* result) { auto info = prover->get_queued_work_item_info(); memcpy(result, &info, sizeof(info)); } -WASM_EXPORT fr* prover_get_scalar_multiplication_data(waffle::TurboProver* prover, size_t work_item_number) +WASM_EXPORT fr* prover_get_scalar_multiplication_data(WasmProver* prover, size_t work_item_number) { return prover->get_scalar_multiplication_data(work_item_number); } -WASM_EXPORT size_t prover_get_scalar_multiplication_size(waffle::TurboProver* prover, size_t work_item_number) +WASM_EXPORT size_t prover_get_scalar_multiplication_size(WasmProver* prover, size_t work_item_number) { return prover->get_scalar_multiplication_size(work_item_number); } -WASM_EXPORT void prover_put_scalar_multiplication_data(waffle::TurboProver* prover, +WASM_EXPORT void prover_put_scalar_multiplication_data(WasmProver* prover, g1::element* result, const size_t work_item_number) { prover->put_scalar_multiplication_data(*result, work_item_number); } -WASM_EXPORT fr* prover_get_fft_data(waffle::TurboProver* prover, fr* shift_factor, size_t work_item_number) +WASM_EXPORT fr* prover_get_fft_data(WasmProver* prover, fr* shift_factor, size_t work_item_number) { auto data = prover->get_fft_data(work_item_number); *shift_factor = data.shift_factor; return data.data; } -WASM_EXPORT void prover_put_fft_data(waffle::TurboProver* prover, fr* result, size_t work_item_number) +WASM_EXPORT void prover_put_fft_data(WasmProver* prover, fr* result, size_t work_item_number) { prover->put_fft_data(result, work_item_number); } -WASM_EXPORT fr* prover_get_ifft_data(waffle::TurboProver* prover, size_t work_item_number) +WASM_EXPORT fr* prover_get_ifft_data(WasmProver* prover, size_t work_item_number) { return prover->get_ifft_data(work_item_number); } -WASM_EXPORT void prover_put_ifft_data(waffle::TurboProver* prover, fr* result, size_t work_item_number) +WASM_EXPORT void prover_put_ifft_data(WasmProver* prover, fr* result, size_t work_item_number) { prover->put_ifft_data(result, work_item_number); } -WASM_EXPORT void prover_execute_preamble_round(waffle::TurboProver* prover) +WASM_EXPORT void prover_execute_preamble_round(WasmProver* prover) { prover->execute_preamble_round(); } -WASM_EXPORT void prover_execute_first_round(waffle::TurboProver* prover) +WASM_EXPORT void prover_execute_first_round(WasmProver* prover) { prover->execute_first_round(); } -WASM_EXPORT void prover_execute_second_round(waffle::TurboProver* prover) +WASM_EXPORT void prover_execute_second_round(WasmProver* prover) { prover->execute_second_round(); } -WASM_EXPORT void prover_execute_third_round(waffle::TurboProver* prover) +WASM_EXPORT void prover_execute_third_round(WasmProver* prover) { prover->execute_third_round(); } -WASM_EXPORT void prover_execute_fourth_round(waffle::TurboProver* prover) +WASM_EXPORT void prover_execute_fourth_round(WasmProver* prover) { prover->execute_fourth_round(); } -WASM_EXPORT void prover_execute_fifth_round(waffle::TurboProver* prover) +WASM_EXPORT void prover_execute_fifth_round(WasmProver* prover) { prover->execute_fifth_round(); } -WASM_EXPORT void prover_execute_sixth_round(waffle::TurboProver* prover) +WASM_EXPORT void prover_execute_sixth_round(WasmProver* prover) { prover->execute_sixth_round(); } -WASM_EXPORT size_t prover_export_proof(waffle::TurboProver* prover, uint8_t** proof_data_buf) +WASM_EXPORT size_t prover_export_proof(WasmProver* prover, uint8_t** proof_data_buf) { auto& proof_data = prover->export_proof().proof_data; *proof_data_buf = proof_data.data(); diff --git a/cpp/src/aztec/plonk/proof_system/prover/c_bind_unrolled.cpp b/cpp/src/aztec/plonk/proof_system/prover/c_bind_unrolled.cpp index 4494ebdf32..7dc2bf3473 100644 --- a/cpp/src/aztec/plonk/proof_system/prover/c_bind_unrolled.cpp +++ b/cpp/src/aztec/plonk/proof_system/prover/c_bind_unrolled.cpp @@ -6,101 +6,101 @@ using namespace barretenberg; extern "C" { -WASM_EXPORT void unrolled_prover_process_queue(waffle::UnrolledTurboProver* prover) +typedef std:: + conditional_t + WasmUnrolledProver; + +WASM_EXPORT void unrolled_prover_process_queue(WasmUnrolledProver* prover) { prover->queue.process_queue(); } -WASM_EXPORT size_t unrolled_prover_get_circuit_size(waffle::UnrolledTurboProver* prover) +WASM_EXPORT size_t unrolled_prover_get_circuit_size(WasmUnrolledProver* prover) { return prover->get_circuit_size(); } -WASM_EXPORT void unrolled_prover_get_work_queue_item_info(waffle::UnrolledTurboProver* prover, uint8_t* result) +WASM_EXPORT void unrolled_prover_get_work_queue_item_info(WasmUnrolledProver* prover, uint8_t* result) { auto info = prover->get_queued_work_item_info(); memcpy(result, &info, sizeof(info)); } -WASM_EXPORT fr* unrolled_prover_get_scalar_multiplication_data(waffle::UnrolledTurboProver* prover, - size_t work_item_number) +WASM_EXPORT fr* unrolled_prover_get_scalar_multiplication_data(WasmUnrolledProver* prover, size_t work_item_number) { return prover->get_scalar_multiplication_data(work_item_number); } -WASM_EXPORT size_t unrolled_prover_get_scalar_multiplication_size(waffle::UnrolledTurboProver* prover, - size_t work_item_number) +WASM_EXPORT size_t unrolled_prover_get_scalar_multiplication_size(WasmUnrolledProver* prover, size_t work_item_number) { return prover->get_scalar_multiplication_size(work_item_number); } -WASM_EXPORT void unrolled_prover_put_scalar_multiplication_data(waffle::UnrolledTurboProver* prover, +WASM_EXPORT void unrolled_prover_put_scalar_multiplication_data(WasmUnrolledProver* prover, g1::element* result, const size_t work_item_number) { prover->put_scalar_multiplication_data(*result, work_item_number); } -WASM_EXPORT fr* unrolled_prover_get_fft_data(waffle::UnrolledTurboProver* prover, - fr* shift_factor, - size_t work_item_number) +WASM_EXPORT fr* unrolled_prover_get_fft_data(WasmUnrolledProver* prover, fr* shift_factor, size_t work_item_number) { auto data = prover->get_fft_data(work_item_number); *shift_factor = data.shift_factor; return data.data; } -WASM_EXPORT void unrolled_prover_put_fft_data(waffle::UnrolledTurboProver* prover, fr* result, size_t work_item_number) +WASM_EXPORT void unrolled_prover_put_fft_data(WasmUnrolledProver* prover, fr* result, size_t work_item_number) { prover->put_fft_data(result, work_item_number); } -WASM_EXPORT fr* unrolled_prover_get_ifft_data(waffle::UnrolledTurboProver* prover, size_t work_item_number) +WASM_EXPORT fr* unrolled_prover_get_ifft_data(WasmUnrolledProver* prover, size_t work_item_number) { return prover->get_ifft_data(work_item_number); } -WASM_EXPORT void unrolled_prover_put_ifft_data(waffle::UnrolledTurboProver* prover, fr* result, size_t work_item_number) +WASM_EXPORT void unrolled_prover_put_ifft_data(WasmUnrolledProver* prover, fr* result, size_t work_item_number) { prover->put_ifft_data(result, work_item_number); } -WASM_EXPORT void unrolled_prover_execute_preamble_round(waffle::UnrolledTurboProver* prover) +WASM_EXPORT void unrolled_prover_execute_preamble_round(WasmUnrolledProver* prover) { prover->execute_preamble_round(); } -WASM_EXPORT void unrolled_prover_execute_first_round(waffle::UnrolledTurboProver* prover) +WASM_EXPORT void unrolled_prover_execute_first_round(WasmUnrolledProver* prover) { prover->execute_first_round(); } -WASM_EXPORT void unrolled_prover_execute_second_round(waffle::UnrolledTurboProver* prover) +WASM_EXPORT void unrolled_prover_execute_second_round(WasmUnrolledProver* prover) { prover->execute_second_round(); } -WASM_EXPORT void unrolled_prover_execute_third_round(waffle::UnrolledTurboProver* prover) +WASM_EXPORT void unrolled_prover_execute_third_round(WasmUnrolledProver* prover) { prover->execute_third_round(); } -WASM_EXPORT void unrolled_prover_execute_fourth_round(waffle::UnrolledTurboProver* prover) +WASM_EXPORT void unrolled_prover_execute_fourth_round(WasmUnrolledProver* prover) { prover->execute_fourth_round(); } -WASM_EXPORT void unrolled_prover_execute_fifth_round(waffle::UnrolledTurboProver* prover) +WASM_EXPORT void unrolled_prover_execute_fifth_round(WasmUnrolledProver* prover) { prover->execute_fifth_round(); } -WASM_EXPORT void unrolled_prover_execute_sixth_round(waffle::UnrolledTurboProver* prover) +WASM_EXPORT void unrolled_prover_execute_sixth_round(WasmUnrolledProver* prover) { prover->execute_sixth_round(); } -WASM_EXPORT size_t unrolled_prover_export_proof(waffle::UnrolledTurboProver* prover, uint8_t** proof_data_buf) +WASM_EXPORT size_t unrolled_prover_export_proof(WasmUnrolledProver* prover, uint8_t** proof_data_buf) { auto& proof_data = prover->export_proof().proof_data; *proof_data_buf = proof_data.data(); diff --git a/cpp/src/aztec/plonk/proof_system/prover/prover.cpp b/cpp/src/aztec/plonk/proof_system/prover/prover.cpp index 89f0f38365..0da77ed3d4 100644 --- a/cpp/src/aztec/plonk/proof_system/prover/prover.cpp +++ b/cpp/src/aztec/plonk/proof_system/prover/prover.cpp @@ -1,6 +1,7 @@ #include "prover.hpp" #include "../public_inputs/public_inputs.hpp" #include "../utils/linearizer.hpp" +#include "polynomials/polynomial.hpp" #include #include #include @@ -64,16 +65,20 @@ template ProverBase& ProverBase::operato } /** - * Compute wire precommitments and add public_inputs from w_2_fft to transcript. + * - Compute wire commitments and add them to the transcript. + * - Add public_inputs from w_2_fft to transcript. * * @tparam settings Program settings. * */ -template void ProverBase::compute_wire_pre_commitments() +template void ProverBase::compute_wire_commitments() { - for (size_t i = 0; i < settings::program_width; ++i) { + // Compute wire commitments + const size_t end = settings::is_plookup ? (settings::program_width - 1) : settings::program_width; + for (size_t i = 0; i < end; ++i) { std::string wire_tag = "w_" + std::to_string(i + 1); std::string commit_tag = "W_" + std::to_string(i + 1); barretenberg::fr* coefficients = key->polynomial_cache.get(wire_tag).get_coefficients(); + // This automatically saves the computed point to the transcript commitment_scheme->commit(coefficients, commit_tag, work_queue::MSMSize::N, queue); } @@ -86,7 +91,7 @@ template void ProverBase::compute_wire_pre_commitm transcript.add_element("public_inputs", ::to_buffer(public_wires)); } -template void ProverBase::compute_quotient_pre_commitment() +template void ProverBase::compute_quotient_commitments() { // In this method, we compute the commitments to polynomials t_{low}(X), t_{mid}(X) and t_{high}(X). // Recall, the quotient polynomial t(X) = t_{low}(X) + t_{mid}(X).X^n + t_{high}(X).X^{2n} @@ -104,7 +109,7 @@ template void ProverBase::compute_quotient_pre_com // deg(t) = (n - 1) * (program_width + 1) - (n - k) // = n * program_width - program_width - 1 + k // - // Since we must cut atleast 4 roots from the vanishing polynomial + // Since we must cut at least 4 roots from the vanishing polynomial // (refer to ./src/aztec/plonk/proof_system/widgets/random_widgets/permutation_widget_impl.hpp/L247), // k = 4 => deg(t) = n * program_width - program_width + 3 // @@ -138,7 +143,9 @@ template void ProverBase::compute_quotient_pre_com /** * Execute preamble round. - * Execute init round, add randomness to witness polynomials for Honest-Verifier Zero Knowledge. + * - Execute init round + * - Add randomness to the wire witness polynomials for Honest-Verifier Zero Knowledge. + * * N.B. Maybe we need to refactor this, since before we execute this function wires are in lagrange basis * and after they are in monomial form. This is an inconsistency that can mislead developers. * @@ -147,20 +154,26 @@ template void ProverBase::compute_quotient_pre_com template void ProverBase::execute_preamble_round() { queue.flush_queue(); + transcript.add_element("circuit_size", { static_cast(n >> 24), static_cast(n >> 16), static_cast(n >> 8), static_cast(n) }); + transcript.add_element("public_input_size", { static_cast(key->num_public_inputs >> 24), static_cast(key->num_public_inputs >> 16), static_cast(key->num_public_inputs >> 8), static_cast(key->num_public_inputs) }); + transcript.apply_fiat_shamir("init"); - for (size_t i = 0; i < settings::program_width; ++i) { - // fetch witness wire w_i + // If this is a plookup proof, do not queue up an ifft on W_4 - we can only finish computing + // the lagrange-base values in W_4 once eta has been generated. + // This is because of the RAM/ROM subprotocol, which adds witnesses into W_4 that depend on eta + const size_t end = settings::is_plookup ? (settings::program_width - 1) : settings::program_width; + for (size_t i = 0; i < end; ++i) { std::string wire_tag = "w_" + std::to_string(i + 1); barretenberg::polynomial wire_lagrange = key->polynomial_cache.get(wire_tag + "_lagrange"); @@ -195,17 +208,20 @@ template void ProverBase::execute_preamble_round() // perfom an IFFT so that the "w_i" polynomials will contain the monomial form queue.add_to_queue({ - work_queue::WorkType::IFFT, - nullptr, - wire_tag, - barretenberg::fr(0), - 0, + .work_type = work_queue::WorkType::IFFT, + .mul_scalars = nullptr, + .tag = wire_tag, + .constant = barretenberg::fr(0), + .index = 0, }); } } /** - * Execute the first round by computing wire precommitments. + * Execute the first round: + * - Compute wire commitments. + * - Add public input values to the transcript + * * N.B. Random widget precommitments aren't actually being computed, since we are using permutation widget * which only does computation in compute_random_commitments function if the round is 3. * @@ -233,7 +249,7 @@ template void ProverBase::execute_first_round() #ifdef DEBUG_TIMING start = std::chrono::steady_clock::now(); #endif - compute_wire_pre_commitments(); + compute_wire_commitments(); for (auto& widget : random_widgets) { widget->compute_round_commitments(transcript, 1, queue); } @@ -245,30 +261,74 @@ template void ProverBase::execute_first_round() } /** - * Execute second round by applying Fiat-Shamir transform on the "eta" challenge - * and computing random_widgets round commitments that need to be computed at round 2. + * Execute second round: + * - Apply Fiat-Shamir transform to generate the "eta" challenge + * - Compute the random_widgets' round commitments that need to be computed at round 2. + * - If using plookup, we compute some w_4 values here (for gates which access "memory"), and apply blinding factors, + * before finally committing to w_4. * * @tname settings Program settings. * */ template void ProverBase::execute_second_round() { queue.flush_queue(); + transcript.apply_fiat_shamir("eta"); + for (auto& widget : random_widgets) { widget->compute_round_commitments(transcript, 2, queue); } + + // RAM/ROM memory subprotocol requires eta is generated before w_4 is comitted + if (settings::is_plookup) { + add_plookup_memory_records_to_w_4(); + std::string wire_tag = "w_4"; + barretenberg::polynomial& w_4_lagrange = key->polynomial_cache.get(wire_tag + "_lagrange"); + // barretenberg::polynomial& wire_fft = key->polynomial_cache.get(wire_tag + "_fft"); + barretenberg::polynomial w_4(key->n); + // barretenberg::polynomial_arithmetic::copy_polynomial(&wire[0], &wire_fft[0], n, n); + barretenberg::polynomial_arithmetic::copy_polynomial(&w_4_lagrange[0], &w_4[0], n, n); + + // TODO: This adds blinding to the what will become the w_4_monomial, NOT to the w_4_lagrange poly. Is this + // intentional? + const size_t w_randomness = 3; + ASSERT(w_randomness < settings::num_roots_cut_out_of_vanishing_polynomial); + for (size_t k = 0; k < w_randomness; ++k) { + // Blinding + w_4.at(n - settings::num_roots_cut_out_of_vanishing_polynomial + k) = fr::random_element(); + } + + // poly w_4 and add to cache + w_4.ifft(key->small_domain); + key->polynomial_cache.put(wire_tag, std::move(w_4)); + + // Commit to w_4: + queue.add_to_queue({ + .work_type = work_queue::WorkType::SCALAR_MULTIPLICATION, + .mul_scalars = key->polynomial_cache.get(wire_tag).get_coefficients(), + .tag = "W_4", + .constant = barretenberg::fr(0), + .index = 0, + }); + } } + /** - * Execute third round by applying Fiat-Shamir transform on the "beta" challenge, - * apply 3rd round random widgets and FFT the wires. For example, standard composer - * executes permutation widget for z polynomial construction at this round. + * Execute third round: + * - Apply Fiat-Shamir transform on the "beta" challenge + * - Apply 3rd round random widgets* + * - FFT the wires. + * + * *For example, standard composer executes permutation widget for z polynomial construction at this round. * * @tparam settings Program settings. * */ template void ProverBase::execute_third_round() { queue.flush_queue(); + transcript.apply_fiat_shamir("beta"); + #ifdef DEBUG_TIMING std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); #endif @@ -287,11 +347,11 @@ template void ProverBase::execute_third_round() for (size_t i = 0; i < settings::program_width; ++i) { std::string wire_tag = "w_" + std::to_string(i + 1); queue.add_to_queue({ - work_queue::WorkType::FFT, - nullptr, - wire_tag, - barretenberg::fr(0), - 0, + .work_type = work_queue::WorkType::FFT, + .mul_scalars = nullptr, + .tag = wire_tag, + .constant = barretenberg::fr(0), + .index = 0, }); } #ifdef DEBUG_TIMING @@ -301,6 +361,9 @@ template void ProverBase::execute_third_round() #endif } +/** + * @brief Computes the quotient polynomial, then commits to its degree-n split parts. + */ template void ProverBase::execute_fourth_round() { queue.flush_queue(); @@ -398,7 +461,7 @@ template void ProverBase::execute_fourth_round() add_blinding_to_quotient_polynomial_parts(); - compute_quotient_pre_commitment(); + compute_quotient_commitments(); #ifdef DEBUG_TIMING end = std::chrono::steady_clock::now(); diff = std::chrono::duration_cast(end - start); @@ -567,11 +630,12 @@ template waffle::plonk_proof& ProverBase::construc execute_second_round(); queue.process_queue(); - // Fiat-Shamir beta, execute random widgets (Permutation widget is executed here) + // Fiat-Shamir beta & gamma, execute random widgets (Permutation widget is executed here) // and fft the witnesses execute_third_round(); queue.process_queue(); + // Fiat-Shamir alpha, compute & commit to quotient polynomial. execute_fourth_round(); queue.process_queue(); @@ -591,11 +655,34 @@ template void ProverBase::reset() transcript = transcript::StandardTranscript(manifest, settings::hash_type, settings::num_challenge_bytes); } +template void ProverBase::add_plookup_memory_records_to_w_4() +{ + // We can only compute memory record values once W_1, W_2, W_3 have been comitted to, + // due to the dependence on the `eta` challenge. + + const fr eta = fr::serialize_from_buffer(transcript.get_challenge("eta").begin()); + const fr eta_sqr = eta.sqr(); + // At this point in the algorithm, the w_1, w_2, w_3 polynomials have been copied into + // the wire_fft map, and inverse fourier transforms have been applied to the original polynomials. + // We need their lagrange-base forms, so we tap into the wire_fft map. + // fr* w_1 = key->polynomial_cache.get("w_1_fft").get_coefficients(); + // fr* w_2 = key->polynomial_cache.get("w_2_fft").get_coefficients(); + // fr* w_3 = key->polynomial_cache.get("w_3_fft").get_coefficients(); + fr* w_1 = key->polynomial_cache.get("w_1_lagrange").get_coefficients(); + fr* w_2 = key->polynomial_cache.get("w_2_lagrange").get_coefficients(); + fr* w_3 = key->polynomial_cache.get("w_3_lagrange").get_coefficients(); + fr* w_4 = key->polynomial_cache.get("w_4_lagrange").get_coefficients(); + for (const auto& gate_idx : key->memory_records) { + w_4[gate_idx] = w_1[gate_idx] + w_2[gate_idx] * eta + w_3[gate_idx] * eta_sqr; + } +} + template class ProverBase; template class ProverBase; -template class ProverBase; +template class ProverBase; +template class ProverBase; template class ProverBase; template class ProverBase; -template class ProverBase; +template class ProverBase; } // namespace waffle diff --git a/cpp/src/aztec/plonk/proof_system/prover/prover.hpp b/cpp/src/aztec/plonk/proof_system/prover/prover.hpp index d456b28b88..da2cbdc66b 100644 --- a/cpp/src/aztec/plonk/proof_system/prover/prover.hpp +++ b/cpp/src/aztec/plonk/proof_system/prover/prover.hpp @@ -29,9 +29,11 @@ template class ProverBase { void add_polynomial_evaluations_to_transcript(); void compute_batch_opening_polynomials(); - void compute_wire_pre_commitments(); - void compute_quotient_pre_commitment(); + void compute_wire_commitments(); + void compute_quotient_commitments(); + void init_quotient_polynomials(); void compute_opening_elements(); + void add_plookup_memory_records_to_w_4(); void compute_linearisation_coefficients(); void add_blinding_to_quotient_polynomial_parts(); @@ -100,15 +102,19 @@ extern template class ProverBase; extern template class ProverBase; extern template class ProverBase; extern template class ProverBase; -extern template class ProverBase; +extern template class ProverBase; typedef ProverBase UnrolledProver; typedef ProverBase UnrolledTurboProver; -typedef ProverBase UnrolledPlookupProver; +typedef ProverBase + UnrolledUltraProver; // TODO: maybe just return a templated proverbase so that I don't need separate casees for + // ultra vs ultra_to_standard...??? +typedef ProverBase UnrolledUltraToStandardProver; typedef ProverBase UnrolledGenPermProver; + typedef ProverBase Prover; typedef ProverBase TurboProver; -typedef ProverBase PlookupProver; +typedef ProverBase UltraProver; typedef ProverBase GenPermProver; } // namespace waffle diff --git a/cpp/src/aztec/plonk/proof_system/prover/prover.test.cpp b/cpp/src/aztec/plonk/proof_system/prover/prover.test.cpp index 60281fcd8f..c2a5cf4035 100644 --- a/cpp/src/aztec/plonk/proof_system/prover/prover.test.cpp +++ b/cpp/src/aztec/plonk/proof_system/prover/prover.test.cpp @@ -4,7 +4,7 @@ #include "prover.hpp" #include -#include +#include #include #include diff --git a/cpp/src/aztec/plonk/proof_system/proving_key/proving_key.cpp b/cpp/src/aztec/plonk/proof_system/proving_key/proving_key.cpp index 7d97ad6594..5475d23798 100644 --- a/cpp/src/aztec/plonk/proof_system/proving_key/proving_key.cpp +++ b/cpp/src/aztec/plonk/proof_system/proving_key/proving_key.cpp @@ -6,7 +6,7 @@ namespace waffle { // In all the constructors below, the pippenger_runtime_state takes (n + 1) as the input // as the degree of t_{high}(X) is (n + 1) for standard plonk. Refer to -// ./src/aztec/plonk/proof_system/prover/prover.cpp/ProverBase::compute_quotient_pre_commitment +// ./src/aztec/plonk/proof_system/prover/prover.cpp/ProverBase::compute_quotient_commitments // for more details on this. // // NOTE: If the number of roots cut out of the vanishing polynomial is increased beyond 4, @@ -53,6 +53,7 @@ proving_key::proving_key(proving_key_data&& data, std::shared_ptr min_thread_block ? n : 4 * n) diff --git a/cpp/src/aztec/plonk/proof_system/proving_key/proving_key.hpp b/cpp/src/aztec/plonk/proof_system/proving_key/proving_key.hpp index e4a15e79e5..9957e79099 100644 --- a/cpp/src/aztec/plonk/proof_system/proving_key/proving_key.hpp +++ b/cpp/src/aztec/plonk/proof_system/proving_key/proving_key.hpp @@ -1,11 +1,10 @@ #pragma once #include #include - #include #include -#include +#include #include #include #include @@ -20,6 +19,7 @@ struct proving_key_data { uint32_t num_public_inputs; bool contains_recursive_proof; std::vector recursive_proof_public_input_indices; + std::vector memory_records; PolynomialCache polynomial_cache; }; @@ -47,6 +47,7 @@ struct proving_key { size_t num_public_inputs; bool contains_recursive_proof = false; std::vector recursive_proof_public_input_indices; + std::vector memory_records; // Used by UltraComposer only; for ROM. // Note: low-memory prover functionality can be achieved by uncommenting the lines below // which allow the polynomial cache to write polynomials to file as necessary. Similar // lines must also be uncommented in constructor. @@ -58,6 +59,8 @@ struct proving_key { barretenberg::evaluation_domain small_domain; barretenberg::evaluation_domain large_domain; + // We are keeping only one reference string which would be monomial srs if we use the Kate based PLONK, + // and Lagrange srs if we use the SHPLONK based PLONK. std::shared_ptr reference_string; barretenberg::polynomial quotient_polynomial_parts[NUM_QUOTIENT_PARTS]; diff --git a/cpp/src/aztec/plonk/proof_system/proving_key/serialize.hpp b/cpp/src/aztec/plonk/proof_system/proving_key/serialize.hpp index cb3743b7e5..e9e72d2a08 100644 --- a/cpp/src/aztec/plonk/proof_system/proving_key/serialize.hpp +++ b/cpp/src/aztec/plonk/proof_system/proving_key/serialize.hpp @@ -31,6 +31,7 @@ template inline void read(B& any, proving_key_data& key) read(any, key.contains_recursive_proof); read(any, key.recursive_proof_public_input_indices); + read(any, key.memory_records); } // Write the pre-computed polynomials @@ -55,6 +56,7 @@ template inline void write(B& buf, proving_key const& key) write(buf, key.contains_recursive_proof); write(buf, key.recursive_proof_public_input_indices); + write(buf, key.memory_records); } template inline void read_mmap(B& is, std::string const& path, proving_key_data& key) @@ -76,9 +78,8 @@ template inline void read_mmap(B& is, std::string const& path, prov } read(is, key.contains_recursive_proof); read(is, key.recursive_proof_public_input_indices); + read(is, key.memory_records); } - -// inline void write_mmap(std::ostream& os, std::string const& path, proving_key const& key) template inline void write_mmap(B& os, std::string const& path, proving_key const& key) { using serialize::write; @@ -107,6 +108,7 @@ template inline void write_mmap(B& os, std::string const& path, pro } write(os, key.contains_recursive_proof); write(os, key.recursive_proof_public_input_indices); + write(os, key.memory_records); } } // namespace waffle diff --git a/cpp/src/aztec/plonk/proof_system/public_inputs/public_inputs_impl.hpp b/cpp/src/aztec/plonk/proof_system/public_inputs/public_inputs_impl.hpp index 8586c5e6b8..e789417248 100644 --- a/cpp/src/aztec/plonk/proof_system/public_inputs/public_inputs_impl.hpp +++ b/cpp/src/aztec/plonk/proof_system/public_inputs/public_inputs_impl.hpp @@ -5,46 +5,33 @@ namespace waffle { /** * Public inputs! * - * This is a linear-time method of evaluating public inputs, that doesn't - *require modifications to any pre-processed selector polynomials. + * This is a linear-time method of evaluating public inputs, that doesn't require modifications to any pre-processed + * selector polynomials. * - * We validate public inputs by using a transition constraint to force every - *public input's copy permutation to be unbalanced. We then directly compute the 'delta' - * factor required - * to re-balance the permutation, and add it back into the grand product. + * We validate public inputs by using a transition constraint to force every public input's copy permutation to be + * unbalanced. We then directly compute the 'delta' factor required to re-balance the permutation, and add it back into + * the grand product. * - * Ok, let's wind back to the start. Let's say we have 'n' public inputs. + * Ok, let's wind back to the start. Let's say we have 'm' public inputs. * - * We reserve the first 'n' rows of program memory for public input validation. - * For each of these constraints, we *force* the first column's cell to be zero, - *using - * a standard arithmetic gate (i.e. w_i = 0 for the first i rows) + * We reserve the first 'm' rows of program memory for public input validation. For each of these constraints, we + * *force* the first column's cell to be zero, using a standard arithmetic gate (i.e. w_l[i] = 0 for the first i rows). * - * We then apply a copy constraint between the first two columns in program - *memory. - * i.e. for each row, the second cell is a copy of the first. + * We then apply a copy constraint between the first two columns in program memory. I.e. for each row, the second cell + * is a copy of the first, as in w_l[i] = w_r[i]. * - * We then apply a copy constraint that maps the second cell to memory cell in - *the circuit, - * to whererever the public input in question is required. + * We then apply a copy constraint that maps the second cell to whererever the public input in question is required. * - * This creates an unbalanced permutation. For the arithmetic constraint to be - *valid, the first - * cell must be 0. + * This creates an unbalanced permutation: + * - For the arithmetic constraint to be valid, the first cell must be 0. + * - But for the copy permutation to be valid, the first cell must be our public input! * - * But for the copy permutation to be valid, the first cell must be our public - *input! + * We make a further modification to the copy permutation argument. For the forced-zero cells, the *correct* permutation + * term for sigma_1(g_i) would be k.g_i , where k is a coset generator that maps to the second column. * - * We make a further modification to the copy permutation argument. For the - *forced-zero cells, - * the *correct* permutation term for sigma_1(g_i) would be k.g_i , where k is a - *coset generator - * that maps to the second column. + * However the actual permutation term for sigma_1(g_i) is just g_i. * - * However the actual permutation term for sigma_1(g_i) is just g_i - * - * This makes the permutation product, for the targeted zero-value public input - *cells, equal to 1 + * This makes the permutation product, for the targeted zero-value public input cells, equal to 1 * * We use the following notation: * @@ -59,8 +46,7 @@ namespace waffle { * (*) σ are the values of the j'th copy permutation selector polynomial * i, j * - * (*) k are coset generators, such that g . k is not an element of H, or - *the coset + * (*) k are coset generators, such that g . k is not an element of H, or the coset * j i j * produced by any other k value, for all l != j * l @@ -68,21 +54,15 @@ namespace waffle { * THIS is our normal permutation grand product: * * n - * ━┳━━┳━ / \ / \ / \ - * ┃ ┃ | w + β . g + γ | | w + β . k . g + γ | | w - *+ β . k . g + γ | - * ┃ ┃ | i, 1 i | | i, 2 1 i | | i, 3 - *2 i | - * ┃ ┃ | ━━━━━━━━━━━━━━━━━━━━━ | . | ━━━━━━━━━━━━━━━━━━━━━━ | . | - *━━━━━━━━━━━━━━━━━━━━━━ | = Z - * ┃ ┃ | w + β . σ + γ | | w + β . σ + γ | | w - *+ β . σ + γ | - * i = 1 \ i, 1 i, 1 / \ i, 2 i, 2 / \ i, 3 - *i, 3 / + * ━┳━━┳━ / \ / \ / \ + * ┃ ┃ | w + β . g + γ | | w + β . k . g + γ | | w + β . k . g + γ | + * ┃ ┃ | i, 1 i | | i, 2 1 i | | i,3 2 i | + * ┃ ┃ | ━━━━━━━━━━━━━━━━━━━━━ | . | ━━━━━━━━━━━━━━━━━━━━━━ | . |━━━━━━━━━━━━━━━━━━━━━━ | = z + * ┃ ┃ | w + β . σ + γ | | w + β . σ + γ | | w + β . σ + γ | + * i = 1 \ i, 1 i, 1 / \ i, 2 i, 2 / \ i,3 i, 3 / * * - * Now let's say that we have m public inputs. We transform the first m products - *involving column 1, into the following: + * Now let's say that we have m public inputs. We transform the first m products involving column 1, into the following: * * * m m @@ -94,8 +74,7 @@ namespace waffle { * i = 1 \ i, 1 i, 1 / i = 1 \ i / * * - * We now define a 'delta' term that can be publicly computed, which is the - *inverse of the following product: + * We now define a 'delta' term that can be publicly computed, which is the inverse of the following product: * * * m @@ -103,40 +82,35 @@ namespace waffle { * ┃ ┃ | w + β . g + γ | * ┃ ┃ | i, 1 i | 1 * ┃ ┃ | ━━━━━━━━━━━━━━━━━━━━━━ | = ━ - * ┃ ┃ | w + β . k . g + γ | δ + * ┃ ┃ | w + β . k . g + γ | Δ * i = 1 \ i, 1 i / * * - * i.e. we apply an explicit copy constraint that maps w to w for the - *first m witnesses + * i.e. we apply an explicit copy constraint that maps w to w for the first m witnesses * i, 1 i, 2 * * After applying these transformations, we have * - * Z = δ + * z = Δ * n * * * This can be validated by verifying that * - * (Z(X.g) - δ).L (X) = 0 mod Z'(X) + * (z(X.g) - Δ).L (X) = 0 mod Z'(X) * n-1 H * - * We check the n-1'th evaluation of Z(X.g), as opposed to the n'th evaluation - *of Z(X), because - * we need to cut the n'th subgroup element out of our vanishing polynomial - *Z'(X), as + * We check the n-1'th evaluation of z(X.g), as opposed to the n'th evaluation of z(X), because + * we need to cut the n'th subgroup element out of our vanishing polynomial Z'(X), as * H * the grand product polynomial identity does not hold at this subgroup element. * - * This validates the correctness of the public inputs. Specifically, that for - *the first m rows of program memory, - * the memory cells on the second column map to our public inputs. We can then - *use traditional copy constraints to map + * This validates the correctness of the public inputs. Specifically, that for the first m rows of program memory, + * the memory cells on the second column map to our public inputs. We can then use traditional copy constraints to map * these cells to other locations in program memory. **/ template -Field compute_public_input_delta(const std::vector& inputs, +Field compute_public_input_delta(const std::vector& public_inputs, const Field& beta, const Field& gamma, const Field& subgroup_generator) @@ -149,7 +123,7 @@ Field compute_public_input_delta(const std::vector& inputs, Field T1; Field T2; Field T3; - for (const auto& witness : inputs) { + for (const auto& witness : public_inputs) { T0 = witness + gamma; T1 = work_root * beta; T2 = T1 * Field::coset_generator(0); diff --git a/cpp/src/aztec/plonk/proof_system/types/polynomial_manifest.hpp b/cpp/src/aztec/plonk/proof_system/types/polynomial_manifest.hpp index 4f1a5c2e20..d26ffd394a 100644 --- a/cpp/src/aztec/plonk/proof_system/types/polynomial_manifest.hpp +++ b/cpp/src/aztec/plonk/proof_system/types/polynomial_manifest.hpp @@ -2,15 +2,10 @@ #include #include +#include namespace waffle { -enum ComposerType { - STANDARD, - TURBO, - PLOOKUP, -}; - enum PolynomialSource { WITNESS, SELECTOR, PERMUTATION }; enum PolynomialRepresentation { MONOMIAL, COSET_FFT }; @@ -25,11 +20,11 @@ enum PolynomialIndex { Q_5, Q_M, Q_C, - Q_ARITHMETIC_SELECTOR, - Q_FIXED_BASE_SELECTOR, - Q_RANGE_SELECTOR, - Q_SORT_SELECTOR, - Q_LOGIC_SELECTOR, + Q_ARITHMETIC, + Q_FIXED_BASE, + Q_RANGE, + Q_SORT, + Q_LOGIC, TABLE_1, TABLE_2, TABLE_3, @@ -37,6 +32,7 @@ enum PolynomialIndex { TABLE_INDEX, TABLE_TYPE, Q_ELLIPTIC, + Q_AUX, SIGMA_1, SIGMA_2, SIGMA_3, @@ -99,7 +95,8 @@ struct PolynomialDescriptor { PolynomialIndex index; }; -static constexpr PolynomialDescriptor standard_polynomial_manifest[12]{ +static constexpr size_t STANDARD_UNROLLED_MANIFEST_SIZE = 12; +static constexpr PolynomialDescriptor standard_polynomial_manifest[STANDARD_UNROLLED_MANIFEST_SIZE]{ PolynomialDescriptor("W_1", "w_1", false, false, WITNESS, W_1), // PolynomialDescriptor("W_2", "w_2", false, false, WITNESS, W_2), // PolynomialDescriptor("W_3", "w_3", false, false, WITNESS, W_3), // @@ -114,91 +111,63 @@ static constexpr PolynomialDescriptor standard_polynomial_manifest[12]{ PolynomialDescriptor("SIGMA_3", "sigma_3", true, false, PERMUTATION, SIGMA_3), // }; -static constexpr PolynomialDescriptor turbo_polynomial_manifest[20]{ - PolynomialDescriptor("W_1", "w_1", false, true, WITNESS, W_1), // - PolynomialDescriptor("W_2", "w_2", false, true, WITNESS, W_2), // - PolynomialDescriptor("W_3", "w_3", false, true, WITNESS, W_3), // - PolynomialDescriptor("W_4", "w_4", false, true, WITNESS, W_4), // - PolynomialDescriptor("Z_PERM", "z_perm", true, true, WITNESS, Z), // - PolynomialDescriptor("Q_1", "q_1", true, false, SELECTOR, Q_1), // - PolynomialDescriptor("Q_2", "q_2", true, false, SELECTOR, Q_2), // - PolynomialDescriptor("Q_3", "q_3", true, false, SELECTOR, Q_3), // - PolynomialDescriptor("Q_4", "q_4", true, false, SELECTOR, Q_4), // - PolynomialDescriptor("Q_5", "q_5", true, false, SELECTOR, Q_5), // - PolynomialDescriptor("Q_M", "q_m", true, false, SELECTOR, Q_M), // - PolynomialDescriptor("Q_C", "q_c", false, false, SELECTOR, Q_C), // - PolynomialDescriptor("Q_ARITHMETIC_SELECTOR", "q_arith", false, false, SELECTOR, Q_ARITHMETIC_SELECTOR), // - PolynomialDescriptor("Q_RANGE_SELECTOR", "q_range", true, false, SELECTOR, Q_RANGE_SELECTOR), // - PolynomialDescriptor("Q_FIXED_BASE_SELECTOR", "q_ecc_1", false, false, SELECTOR, Q_FIXED_BASE_SELECTOR), // - PolynomialDescriptor("Q_LOGIC_SELECTOR", "q_logic", true, false, SELECTOR, Q_LOGIC_SELECTOR), // - PolynomialDescriptor("SIGMA_1", "sigma_1", false, false, PERMUTATION, SIGMA_1), // - PolynomialDescriptor("SIGMA_2", "sigma_2", false, false, PERMUTATION, SIGMA_2), // - PolynomialDescriptor("SIGMA_3", "sigma_3", false, false, PERMUTATION, SIGMA_3), // - PolynomialDescriptor("SIGMA_4", "sigma_4", true, false, PERMUTATION, SIGMA_4), // -}; - -static constexpr PolynomialDescriptor plookup_polynomial_manifest[34]{ - PolynomialDescriptor("W_1", "w_1", false, true, WITNESS, W_1), // - PolynomialDescriptor("W_2", "w_2", false, true, WITNESS, W_2), // - PolynomialDescriptor("W_3", "w_3", false, true, WITNESS, W_3), // - PolynomialDescriptor("W_4", "w_4", false, true, WITNESS, W_4), // - PolynomialDescriptor("S", "s", false, true, WITNESS, S), // - PolynomialDescriptor("Z_PERM", "z_perm", true, true, WITNESS, Z), // - PolynomialDescriptor("Z_LOOKUP", "z_lookup", false, true, WITNESS, Z_LOOKUP), // - PolynomialDescriptor("Q_1", "q_1", true, false, SELECTOR, Q_1), // - PolynomialDescriptor("Q_2", "q_2", false, false, SELECTOR, Q_2), // - PolynomialDescriptor("Q_3", "q_3", false, false, SELECTOR, Q_3), // - PolynomialDescriptor("Q_4", "q_4", false, false, SELECTOR, Q_4), // - PolynomialDescriptor("Q_5", "q_5", false, false, SELECTOR, Q_5), // - PolynomialDescriptor("Q_M", "q_m", false, false, SELECTOR, Q_M), // - PolynomialDescriptor("Q_C", "q_c", false, false, SELECTOR, Q_C), // - PolynomialDescriptor("Q_ARITHMETIC_SELECTOR", "q_arith", false, false, SELECTOR, Q_ARITHMETIC_SELECTOR), // - PolynomialDescriptor("Q_RANGE_SELECTOR", "q_range", true, false, SELECTOR, Q_RANGE_SELECTOR), // - PolynomialDescriptor("Q_SORT_SELECTOR", "q_sort", true, false, SELECTOR, Q_SORT_SELECTOR), // - PolynomialDescriptor("Q_FIXED_BASE_SELECTOR", "q_ecc_1", false, false, SELECTOR, Q_FIXED_BASE_SELECTOR), // - PolynomialDescriptor("Q_LOGIC_SELECTOR", "q_logic", true, false, SELECTOR, Q_LOGIC_SELECTOR), // - PolynomialDescriptor("Q_ELLIPTIC", "q_elliptic", true, false, SELECTOR, Q_ELLIPTIC), // - PolynomialDescriptor("SIGMA_1", "sigma_1", false, false, PERMUTATION, SIGMA_1), // - PolynomialDescriptor("SIGMA_2", "sigma_2", false, false, PERMUTATION, SIGMA_2), // - PolynomialDescriptor("SIGMA_3", "sigma_3", false, false, PERMUTATION, SIGMA_3), // - PolynomialDescriptor("SIGMA_4", "sigma_4", true, false, PERMUTATION, SIGMA_4), // - PolynomialDescriptor("TABLE_1", "table_value_1", false, true, SELECTOR, TABLE_1), // - PolynomialDescriptor("TABLE_2", "table_value_2", false, true, SELECTOR, TABLE_2), // - PolynomialDescriptor("TABLE_3", "table_value_3", false, true, SELECTOR, TABLE_3), // - PolynomialDescriptor("TABLE_4", "table_value_4", false, true, SELECTOR, TABLE_4), // - PolynomialDescriptor("TABLE_INDEX", "table_index", false, false, SELECTOR, TABLE_INDEX), // - PolynomialDescriptor("TABLE_TYPE", "table_type", false, false, SELECTOR, TABLE_TYPE), // - PolynomialDescriptor("ID_1", "id_1", false, false, PERMUTATION, ID_1), // - PolynomialDescriptor("ID_2", "id_2", false, false, PERMUTATION, ID_2), // - PolynomialDescriptor("ID_3", "id_3", false, false, PERMUTATION, ID_3), // - PolynomialDescriptor("ID_4", "id_4", false, false, PERMUTATION, ID_4), // +static constexpr size_t TURBO_UNROLLED_MANIFEST_SIZE = 20; +static constexpr PolynomialDescriptor turbo_polynomial_manifest[TURBO_UNROLLED_MANIFEST_SIZE]{ + PolynomialDescriptor("W_1", "w_1", false, true, WITNESS, W_1), // + PolynomialDescriptor("W_2", "w_2", false, true, WITNESS, W_2), // + PolynomialDescriptor("W_3", "w_3", false, true, WITNESS, W_3), // + PolynomialDescriptor("W_4", "w_4", false, true, WITNESS, W_4), // + PolynomialDescriptor("Z_PERM", "z_perm", true, true, WITNESS, Z), // + PolynomialDescriptor("Q_1", "q_1", true, false, SELECTOR, Q_1), // + PolynomialDescriptor("Q_2", "q_2", true, false, SELECTOR, Q_2), // + PolynomialDescriptor("Q_3", "q_3", true, false, SELECTOR, Q_3), // + PolynomialDescriptor("Q_4", "q_4", true, false, SELECTOR, Q_4), // + PolynomialDescriptor("Q_5", "q_5", true, false, SELECTOR, Q_5), // + PolynomialDescriptor("Q_M", "q_m", true, false, SELECTOR, Q_M), // + PolynomialDescriptor("Q_C", "q_c", false, false, SELECTOR, Q_C), // + PolynomialDescriptor("Q_ARITHMETIC", "q_arith", false, false, SELECTOR, Q_ARITHMETIC), // + PolynomialDescriptor("Q_RANGE", "q_range", true, false, SELECTOR, Q_RANGE), // + PolynomialDescriptor("Q_FIXED_BASE", "q_fixed_base", false, false, SELECTOR, Q_FIXED_BASE), // + PolynomialDescriptor("Q_LOGIC", "q_logic", true, false, SELECTOR, Q_LOGIC), // + PolynomialDescriptor("SIGMA_1", "sigma_1", false, false, PERMUTATION, SIGMA_1), // + PolynomialDescriptor("SIGMA_2", "sigma_2", false, false, PERMUTATION, SIGMA_2), // + PolynomialDescriptor("SIGMA_3", "sigma_3", false, false, PERMUTATION, SIGMA_3), // + PolynomialDescriptor("SIGMA_4", "sigma_4", true, false, PERMUTATION, SIGMA_4), // }; -static constexpr PolynomialDescriptor genperm_polynomial_manifest[24]{ - PolynomialDescriptor("W_1", "w_1", false, true, WITNESS, W_1), // - PolynomialDescriptor("W_2", "w_2", false, true, WITNESS, W_2), // - PolynomialDescriptor("W_3", "w_3", false, true, WITNESS, W_3), // - PolynomialDescriptor("W_4", "w_4", false, true, WITNESS, W_4), // - PolynomialDescriptor("Z_PERM", "z_perm", true, true, WITNESS, Z), // - PolynomialDescriptor("Q_1", "q_1", true, false, SELECTOR, Q_1), // - PolynomialDescriptor("Q_2", "q_2", true, false, SELECTOR, Q_2), // - PolynomialDescriptor("Q_3", "q_3", true, false, SELECTOR, Q_3), // - PolynomialDescriptor("Q_4", "q_4", true, false, SELECTOR, Q_4), // - PolynomialDescriptor("Q_5", "q_5", true, false, SELECTOR, Q_5), // - PolynomialDescriptor("Q_M", "q_m", true, false, SELECTOR, Q_M), // - PolynomialDescriptor("Q_C", "q_c", false, false, SELECTOR, Q_C), // - PolynomialDescriptor("Q_ARITHMETIC_SELECTOR", "q_arith", false, false, SELECTOR, Q_ARITHMETIC_SELECTOR), // - PolynomialDescriptor("Q_RANGE_SELECTOR", "q_range", true, false, SELECTOR, Q_RANGE_SELECTOR), // - PolynomialDescriptor("Q_FIXED_BASE_SELECTOR", "q_ecc_1", false, false, SELECTOR, Q_FIXED_BASE_SELECTOR), // - PolynomialDescriptor("Q_LOGIC_SELECTOR", "q_logic", true, false, SELECTOR, Q_LOGIC_SELECTOR), // - PolynomialDescriptor("SIGMA_1", "sigma_1", false, false, PERMUTATION, SIGMA_1), // - PolynomialDescriptor("SIGMA_2", "sigma_2", false, false, PERMUTATION, SIGMA_2), // - PolynomialDescriptor("SIGMA_3", "sigma_3", false, false, PERMUTATION, SIGMA_3), // - PolynomialDescriptor("SIGMA_4", "sigma_4", true, false, PERMUTATION, SIGMA_4), // - PolynomialDescriptor("ID_1", "id_1", false, false, PERMUTATION, ID_1), // - PolynomialDescriptor("ID_2", "id_2", false, false, PERMUTATION, ID_2), // - PolynomialDescriptor("ID_3", "id_3", false, false, PERMUTATION, ID_3), // - PolynomialDescriptor("ID_4", "id_4", false, false, PERMUTATION, ID_4), // +static constexpr size_t ULTRA_UNROLLED_MANIFEST_SIZE = 31; +static constexpr PolynomialDescriptor ultra_polynomial_manifest[ULTRA_UNROLLED_MANIFEST_SIZE]{ + PolynomialDescriptor("W_1", "w_1", false, true, WITNESS, W_1), // + PolynomialDescriptor("W_2", "w_2", false, true, WITNESS, W_2), // + PolynomialDescriptor("W_3", "w_3", false, true, WITNESS, W_3), // + PolynomialDescriptor("W_4", "w_4", false, true, WITNESS, W_4), // + PolynomialDescriptor("S", "s", false, true, WITNESS, S), // + PolynomialDescriptor("Z_PERM", "z_perm", true, true, WITNESS, Z), // + PolynomialDescriptor("Z_LOOKUP", "z_lookup", false, true, WITNESS, Z_LOOKUP), // + PolynomialDescriptor("Q_1", "q_1", false, false, SELECTOR, Q_1), // + PolynomialDescriptor("Q_2", "q_2", false, false, SELECTOR, Q_2), // + PolynomialDescriptor("Q_3", "q_3", false, false, SELECTOR, Q_3), // + PolynomialDescriptor("Q_4", "q_4", false, false, SELECTOR, Q_4), // + PolynomialDescriptor("Q_M", "q_m", false, false, SELECTOR, Q_M), // + PolynomialDescriptor("Q_C", "q_c", false, false, SELECTOR, Q_C), // + PolynomialDescriptor("Q_ARITHMETIC", "q_arith", false, false, SELECTOR, Q_ARITHMETIC), // + PolynomialDescriptor("Q_FIXED_BASE", "q_fixed_base", false, false, SELECTOR, Q_FIXED_BASE), // + PolynomialDescriptor("Q_SORT", "q_sort", true, false, SELECTOR, Q_SORT), // + PolynomialDescriptor("Q_ELLIPTIC", "q_elliptic", true, false, SELECTOR, Q_ELLIPTIC), // + PolynomialDescriptor("Q_AUX", "q_aux", false, false, SELECTOR, Q_AUX), // + PolynomialDescriptor("SIGMA_1", "sigma_1", false, false, PERMUTATION, SIGMA_1), // + PolynomialDescriptor("SIGMA_2", "sigma_2", false, false, PERMUTATION, SIGMA_2), // + PolynomialDescriptor("SIGMA_3", "sigma_3", false, false, PERMUTATION, SIGMA_3), // + PolynomialDescriptor("SIGMA_4", "sigma_4", true, false, PERMUTATION, SIGMA_4), // + PolynomialDescriptor("TABLE_1", "table_value_1", false, true, SELECTOR, TABLE_1), // + PolynomialDescriptor("TABLE_2", "table_value_2", false, true, SELECTOR, TABLE_2), // + PolynomialDescriptor("TABLE_3", "table_value_3", false, true, SELECTOR, TABLE_3), // + PolynomialDescriptor("TABLE_4", "table_value_4", false, true, SELECTOR, TABLE_4), // + PolynomialDescriptor("TABLE_TYPE", "table_type", false, false, SELECTOR, TABLE_TYPE), // + PolynomialDescriptor("ID_1", "id_1", false, false, PERMUTATION, ID_1), // + PolynomialDescriptor("ID_2", "id_2", false, false, PERMUTATION, ID_2), // + PolynomialDescriptor("ID_3", "id_3", false, false, PERMUTATION, ID_3), // + PolynomialDescriptor("ID_4", "id_4", false, false, PERMUTATION, ID_4), // }; // Simple class allowing for access to a polynomial manifest based on composer type @@ -213,15 +182,21 @@ class PolynomialManifest { { switch (composer_type) { case ComposerType::STANDARD: { - std::copy(standard_polynomial_manifest, standard_polynomial_manifest + 12, std::back_inserter(manifest)); + std::copy(standard_polynomial_manifest, + standard_polynomial_manifest + STANDARD_UNROLLED_MANIFEST_SIZE, + std::back_inserter(manifest)); break; }; case ComposerType::TURBO: { - std::copy(turbo_polynomial_manifest, turbo_polynomial_manifest + 20, std::back_inserter(manifest)); + std::copy(turbo_polynomial_manifest, + turbo_polynomial_manifest + TURBO_UNROLLED_MANIFEST_SIZE, + std::back_inserter(manifest)); break; }; case ComposerType::PLOOKUP: { - std::copy(plookup_polynomial_manifest, plookup_polynomial_manifest + 34, std::back_inserter(manifest)); + std::copy(ultra_polynomial_manifest, + ultra_polynomial_manifest + ULTRA_UNROLLED_MANIFEST_SIZE, + std::back_inserter(manifest)); break; }; default: { diff --git a/cpp/src/aztec/plonk/proof_system/types/program_settings.hpp b/cpp/src/aztec/plonk/proof_system/types/program_settings.hpp index dd42d2231d..425facb295 100644 --- a/cpp/src/aztec/plonk/proof_system/types/program_settings.hpp +++ b/cpp/src/aztec/plonk/proof_system/types/program_settings.hpp @@ -5,10 +5,12 @@ #include "../../transcript/transcript_wrappers.hpp" #include "../widgets/transition_widgets/arithmetic_widget.hpp" #include "../widgets/transition_widgets/turbo_arithmetic_widget.hpp" -#include "../widgets/transition_widgets/turbo_fixed_base_widget.hpp" +#include "../widgets/transition_widgets/plookup_arithmetic_widget.hpp" +#include "../widgets/transition_widgets/fixed_base_widget.hpp" #include "../widgets/transition_widgets/turbo_logic_widget.hpp" #include "../widgets/transition_widgets/turbo_range_widget.hpp" #include "../widgets/transition_widgets/elliptic_widget.hpp" +#include "../widgets/transition_widgets/plookup_auxiliary_widget.hpp" #include "../widgets/transition_widgets/genperm_sort_widget.hpp" #include "../widgets/random_widgets/random_widget.hpp" #include "../widgets/random_widgets/permutation_widget.hpp" @@ -62,7 +64,7 @@ class unrolled_standard_verifier_settings : public unrolled_standard_settings { typedef VerifierArithmeticWidget ArithmeticWidget; typedef VerifierPermutationWidget PermutationWidget; - static constexpr transcript::HashType hash_type = transcript::HashType::PedersenBlake2s; + static constexpr transcript::HashType hash_type = transcript::HashType::PedersenBlake3s; static constexpr size_t num_challenge_bytes = 16; static constexpr bool use_linearisation = false; static constexpr bool idpolys = false; @@ -147,18 +149,19 @@ class turbo_verifier_settings : public turbo_settings { } }; -class plookup_verifier_settings : public plookup_settings { +class ultra_verifier_settings : public ultra_settings { public: typedef barretenberg::fr fr; typedef barretenberg::g1 g1; typedef transcript::StandardTranscript Transcript; - typedef VerifierTurboArithmeticWidget TurboArithmeticWidget; - typedef VerifierTurboFixedBaseWidget TurboFixedBaseWidget; - typedef VerifierGenPermSortWidget GenPermSortWidget; - typedef VerifierTurboLogicWidget TurboLogicWidget; + typedef VerifierPlookupArithmeticWidget PlookupArithmeticWidget; + typedef VerifierUltraFixedBaseWidget UltraFixedBaseWidget; + typedef VerifierGenPermSortWidget GenPermSortWidget; + typedef VerifierTurboLogicWidget TurboLogicWidget; typedef VerifierPermutationWidget PermutationWidget; typedef VerifierPlookupWidget PlookupWidget; - typedef VerifierEllipticWidget EllipticWidget; + typedef VerifierEllipticWidget EllipticWidget; + typedef VerifierPlookupAuxiliaryWidget PlookupAuxiliaryWidget; static constexpr size_t num_challenge_bytes = 32; static constexpr transcript::HashType hash_type = transcript::HashType::Keccak256; @@ -170,18 +173,19 @@ class plookup_verifier_settings : public plookup_settings { const transcript::StandardTranscript& transcript, std::map& scalars) { + // Similarly for unrolled case. auto updated_alpha = PermutationWidget::append_scalar_multiplication_inputs( - key, alpha_base, transcript, scalars, use_linearisation, true); + key, alpha_base, transcript, scalars, use_linearisation, idpolys); updated_alpha = PlookupWidget::append_scalar_multiplication_inputs( key, updated_alpha, transcript, scalars, use_linearisation); - updated_alpha = - TurboArithmeticWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); + PlookupArithmeticWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); updated_alpha = - TurboFixedBaseWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); + UltraFixedBaseWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); updated_alpha = GenPermSortWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); - updated_alpha = TurboLogicWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); updated_alpha = EllipticWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); + updated_alpha = + PlookupAuxiliaryWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); return updated_alpha; } @@ -195,17 +199,16 @@ class plookup_verifier_settings : public plookup_settings { key, alpha_base, transcript, r_0, use_linearisation, idpolys); updated_alpha_base = PlookupWidget::compute_quotient_evaluation_contribution( key, updated_alpha_base, transcript, r_0, use_linearisation); - updated_alpha_base = - TurboArithmeticWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); + PlookupArithmeticWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); updated_alpha_base = - TurboFixedBaseWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); + UltraFixedBaseWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); updated_alpha_base = GenPermSortWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); - updated_alpha_base = - TurboLogicWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); updated_alpha_base = EllipticWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); + updated_alpha_base = + PlookupAuxiliaryWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); return updated_alpha_base; } @@ -224,8 +227,11 @@ class unrolled_turbo_verifier_settings : public unrolled_turbo_settings { typedef VerifierTurboLogicWidget TurboLogicWidget; typedef VerifierPermutationWidget PermutationWidget; - static constexpr size_t num_challenge_bytes = 16; - static constexpr transcript::HashType hash_type = transcript::HashType::PedersenBlake2s; + static constexpr size_t num_challenge_bytes = + 16; // In the unrolled setting, challenges are only 128-bits (16-bytes) to reduce the number of constraints + // required in the verification circuit. 128-bits is ample security, given the security of altBN254 snarks + // is in the low-100-bits. + static constexpr transcript::HashType hash_type = transcript::HashType::PedersenBlake3s; static constexpr bool use_linearisation = false; static constexpr bool idpolys = false; @@ -268,25 +274,27 @@ class unrolled_turbo_verifier_settings : public unrolled_turbo_settings { } }; -class unrolled_plookup_verifier_settings : public unrolled_plookup_settings { +class unrolled_ultra_verifier_settings : public unrolled_ultra_settings { public: typedef barretenberg::fr fr; typedef barretenberg::g1 g1; typedef transcript::StandardTranscript Transcript; - typedef VerifierTurboArithmeticWidget - TurboArithmeticWidget; - typedef VerifierTurboFixedBaseWidget - TurboFixedBaseWidget; - typedef VerifierTurboRangeWidget TurboRangeWidget; - typedef VerifierTurboLogicWidget TurboLogicWidget; + typedef VerifierPlookupArithmeticWidget + PlookupArithmeticWidget; + typedef VerifierUltraFixedBaseWidget + UltraFixedBaseWidget; + typedef VerifierGenPermSortWidget GenPermSortWidget; + typedef VerifierTurboLogicWidget TurboLogicWidget; typedef VerifierPermutationWidget PermutationWidget; typedef VerifierPlookupWidget PlookupWidget; - typedef VerifierEllipticWidget EllipticWidget; + typedef VerifierEllipticWidget EllipticWidget; + typedef VerifierPlookupAuxiliaryWidget + PlookupAuxiliaryWidget; static constexpr size_t num_challenge_bytes = 16; - static constexpr transcript::HashType hash_type = transcript::HashType::PedersenBlake2s; + static constexpr transcript::HashType hash_type = transcript::HashType::PlookupPedersenBlake3s; static constexpr bool use_linearisation = false; - static constexpr bool idpolys = false; + static constexpr bool idpolys = true; static fr append_scalar_multiplication_inputs(verification_key* key, const fr& alpha_base, @@ -294,17 +302,17 @@ class unrolled_plookup_verifier_settings : public unrolled_plookup_settings { std::map& scalars) { auto updated_alpha = PermutationWidget::append_scalar_multiplication_inputs( - key, alpha_base, transcript, scalars, use_linearisation); + key, alpha_base, transcript, scalars, use_linearisation, idpolys); updated_alpha = PlookupWidget::append_scalar_multiplication_inputs( key, updated_alpha, transcript, scalars, use_linearisation); - updated_alpha = - TurboArithmeticWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); + PlookupArithmeticWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); updated_alpha = - TurboFixedBaseWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); - updated_alpha = TurboRangeWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); - updated_alpha = TurboLogicWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); + UltraFixedBaseWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); + updated_alpha = GenPermSortWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); updated_alpha = EllipticWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); + updated_alpha = + PlookupAuxiliaryWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); return updated_alpha; } @@ -315,25 +323,46 @@ class unrolled_plookup_verifier_settings : public unrolled_plookup_settings { barretenberg::fr& r_0) { auto updated_alpha_base = PermutationWidget::compute_quotient_evaluation_contribution( - key, alpha_base, transcript, r_0, use_linearisation, true); + key, alpha_base, transcript, r_0, use_linearisation, idpolys); updated_alpha_base = PlookupWidget::compute_quotient_evaluation_contribution( key, updated_alpha_base, transcript, r_0, use_linearisation); - updated_alpha_base = - TurboArithmeticWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); - updated_alpha_base = - TurboFixedBaseWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); + PlookupArithmeticWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); updated_alpha_base = - TurboRangeWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); + UltraFixedBaseWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); updated_alpha_base = - TurboLogicWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); + GenPermSortWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); updated_alpha_base = EllipticWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); + updated_alpha_base = + PlookupAuxiliaryWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); return updated_alpha_base; } }; +// Only needed because ultra-to-standard recursion requires us to use a Pedersen hash which is common to both Ultra & +// Standard plonk i.e. the non-ultra version. +class unrolled_ultra_to_standard_verifier_settings : public unrolled_ultra_verifier_settings { + public: + typedef VerifierPlookupArithmeticWidget + PlookupArithmeticWidget; + typedef VerifierUltraFixedBaseWidget + UltraFixedBaseWidget; + typedef VerifierGenPermSortWidget + GenPermSortWidget; + typedef VerifierTurboLogicWidget + TurboLogicWidget; + typedef VerifierPermutationWidget PermutationWidget; + typedef VerifierPlookupWidget PlookupWidget; + typedef VerifierEllipticWidget + EllipticWidget; + typedef VerifierPlookupAuxiliaryWidget + PlookupAuxiliaryWidget; + + static constexpr transcript::HashType hash_type = transcript::HashType::PedersenBlake3s; +}; + class generalized_permutation_verifier_settings : public turbo_settings { public: typedef barretenberg::fr fr; diff --git a/cpp/src/aztec/plonk/proof_system/types/prover_settings.hpp b/cpp/src/aztec/plonk/proof_system/types/prover_settings.hpp index 3f1a70d006..a10433ddff 100644 --- a/cpp/src/aztec/plonk/proof_system/types/prover_settings.hpp +++ b/cpp/src/aztec/plonk/proof_system/types/prover_settings.hpp @@ -20,12 +20,13 @@ class standard_settings : public settings_base { static constexpr uint32_t permutation_mask = 0xC0000000; static constexpr bool use_linearisation = true; static constexpr size_t num_roots_cut_out_of_vanishing_polynomial = 4; + static constexpr bool is_plookup = false; }; class unrolled_standard_settings : public settings_base { public: static constexpr size_t num_challenge_bytes = 16; - static constexpr transcript::HashType hash_type = transcript::HashType::PedersenBlake2s; + static constexpr transcript::HashType hash_type = transcript::HashType::PedersenBlake3s; static constexpr size_t program_width = 3; static constexpr size_t num_shifted_wire_evaluations = 1; static constexpr uint64_t wire_shift_settings = 0b0100; @@ -33,6 +34,7 @@ class unrolled_standard_settings : public settings_base { static constexpr uint32_t permutation_mask = 0xC0000000; static constexpr bool use_linearisation = false; static constexpr size_t num_roots_cut_out_of_vanishing_polynomial = 4; + static constexpr bool is_plookup = false; }; class turbo_settings : public settings_base { @@ -46,9 +48,10 @@ class turbo_settings : public settings_base { static constexpr uint32_t permutation_mask = 0xC0000000; static constexpr bool use_linearisation = true; static constexpr size_t num_roots_cut_out_of_vanishing_polynomial = 4; + static constexpr bool is_plookup = false; }; -class plookup_settings : public settings_base { +class ultra_settings : public settings_base { public: static constexpr size_t num_challenge_bytes = 32; static constexpr transcript::HashType hash_type = transcript::HashType::Keccak256; @@ -59,12 +62,13 @@ class plookup_settings : public settings_base { static constexpr uint32_t permutation_mask = 0xC0000000; static constexpr bool use_linearisation = true; static constexpr size_t num_roots_cut_out_of_vanishing_polynomial = 4; + static constexpr bool is_plookup = true; }; -class unrolled_plookup_settings : public settings_base { +class unrolled_ultra_settings : public settings_base { public: static constexpr size_t num_challenge_bytes = 16; - static constexpr transcript::HashType hash_type = transcript::HashType::PedersenBlake2s; + static constexpr transcript::HashType hash_type = transcript::HashType::PlookupPedersenBlake3s; static constexpr size_t program_width = 4; static constexpr size_t num_shifted_wire_evaluations = 4; static constexpr uint64_t wire_shift_settings = 0b1111; @@ -72,12 +76,20 @@ class unrolled_plookup_settings : public settings_base { static constexpr uint32_t permutation_mask = 0xC0000000; static constexpr bool use_linearisation = false; static constexpr size_t num_roots_cut_out_of_vanishing_polynomial = 4; + static constexpr bool is_plookup = true; +}; + +// Only needed because ultra-to-standard recursion requires us to use a Pedersen hash which is common to both Ultra & +// Standard plonk i.e. the non-ultra version. +class unrolled_ultra_to_standard_settings : public unrolled_ultra_settings { + public: + static constexpr transcript::HashType hash_type = transcript::HashType::PedersenBlake3s; }; class unrolled_turbo_settings : public settings_base { public: static constexpr size_t num_challenge_bytes = 16; - static constexpr transcript::HashType hash_type = transcript::HashType::PedersenBlake2s; + static constexpr transcript::HashType hash_type = transcript::HashType::PedersenBlake3s; static constexpr size_t program_width = 4; static constexpr size_t num_shifted_wire_evaluations = 4; static constexpr uint64_t wire_shift_settings = 0b1111; @@ -85,5 +97,6 @@ class unrolled_turbo_settings : public settings_base { static constexpr uint32_t permutation_mask = 0xC0000000; static constexpr bool use_linearisation = false; static constexpr size_t num_roots_cut_out_of_vanishing_polynomial = 4; + static constexpr bool is_plookup = false; }; } // namespace waffle \ No newline at end of file diff --git a/cpp/src/aztec/plonk/proof_system/utils/kate_verification.hpp b/cpp/src/aztec/plonk/proof_system/utils/kate_verification.hpp index 600466ec6c..9bfc669dd9 100644 --- a/cpp/src/aztec/plonk/proof_system/utils/kate_verification.hpp +++ b/cpp/src/aztec/plonk/proof_system/utils/kate_verification.hpp @@ -104,11 +104,7 @@ void populate_kate_element_map(verification_key* key, const auto zeta = transcript.get_challenge_field_element("z", 0); const auto quotient_nu = transcript.get_challenge_field_element_from_map("nu", "t"); - Field z_pow_n = zeta; - const size_t log2_n = numeric::get_msb(key->n); - for (size_t j = 0; j < log2_n; ++j) { - z_pow_n = z_pow_n.sqr(); - } + Field z_pow_n = zeta.pow(key->n); Field z_power = 1; for (size_t i = 0; i < program_settings::program_width; ++i) { std::string quotient_label = "T_" + std::to_string(i + 1); @@ -149,10 +145,10 @@ inline void print_turbo_verification_key(verification_key* key) print_g1("Q5", key->constraint_selectors.at("Q_5")); print_g1("QM", key->constraint_selectors.at("Q_M")); print_g1("QC", key->constraint_selectors.at("Q_C")); - print_g1("QARITH", key->constraint_selectors.at("Q_ARITHMETIC_SELECTOR")); - print_g1("QECC", key->constraint_selectors.at("Q_FIXED_BASE_SELECTOR")); - print_g1("QRANGE", key->constraint_selectors.at("Q_RANGE_SELECTOR")); - print_g1("QLOGIC", key->constraint_selectors.at("Q_LOGIC_SELECTOR")); + print_g1("QARITH", key->constraint_selectors.at("Q_ARITHMETIC")); + print_g1("QFIXEDBASE", key->constraint_selectors.at("Q_FIXED_BASE")); + print_g1("QRANGE", key->constraint_selectors.at("Q_RANGE")); + print_g1("QLOGIC", key->constraint_selectors.at("Q_LOGIC")); print_g1("sigma_commitments[0]", key->permutation_selectors.at("SIGMA_1")); print_g1("sigma_commitments[1]", key->permutation_selectors.at("SIGMA_2")); print_g1("sigma_commitments[2]", key->permutation_selectors.at("SIGMA_3")); diff --git a/cpp/src/aztec/plonk/proof_system/utils/permutation.hpp b/cpp/src/aztec/plonk/proof_system/utils/permutation.hpp index 170c9ac34e..2203c60eb1 100644 --- a/cpp/src/aztec/plonk/proof_system/utils/permutation.hpp +++ b/cpp/src/aztec/plonk/proof_system/utils/permutation.hpp @@ -59,49 +59,46 @@ inline void compute_permutation_lagrange_base_single(barretenberg::polynomial& o const size_t log2_root_size = static_cast(numeric::get_msb(root_size)); ITERATE_OVER_DOMAIN_START(small_domain); - // permutation[i] will specify the 'index' that this wire value will map to - // here, 'index' refers to an element of our subgroup H - // we can almost use permutation[i] to directly index our `roots` array, which contains our subgroup elements - // we first have to mask off the 2 high bits, which describe which wire polynomial our permutation maps to (we'll - // deal with that in a bit) we then have to accomodate for the fact that, `roots` only contains *half* of our - // subgroup elements. this is because w^{n/2} = -w and we don't want to perform redundant work computing roots of - // unity - // Step 1: mask the high bits and get the permutation index + // `permutation[i]` will specify the 'index' that this wire value will map to. + // Here, 'index' refers to an element of our subgroup H. + // We can almost use `permutation[i]` to directly index our `roots` array, which contains our subgroup elements. + // We first have to accomodate for the fact that `roots` only contains *half* of our subgroup elements. This is + // because ω^{n/2} = -ω and we don't want to perform redundant work computing roots of unity. + size_t raw_idx = permutation[i].subgroup_index; - bool is_public_input = permutation[i].is_public_input; - bool is_tag = permutation[i].is_tag; - // Step 2: is `raw_idx` >= (n / 2)? if so, we will need to index `-roots[raw_idx - subgroup_size / 2]` instead + // Step 1: is `raw_idx` >= (n / 2)? if so, we will need to index `-roots[raw_idx - subgroup_size / 2]` instead // of `roots[raw_idx]` const bool negative_idx = raw_idx >= root_size; - // Step 3: compute the index of the subgroup element we'll be accessing. - // To avoid a conditional branch, we can subtract `negative_idx << log2_root_size` from `raw_idx` - // here, `log2_root_size = numeric::get_msb(subgroup_size / 2)` (we know our subgroup size will be a power of 2, + // Step 2: compute the index of the subgroup element we'll be accessing. + // To avoid a conditional branch, we can subtract `negative_idx << log2_root_size` from `raw_idx`. + // Here, `log2_root_size = numeric::get_msb(subgroup_size / 2)` (we know our subgroup size will be a power of 2, // so we lose no precision here) const size_t idx = raw_idx - (static_cast(negative_idx) << log2_root_size); - // call `conditionally_subtract_double_modulus`, using `negative_idx` as our predicate. + // Call `conditionally_subtract_double_modulus`, using `negative_idx` as our predicate. // Our roots of unity table is partially 'overloaded' - we either store the root `w`, or `modulus + w` // So to ensure we correctly compute `modulus - w`, we need to compute `2 * modulus - w` // The output will similarly be overloaded (containing either 2 * modulus - w, or modulus - w) output[i] = roots[idx].conditionally_subtract_from_double_modulus(static_cast(negative_idx)); - // finally, if our permutation maps to an index in either the right wire vector, or the output wire vector, we - // need to multiply our result by one of two quadratic non-residues. (this ensure that mapping into the left + // Finally, if our permutation maps to an index in either the right wire vector, or the output wire vector, we + // need to multiply our result by one of two quadratic non-residues. (This ensures that mapping into the left // wires gives unique values that are not repeated in the right or output wire permutations) (ditto for right // wire and output wire mappings) - if (is_public_input) { + if (permutation[i].is_public_input) { + // As per the paper which modifies plonk to include the public inputs in a permutation argument, the permutation + // `σ` is modified to `σ'`, where `σ'` maps all public inputs to a set of l distinct ζ elements which are + // disjoint from H ∪ k1·H ∪ k2·H. output[i] *= barretenberg::fr::external_coset_generator(); - } else if (is_tag) { + } else if (permutation[i].is_tag) { output[i] *= barretenberg::fr::tag_coset_generator(); } else { { - // isolate the highest 2 bits of `permutation[i]` and shunt them down into the 2 least significant bits const uint32_t column_index = permutation[i].column_index; - // ((permutation[i] & program_settings::permutation_mask) >> program_settings::permutation_shift); if (column_index > 0) { output[i] *= barretenberg::fr::coset_generator(column_index - 1); } diff --git a/cpp/src/aztec/plonk/proof_system/verification_key/verification_key.cpp b/cpp/src/aztec/plonk/proof_system/verification_key/verification_key.cpp index bbbc37fe34..20cbce3b50 100644 --- a/cpp/src/aztec/plonk/proof_system/verification_key/verification_key.cpp +++ b/cpp/src/aztec/plonk/proof_system/verification_key/verification_key.cpp @@ -1,5 +1,6 @@ #include #include "verification_key.hpp" +#include "../constants.hpp" namespace waffle { diff --git a/cpp/src/aztec/plonk/proof_system/verification_key/verification_key.hpp b/cpp/src/aztec/plonk/proof_system/verification_key/verification_key.hpp index 007ed56ae8..3a4bb977dc 100644 --- a/cpp/src/aztec/plonk/proof_system/verification_key/verification_key.hpp +++ b/cpp/src/aztec/plonk/proof_system/verification_key/verification_key.hpp @@ -1,6 +1,6 @@ #pragma once #include -#include +#include #include #include #include "../types/polynomial_manifest.hpp" @@ -76,9 +76,9 @@ struct verification_key { PolynomialManifest polynomial_manifest; - // this is a member variable because stdlib::field has no `pow` method, we - // have to compute this differently for the normal and recursive settings respectively - barretenberg::fr z_pow_n; + // This is a member variable so as to avoid recomputing it in the different places of the verifier algorithm. + // Note that recomputing would also have added constraints to the recursive verifier circuit. + barretenberg::fr z_pow_n; // ʓ^n (ʓ being the 'evaluation challenge') bool contains_recursive_proof = false; std::vector recursive_proof_public_input_indices; diff --git a/cpp/src/aztec/plonk/proof_system/verification_key/verification_key.test.cpp b/cpp/src/aztec/plonk/proof_system/verification_key/verification_key.test.cpp index 07e767609f..4022da80d7 100644 --- a/cpp/src/aztec/plonk/proof_system/verification_key/verification_key.test.cpp +++ b/cpp/src/aztec/plonk/proof_system/verification_key/verification_key.test.cpp @@ -1,6 +1,7 @@ #include #include #include "verification_key.hpp" +#include "../constants.hpp" using namespace barretenberg; using namespace waffle; diff --git a/cpp/src/aztec/plonk/proof_system/verifier/verifier.cpp b/cpp/src/aztec/plonk/proof_system/verifier/verifier.cpp index 73b79e9ff1..f4513ea313 100644 --- a/cpp/src/aztec/plonk/proof_system/verifier/verifier.cpp +++ b/cpp/src/aztec/plonk/proof_system/verifier/verifier.cpp @@ -45,23 +45,29 @@ template bool VerifierBase::verify // π_SNARK = { [a]_1,[b]_1,[c]_1,[z]_1,[t_{low}]_1,[t_{mid}]_1,[t_{high}]_1,[W_z]_1,[W_zω]_1 \in G, // a_eval, b_eval, c_eval, sigma1_eval, sigma2_eval, z_eval_omega \in F } // - // Proof π_SNARK must be first added in the transcrip with other program settings. - // + // Proof π_SNARK must first be added to the transcript with the other program_settings. key->program_width = program_settings::program_width; + + // Add the proof data to the transcript, according to the manifest. Also initialise the transcript's hash type and + // challenge bytes. transcript::StandardTranscript transcript = transcript::StandardTranscript( proof.proof_data, manifest, program_settings::hash_type, program_settings::num_challenge_bytes); - // Compute challenges using Fiat-Shamir heuristic from transcript + + // From the verification key, also add n & l (the circuit size and the number of public inputs) to the transcript. transcript.add_element("circuit_size", { static_cast(key->n >> 24), static_cast(key->n >> 16), static_cast(key->n >> 8), static_cast(key->n) }); + transcript.add_element("public_input_size", { static_cast(key->num_public_inputs >> 24), static_cast(key->num_public_inputs >> 16), static_cast(key->num_public_inputs >> 8), static_cast(key->num_public_inputs) }); + + // Compute challenges from the proof data, based on the manifest, using the Fiat-Shamir heuristic transcript.apply_fiat_shamir("init"); transcript.apply_fiat_shamir("eta"); transcript.apply_fiat_shamir("beta"); @@ -69,38 +75,48 @@ template bool VerifierBase::verify transcript.apply_fiat_shamir("z"); const auto alpha = fr::serialize_from_buffer(transcript.get_challenge("alpha").begin()); - const auto zeta = fr::serialize_from_buffer(transcript.get_challenge("z").begin()); + const auto zeta = fr::serialize_from_buffer( + transcript.get_challenge("z") + .begin()); // `zeta` is the name being given to the "Fraktur" (gothic) z from the plonk paper, so as not to + // confuse us with the z permutation polynomial and Z_H vanishing polynomial. + // You could use a unicode "latin small letter ezh with curl" (ʓ) to get close, if you wanted. - // Compute the evaluations of the lagrange polynomials L_1(X) and L_{n - k}(X) at X = zeta. + // Compute the evaluations of the lagrange polynomials L_1(X) and L_{n - k}(X) at X = ʓ. + // Also computes the evaluation of the vanishing polynomial Z_H*(X) at X = ʓ. // Here k = num_roots_cut_out_of_the_vanishing_polynomial and n is the size of the evaluation domain. + /// TODO: can we add these lagrange evaluations to the transcript? They get recalcualted after this multiple times, + // by each widget. const auto lagrange_evals = barretenberg::polynomial_arithmetic::get_lagrange_evaluations(zeta, key->domain); // Step 8: Compute quotient polynomial evaluation at zeta - // r_eval − ((a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ)(c_eval + γ) z_eval_omega)α − - // L_1(zeta).α^{3} + (z_eval_omega - ∆_{PI}).L_{n-k}(zeta)α^{2} - // t_eval = - // -------------------------------------------------------------------------------------------------------------------------------------------------------------- - // Z_H*(zeta) + // r_eval − (a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ)(c_eval + γ).z_eval_omega.α − + // L_1(ʓ).α^3 + (z_eval_omega - ∆_{PI}).L_{n-k}(ʓ).α^2 + // t_eval = ----------------------------------------------------------------------------------------------- + // Z_H*(ʓ) + // // where Z_H*(X) is the modified vanishing polynomial. + + // Compute ʓ^n. + key->z_pow_n = zeta.pow(key->domain.size); + // The `compute_quotient_evaluation_contribution` function computes the numerator of t_eval and also r_0 (for // the simplified version) according to the program settings for standard/turbo/ultra PLONK. - // - key->z_pow_n = zeta; - for (size_t i = 0; i < key->domain.log2_size; ++i) { - key->z_pow_n *= key->z_pow_n; - } - // when use_linearisation is true, r_0 is the contant term of r(X). When use_linearisation is false, r_0 - // is the evaluation of the numerator of quotient polynomial t(X) + // When use_linearisation = true, r_0 is the constant term of r(X). + // When use_linearisation = false, r_0 is the evaluation of the numerator of quotient polynomial t(X). fr r_0(0); program_settings::compute_quotient_evaluation_contribution(key.get(), alpha, transcript, r_0); - // We want to include t_eval to transcript only when use_linearisation is false + + // We want to include t_eval in the transcript only when use_linearisation = false. if (!program_settings::use_linearisation) { fr t_eval = r_0 * lagrange_evals.vanishing_poly.invert(); transcript.add_element("t", t_eval.to_buffer()); } + transcript.apply_fiat_shamir("nu"); transcript.apply_fiat_shamir("separator"); - const auto separator_challenge = fr::serialize_from_buffer(transcript.get_challenge("separator").begin()); + + const auto separator_challenge = + fr::serialize_from_buffer(transcript.get_challenge("separator").begin()); // a.k.a. `u` in the plonk paper // In the following function, we do the following computation. // Step 10: Compute batch opening commitment [F]_1 @@ -121,7 +137,7 @@ template bool VerifierBase::verify // commitment_scheme->batch_verify(transcript, kate_g1_elements, kate_fr_elements, key, r_0); - // Step 9: Compute partial opening batch commitment [D]_1: + // Step 9: Compute the partial opening batch commitment [D]_1: // [D]_1 = (a_eval.b_eval.[qM]_1 + a_eval.[qL]_1 + b_eval.[qR]_1 + c_eval.[qO]_1 + [qC]_1) * nu_{linear} * α // >> selector polynomials // + [(a_eval + β.z + γ)(b_eval + β.k_1.z + γ)(c_eval + β.k_2.z + γ).α + @@ -138,7 +154,7 @@ template bool VerifierBase::verify g1::affine_element PI_Z = g1::affine_element::serialize_from_buffer(&transcript.get_element("PI_Z")[0]); g1::affine_element PI_Z_OMEGA = g1::affine_element::serialize_from_buffer(&transcript.get_element("PI_Z_OMEGA")[0]); - // validate PI_Z, PI_Z_OMEGA are valid ecc points. + // Validate PI_Z, PI_Z_OMEGA are valid ecc points. // N.B. we check that witness commitments are valid points in KateCommitmentScheme::batch_verify if (!PI_Z.on_curve() || PI_Z.is_point_at_infinity()) { throw_or_abort("opening proof group element PI_Z not a valid point"); @@ -158,6 +174,7 @@ template bool VerifierBase::verify std::vector elements; for (const auto& [key, value] : kate_g1_elements) { + // TODO: perhaps we should throw if not on curve or if infinity? if (value.on_curve() && !value.is_point_at_infinity()) { scalars.emplace_back(kate_fr_elements.at(key)); elements.emplace_back(value); @@ -208,6 +225,7 @@ template bool VerifierBase::verify key->recursive_proof_public_input_indices[13], key->recursive_proof_public_input_indices[14], key->recursive_proof_public_input_indices[15]); + P[0] += g1::element(x0, y0, 1) * recursion_separator_challenge; P[1] += g1::element(x1, y1, 1) * recursion_separator_challenge; } @@ -228,10 +246,12 @@ template bool VerifierBase::verify template class VerifierBase; template class VerifierBase; -template class VerifierBase; +template class VerifierBase; +template class VerifierBase; + template class VerifierBase; template class VerifierBase; -template class VerifierBase; +template class VerifierBase; template class VerifierBase; } // namespace waffle \ No newline at end of file diff --git a/cpp/src/aztec/plonk/proof_system/verifier/verifier.hpp b/cpp/src/aztec/plonk/proof_system/verifier/verifier.hpp index 9a99d45aaf..936718b102 100644 --- a/cpp/src/aztec/plonk/proof_system/verifier/verifier.hpp +++ b/cpp/src/aztec/plonk/proof_system/verifier/verifier.hpp @@ -32,18 +32,22 @@ template class VerifierBase { extern template class VerifierBase; extern template class VerifierBase; +extern template class VerifierBase; +extern template class VerifierBase; + extern template class VerifierBase; -extern template class VerifierBase; extern template class VerifierBase; -extern template class VerifierBase; +extern template class VerifierBase; extern template class VerifierBase; typedef VerifierBase UnrolledVerifier; typedef VerifierBase UnrolledTurboVerifier; +typedef VerifierBase UnrolledUltraVerifier; +typedef VerifierBase UnrolledUltraToStandardVerifier; + typedef VerifierBase Verifier; typedef VerifierBase TurboVerifier; -typedef VerifierBase PlookupVerifier; -typedef VerifierBase UnrolledPlookupVerifier; +typedef VerifierBase UltraVerifier; typedef VerifierBase GenPermVerifier; } // namespace waffle \ No newline at end of file diff --git a/cpp/src/aztec/plonk/proof_system/verifier/verifier.test.cpp b/cpp/src/aztec/plonk/proof_system/verifier/verifier.test.cpp index c4b47a4f34..9261ed9f7d 100644 --- a/cpp/src/aztec/plonk/proof_system/verifier/verifier.test.cpp +++ b/cpp/src/aztec/plonk/proof_system/verifier/verifier.test.cpp @@ -7,7 +7,7 @@ #include "verifier.hpp" #include #include -#include +#include #include #include #include diff --git a/cpp/src/aztec/plonk/proof_system/widgets/random_widgets/permutation_widget_impl.hpp b/cpp/src/aztec/plonk/proof_system/widgets/random_widgets/permutation_widget_impl.hpp index c0b8ec2cfc..376d0da5d1 100644 --- a/cpp/src/aztec/plonk/proof_system/widgets/random_widgets/permutation_widget_impl.hpp +++ b/cpp/src/aztec/plonk/proof_system/widgets/random_widgets/permutation_widget_impl.hpp @@ -49,6 +49,12 @@ ProverPermutationWidget void ProverPermutationWidget:: compute_round_commitments(transcript::StandardTranscript& transcript, const size_t round_number, work_queue& queue) @@ -81,8 +87,8 @@ void ProverPermutationWidgetpolynomial_cache.get("sigma_" + std::to_string(i + 1) + "_lagrange").get_coefficients(); - // if idpolys = true, it implies that we do NOT use the identity permutation - // S_ID1(X) = X, S_ID2(X) = k_1X, ... + // If idpolys = true, it implies that we do NOT use the identity permutation + // S_ID1(X) = X, S_ID2(X) = k_1X, S_ID3(X) = k_2X. if constexpr (idpolys) lagrange_base_ids[i] = key->polynomial_cache.get("id_" + std::to_string(i + 1) + "_lagrange").get_coefficients(); @@ -92,92 +98,109 @@ void ProverPermutationWidget (w_1 + \gamma + \beta.w^{0}), (w_2 + \gamma + \beta.w^{1}), ...., (w_n + \gamma - // + \beta.w^{n-1}) 1 -> (w_{n+1} + \gamma + \beta.k_1.w^{0}), (w_{n+1} + \gamma + \beta.k_1.w^{2}), ...., - // (w_{n+1} + \gamma + \beta.k_1.w^{n-1}) 2 -> (w_{2n+1} + \gamma + \beta.k_2.w^{0}), (w_{2n+1} + \gamma + - // \beta.k_2.w^{0}), ...., (w_{2n+1} + \gamma + \beta.k_2.w^{n-1}) + // accumulator data structure: + // numerators are stored in accumulator[0: program_width-1], + // denominators are stored in accumulator[program_width:] + // + // 0 1 (n-1) + // 0 -> (w_1 + γ + β.ω^{0} ), (w_2 + γ + β.ω^{1} ), ...., (w_n + γ + β.ω^{n-1} ) + // 1 -> (w_{n+1} + γ + β.k_1.ω^{0}), (w_{n+1} + γ + β.k_1.ω^{2}), ...., (w_{n+1} + γ + β.k_1.ω^{n-1}) + // 2 -> (w_{2n+1} + γ + β.k_2.ω^{0}), (w_{2n+1} + γ + β.k_2.ω^{0}), ...., (w_{2n+1} + γ + β.k_2.ω^{n-1}) // - // 3 -> (w_1 + \gamma + \beta.\sigma(1)), (w_2 + \gamma + \beta.\sigma(2)), ...., (w_n + \gamma - // + \beta.\sigma(n)) 4 -> (w_{n+1} + \gamma + \beta.\sigma(n+1)), (w_{n+1} + \gamma + \beta.\sigma{n+2}), - // ...., (w_{n+1} + \gamma + \beta.\sigma{n+n}) 5 -> (w_{2n+1} + \gamma + \beta.\sigma(2n+1)), (w_{2n+1} + - // \gamma + \beta.\sigma(2n+2)), ...., (w_{2n+1} + \gamma + \beta.\sigma(2n+n)) + // 3 -> (w_1 + γ + β.σ(1) ), (w_2 + γ + β.σ(2) ), ...., (w_n + γ + β.σ(n) ) + // 4 -> (w_{n+1} + γ + β.σ(n+1) ), (w_{n+1} + γ + β.σ{n+2} ), ...., (w_{n+1} + γ + β.σ{n+n} ) + // 5 -> (w_{2n+1} + γ + β.σ(2n+1) ), (w_{2n+1} + γ + β.σ(2n+2) ), ...., (w_{2n+1} + γ + β.σ(2n+n) ) + // + // Thus, to obtain coefficient_of_L2, we need to use accumulators[:][0]: + // acc[0][0]*acc[1][0]*acc[2][0] / acc[program_width][0]*acc[program_width+1][0]*acc[program_width+2][0] // - // Thus, to obtain coefficient_of_L2, we need to use accumulators[:][0]. // To obtain coefficient_of_L3, we need to use accumulator[:][0] and accumulator[:][1] // and so on upto coefficient_of_Ln. #ifndef NO_MULTITHREADING #pragma omp for #endif + // Recall: In a domain: num_threads * thread_size = size (= subgroup_size) + // | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <-- n = 16 + // j: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | num_threads = 8 + // i: 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 thread_size = 2 + // So i will access a different element from 0..(n-1) each time. + // Commented maths notation mirrors the indexing from the giant comment immediately above. for (size_t j = 0; j < key->small_domain.num_threads; ++j) { - barretenberg::fr thread_root = - key->small_domain.root.pow(static_cast(j * key->small_domain.thread_size)); - [[maybe_unused]] barretenberg::fr cur_root_times_beta = thread_root * beta; + barretenberg::fr thread_root = key->small_domain.root.pow( + static_cast(j * key->small_domain.thread_size)); // effectively ω^{i} in inner loop + [[maybe_unused]] barretenberg::fr cur_root_times_beta = thread_root * beta; // β.ω^{i} barretenberg::fr T0; barretenberg::fr wire_plus_gamma; size_t start = j * key->small_domain.thread_size; size_t end = (j + 1) * key->small_domain.thread_size; for (size_t i = start; i < end; ++i) { - wire_plus_gamma = gamma + lagrange_base_wires[0][i]; + wire_plus_gamma = gamma + lagrange_base_wires[0][i]; // w_{i + 1} + γ + // i in 0..(n-1) if constexpr (!idpolys) { - accumulators[0][i] = wire_plus_gamma + cur_root_times_beta; + accumulators[0][i] = wire_plus_gamma + cur_root_times_beta; // w_{i + 1} + γ + β.ω^{i} } if constexpr (idpolys) { - T0 = lagrange_base_ids[0][i] * beta; - accumulators[0][i] = T0 + wire_plus_gamma; + T0 = lagrange_base_ids[0][i] * beta; // β.id(i + 1) + accumulators[0][i] = T0 + wire_plus_gamma; // w_{i + 1} + γ + β.id(i + 1) } - T0 = lagrange_base_sigmas[0][i] * beta; - accumulators[program_width][i] = T0 + wire_plus_gamma; + T0 = lagrange_base_sigmas[0][i] * beta; // β.σ(i + 1) + accumulators[program_width][i] = T0 + wire_plus_gamma; // w_{i + 1} + γ + β.σ(i + 1) for (size_t k = 1; k < program_width; ++k) { - wire_plus_gamma = gamma + lagrange_base_wires[k][i]; + wire_plus_gamma = gamma + lagrange_base_wires[k][i]; // w_{k.n + i + 1} + γ + // i in 0..(n-1) if constexpr (idpolys) { - T0 = lagrange_base_ids[k][i] * beta; + T0 = lagrange_base_ids[k][i] * beta; // β.id(k.n + i + 1) } else { - T0 = fr::coset_generator(k - 1) * cur_root_times_beta; + T0 = fr::coset_generator(k - 1) * cur_root_times_beta; // β.k_{k}.ω^{i} + // ^coset generator k } - accumulators[k][i] = T0 + wire_plus_gamma; + accumulators[k][i] = T0 + wire_plus_gamma; // w_{k.n + i + 1} + γ + β.id(k.n + i + 1) - T0 = lagrange_base_sigmas[k][i] * beta; - accumulators[k + program_width][i] = T0 + wire_plus_gamma; + T0 = lagrange_base_sigmas[k][i] * beta; // β.σ(k.n + i + 1) + accumulators[k + program_width][i] = T0 + wire_plus_gamma; // w_{k.n + i + 1} + γ + β.σ(k.n + i + 1) } if constexpr (!idpolys) - cur_root_times_beta *= key->small_domain.root; + cur_root_times_beta *= key->small_domain.root; // β.ω^{i + 1} } } - // step 2: compute the constituent components of Z(X). This is a small multithreading bottleneck, as we have + // Step 2: compute the constituent components of z(X). This is a small multithreading bottleneck, as we have // program_width * 2 non-parallelizable processes // - // update the accumulator matrix a[:][:] to: - // 0 1 2 3 - // 0 -> (a[0][0]), (a[0][1] * a[0][0]), (a[0][2] * a[0][1]), ..., (a[0][n-1] * a[0][n-2]) - // 1 -> (a[1][0]), (a[1][1] * a[1][0]), (a[1][2] * a[1][1]), ..., (a[1][n-1] * a[1][n-2]) + // Update the accumulator matrix a[:][:] to contain the left products like so: + // 0 1 2 (n-1) + // 0 -> (a[0][0]), (a[0][1] * a[0][0]), (a[0][2] * a[0][1]), ..., (a[0][n-1] * a[0][n-2]) + // 1 -> (a[1][0]), (a[1][1] * a[1][0]), (a[1][2] * a[1][1]), ..., (a[1][n-1] * a[1][n-2]) + // 2 -> (a[2][0]), (a[2][1] * a[2][0]), (a[2][2] * a[2][1]), ..., (a[2][n-1] * a[2][n-2]) + // + // 3 -> (a[3][0]), (a[3][1] * a[3][0]), (a[3][2] * a[3][1]), ..., (a[3][n-1] * a[3][n-2]) + // 4 -> (a[4][0]), (a[4][1] * a[4][0]), (a[4][2] * a[4][1]), ..., (a[4][n-1] * a[4][n-2]) + // 5 -> (a[5][0]), (a[5][1] * a[5][0]), (a[5][2] * a[5][1]), ..., (a[5][n-1] * a[5][n-2]) // // and so on... #ifndef NO_MULTITHREADING @@ -190,15 +213,21 @@ void ProverPermutationWidget (a[0][0] * a[1][0] * a[2][0]), (a[0][1] * a[1][1] * a[2][1]), ...., (a[0][n-1] * - // a[1][n-1] * a[2][n-1]) pw -> (a[pw][0] * a[pw+1][0] * a[pw+2][0]), (a[pw][1] * a[pw+1][1] * a[pw+2][1]), - // ...., (a[pw][n-1] * a[pw+1][n-1] * a[pw+2][n-1]) + // a[1][n-1] * a[2][n-1]) + // + // pw -> (a[pw][0] * a[pw+1][0] * a[pw+2][0]), (a[pw][1] * a[pw+1][1] * a[pw+2][1]), ...., (a[pw][n-1] * + // a[pw+1][n-1] * a[pw+2][n-1]) + // + // Note that pw = program_width // - // note that pw = program_width // Hereafter, we can compute // coefficient_Lj = a[0][j]/a[pw][j] // @@ -258,8 +287,8 @@ void ProverPermutationWidgetquotient_polynomial_parts[1][key->n] = 0; key->quotient_polynomial_parts[2][key->n] = 0; - // Our permutation check boils down to two 'grand product' arguments, - // that we represent with a single polynomial Z(X). - // We want to test that Z(X) has been constructed correctly. - // When evaluated at elements of w \in H, the numerator of Z(w) will equal the - // identity permutation grand product, and the denominator will equal the copy permutation grand product. + // Our permutation check boils down to two 'grand product' arguments, that we represent with a single polynomial + // z(X). We want to test that z(X) has been constructed correctly. When evaluated at elements of ω ∈ H, the + // numerator of z(ω) will equal the identity permutation grand product, and the denominator will equal the copy + // permutation grand product. - // The identity that we need to evaluate is: Z(X.w).(permutation grand product) = Z(X).(identity grand product) - // i.e. The next element of Z is equal to the current element of Z, multiplied by (identity grand product) / + // The identity that we need to evaluate is: z(X.ω).(permutation grand product) == z(X).(identity grand product) + // i.e. The next element of z is equal to the current element of z, multiplied by (identity grand product) / // (permutation grand product) - // This method computes `Z(X).(identity grand product).{alpha}`. + // This method computes `(identity grand product).z(X).α`. // The random `alpha` is there to ensure our grand product polynomial identity is linearly independent from the // other polynomial identities that we are going to roll into the quotient polynomial T(X). // Specifically, we want to compute: - // (w_l(X) + \beta.sigma1(X) + \gamma).(w_r(X) + \beta.sigma2(X) + \gamma).(w_o(X) + \beta.sigma3(X) + - // \gamma).Z(X).alpha Once we divide by the vanishing polynomial, this will be a degree 3n polynomial. + // (w_l(X) + β.σ_1(X) + γ).(w_r(X) + β.σ_2(X) + γ).(w_o(X) + β.σ_3(X) + γ).z(X).α + // Once we divide by the vanishing polynomial, this will be a degree 3n polynomial. (4 * (n-1) - (n-4)). std::array wire_ffts; std::array sigma_ffts; @@ -369,7 +400,7 @@ barretenberg::fr ProverPermutationWidgetpolynomial_cache.get("lagrange_1_fft"); - // compute our public input component + // Compute our public input component std::vector public_inputs = many_from_buffer(transcript.get_element("public_inputs")); barretenberg::fr public_input_delta = @@ -377,8 +408,6 @@ barretenberg::fr ProverPermutationWidgetlarge_domain.size - 1; // Step 4: Set the quotient polynomial to be equal to - // (w_l(X) + \beta.sigma1(X) + \gamma).(w_r(X) + \beta.sigma2(X) + \gamma).(w_o(X) + \beta.sigma3(X) + - // \gamma).Z(X).alpha #ifndef NO_MULTITHREADING #pragma omp parallel for #endif @@ -386,10 +415,10 @@ barretenberg::fr ProverPermutationWidgetlarge_domain.thread_size; const size_t end = (j + 1) * key->large_domain.thread_size; - // leverage multi-threading by computing quotient polynomial at points - // (w^{j * num_threads}, w^{j * num_threads + 1}, ..., w^{j * num_threads + num_threads}) + // Leverage multi-threading by computing quotient polynomial at points + // (ω^{j * num_threads}, ω^{j * num_threads + 1}, ..., ω^{j * num_threads + num_threads}) // - // curr_root = w^{j * num_threads} * g_{small} * beta + // curr_root = ω^{j * num_threads} * g_{small} * β // curr_root will be used in denominator barretenberg::fr cur_root_times_beta = key->large_domain.root.pow(static_cast(j * key->large_domain.thread_size)); @@ -406,20 +435,20 @@ barretenberg::fr ProverPermutationWidget add linearly independent term (Z(X.w) - 1).(\alpha^3).L{n-1}(X) into the quotient polynomial to check + // To summarise the summary: If z(ω_n) = 1, then (z(X.ω) - 1).L_{n-1}(X) will be divisible by Z_H*(X) + // => add linearly independent term (z(X.ω) - 1).(α^3).L{n-1}(X) into the quotient polynomial to check // this // z_perm_fft already contains evaluations of Z(X).(\alpha^2) @@ -475,11 +504,11 @@ barretenberg::fr ProverPermutationWidget L_{end}= L_1(X . w^{num_roots_cut_out_of_vanishing_polynomial + 1}) + // Note that L_j(X) = L_1(X . ω^{-j}) = L_1(X . ω^{n-j}) + // => L_{end}= L_1(X . ω^{num_roots_cut_out_of_vanishing_polynomial + 1}) // => fetch the value at index (i + (num_roots_cut_out_of_vanishing_polynomial + 1) * 4) in l_1 // the factor of 4 is because l_1 is a 4n-size fft. // @@ -487,9 +516,9 @@ barretenberg::fr ProverPermutationWidget Field VerifierPermutationWidget:: compute_quotient_evaluation_contribution(typename Transcript::Key* key, @@ -634,13 +679,9 @@ Field VerifierPermutationWidget wire_evaluations; std::vector sigma_evaluations; + // E.g. in standard plonk, S_σ_3(X) is not evaluated when computing the linearisation polynomial: const size_t num_sigma_evaluations = (use_linearisation ? key->program_width - 1 : key->program_width); for (size_t i = 0; i < num_sigma_evaluations; ++i) { std::string index = std::to_string(i + 1); - sigma_evaluations.emplace_back(transcript.get_field_element("sigma_" + index)); + sigma_evaluations.emplace_back(transcript.get_field_element("sigma_" + index)); // S_σ_i(ʓ) } for (size_t i = 0; i < key->program_width; ++i) { - wire_evaluations.emplace_back(transcript.get_field_element("w_" + std::to_string(i + 1))); + wire_evaluations.emplace_back(transcript.get_field_element( + "w_" + std::to_string( + i + 1))); // w_i(ʓ) + // (Note: in the Plonk paper, these polys are called a, b, c. We interchangeably call + // them a,b,c or w_l, w_r, w_o, or w_1, w_2, w_3,... depending on the context). } - // Compute evaluations of lagrange polynomials L_1(X) and L_{n-k} at z (= zeta) - // Recall, k is the number of roots cut out of the vanishing polynomial Z_H(X) + // Compute evaluations of lagrange polynomials L_1(X) and L_{n-k} at ʓ. + // Recall, k is the number of roots cut out of the vanishing polynomial Z_H(X), to yield Z_H*(X). // Note that // X^n - 1 - // L_i(X) = L_1(X.w^{-i + 1}) = ----------------- - // X.w^{-i + 1} - 1 + // L_i(X) = L_1(X.ω^{-i + 1}) = ----------------- + // X.ω^{-i + 1} - 1 // - Field numerator = key->z_pow_n - Field(1); + Field numerator = key->z_pow_n - Field(1); // ʓ^n - 1 numerator *= key->domain.domain_inverse; - Field l_start = numerator / (z - Field(1)); + Field l_start = numerator / (z - Field(1)); // [ʓ^n - 1] / [n.(ʓ - 1)] =: L_1(ʓ) - // compute w^{num_roots_cut_out_of_vanishing_polynomial + 1} + // Compute ω^{num_roots_cut_out_of_vanishing_polynomial + 1} Field l_end_root = (num_roots_cut_out_of_vanishing_polynomial & 1) ? key->domain.root.sqr() : key->domain.root; for (size_t i = 0; i < num_roots_cut_out_of_vanishing_polynomial / 2; ++i) { l_end_root *= key->domain.root.sqr(); } - Field l_end = numerator / ((z * l_end_root) - Field(1)); + Field l_end = numerator / ((z * l_end_root) - Field(1)); // [ʓ^n - 1] / [n.(ʓ.ω^{k+1} - 1)] =: L_{n-k}(ʓ) Field z_1_shifted_eval = transcript.get_field_element("z_perm_omega"); - // reconstruct evaluation of quotient polynomial from prover messages + // Reconstruct the evaluation of the quotient polynomial, t(ʓ), from prover messages. + // Recall: + // + // t(X) = 1/Z_H*(X) * ( + // [ a(X).b(X).qm(X) + a(X).ql(X) + b(X).qr(X) + c(X).qo(X) + qc(X) ] + // + α.[ + // [ a(X) + β.X + γ)(b(X) + β.k_1.X + γ)(c(X) + β.k_2.X + γ).z(X) ] + // - [ a(X) + β.Sσ1(X) + γ)(b(X) + β.Sσ2(X) + γ)(c(X) + β.Sσ3(X) + γ).z(X.ω) ] + // ] + // + α^3.[ (z(X) - 1).L_1(X) ] + // + α^2.[ (z(X.ω) - ∆_PI).L_{n-k}(X) ] + // ) + // Field T1; Field T2; - Field alpha_pow[4]; - alpha_pow[0] = alpha; - for (size_t i = 1; i < 4; ++i) { - alpha_pow[i] = alpha_pow[i - 1] * alpha_pow[0]; - } - // Part 1: compute sigma contribution, i.e. - // ((a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ)(c_eval + γ) z_eval_omega)α + // Part 1: compute the sigma contribution, i.e. + // + // sigma_contribution = (a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ)(c_eval + γ).z(ʓ.ω).α + // Field sigma_contribution = Field(1); Field T0; for (size_t i = 0; i < key->program_width - 1; ++i) { @@ -705,52 +760,67 @@ Field VerifierPermutationWidgetprogram_width - 1] + gamma; sigma_contribution *= T0; sigma_contribution *= z_1_shifted_eval; - sigma_contribution *= alpha_pow[0]; + sigma_contribution *= alpha; // Part 2: compute the public-inputs term, i.e. - // (z_eval_omega - ∆_{PI}).L_{n-k}(z).α^{2} + // + // (z(ʓ.ω) - ∆_{PI}).L_{n-k}(ʓ).α^2 + // + // (See the separate paper which alters the 'public inputs' component of the plonk protocol) std::vector public_inputs = transcript.get_field_element_vector("public_inputs"); Field public_input_delta = compute_public_input_delta(public_inputs, beta, gamma, key->domain.root); T1 = z_1_shifted_eval - public_input_delta; T1 *= l_end; - T1 *= alpha_pow[1]; + T1 *= alpha_squared; // Part 3: compute starting lagrange polynomial term, i.e. - // L_1(z).α^{3} - T2 = l_start * alpha_pow[2]; + // + // L_1(ʓ).α^3 + // + T2 = l_start * alpha_cubed; // Combine parts 1, 2, 3. If linearisation is used, we need to add r_eval to T1 and we're done. + // + // r_0 = α^2.(z(ʓ.ω) - ∆_{PI}).L_{n-k}(ʓ) + // - α^3.L_1(ʓ) + // - α.(a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ)(c_eval + γ).z(ʓ.ω) + // T1 -= T2; T1 -= sigma_contribution; r_0 += T1; - // If we use linearisation, then T1 computed at this point is equal to the constant contribution of r(X) computed in - // this widget + + // If we use linearisation, then r_0 computed at this point is equal to the constant contribution of r(X). if (use_linearisation) { - return alpha.sqr().sqr(); + return alpha_squared.sqr(); } - // If linearisation is not used, the verifier needs to compute the evaluation of the linearisation polynomial r(X). - // It has two terms, one due to the permutation argument (aka copy constraints) and the other due to the gate - // constraints. + // If linearisation is not used, the verifier needs to compute the evaluation of the linearisation polynomial r(X) + // at ʓ (i.e. r(ʓ)). // - // r(X) = (a_eval.b_eval.q_M(X) + a_eval.q_L(X) + b_eval.q_R(X) + c_eval.q_O(X) + q_C(X)) + |-------> - // gate constraints - // ((a_eval + β.z + γ)(b_eval + β.k_1.z + γ)(c_eval + β.k_2.z + γ) z(X))α - | - // ((a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ) β.z_eval_omega.S_{sigma3}(X))α + )------> - // copy constraints z(X).L_1(z).α^{3} | + // r(X) has two terms: + // - one due to the permutation argument (aka copy constraints); + // - and the other due to the gate constraints. + // (See the separate paper which alters the 'public inputs' component of the plonk protocol) + // + // r(X) = (a_eval.b_eval.q_M(X) + a_eval.q_L(X) + b_eval.q_R(X) + c_eval.q_O(X) + q_C(X)) |-> gate + // constraints + // + α.(a_eval + β.ʓ + γ)(b_eval + β.k_1.ʓ + γ)(c_eval + β.k_2.ʓ + γ).z(X) | + // - α.(a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ).β.z(ʓ.ω).S_{sigma3}(X) |-> copy + // + α^3.L_1(ʓ).z(X) | constraints // - // Note here, we are only trying to compute the `copy constraints` part and the `gate constraints` part need to be - // done using Arithmetic widget. + // Note here, we are only trying to compute the `copy constraints` part. The `gate constraints` part is calculated + // in the Arithmetic widget. // else { - // Part 4: compute multiplicand of last sigma polynomial, i.e. - // -(a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ) β.z_eval_omega.α + // Part 4: compute multiplicand of last sigma polynomial S_{sigma3}(X), i.e. + // + // - α.(a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ).β.z(ʓ.ω) + // Field sigma_contribution = Field(1); for (size_t i = 0; i < key->program_width - 1; ++i) { - Field permutation_evaluation = transcript.get_field_element("sigma_" + std::to_string(i + 1)); - T0 = permutation_evaluation * beta; + T0 = sigma_evaluations[i] * beta; T0 += wire_evaluations[i]; T0 += gamma; sigma_contribution *= T0; @@ -759,17 +829,36 @@ Field VerifierPermutationWidget r_0 from + // - α.(a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ)(c_eval + γ).z(ʓ.ω) | before + // + // - α.(a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ).β.z(ʓ.ω).S_{sigma3}(ʓ) + // ^^^^^^^^^^^^^ + // Evaluated at X=ʓ + // = α^2.(z(ʓ.ω) - ∆_{PI}).L_{n-k}(ʓ) + // - α^3.L_1(ʓ) + // - α.(a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ)(c_eval + β.S_{sigma3}(ʓ) + γ).z(ʓ.ω) + // r_0 += (sigma_last_multiplicand * sigma_evaluations[key->program_width - 1]); Field z_eval = transcript.get_field_element("z_perm"); if (idpolys) { - // Part 5.1: If idpolys is true, it indicates that we are not using the identity polynomials to + // Part 5.1: If idpolys = true, it indicates that we are not using the identity polynomials to // represent identity permutations. In that case, we need to use the pre-defined values for - // representing identity permutations and then compute the term - // [(a_eval + β.id_1 + γ)(b_eval + β.id_2 + γ)(c_eval + β.id_3 + γ).α + L_1(z).α^{3}] + // representing identity permutations and then compute the coefficient of the z(X) component of r(X): + // + // [ + // α.(a_eval + β.id_1 + γ)(b_eval + β.id_2 + γ)(c_eval + β.id_3 + γ) + // + α^3.L_1(ʓ) + // ].z(X) + // Field id_contribution = Field(1); for (size_t i = 0; i < key->program_width; ++i) { Field id_evaluation = transcript.get_field_element("id_" + std::to_string(i + 1)); @@ -778,17 +867,33 @@ Field VerifierPermutationWidgetprogram_width; ++i) { Field coset_generator = (i == 0) ? Field(1) : Field::coset_generator(i - 1); @@ -804,7 +909,7 @@ Field VerifierPermutationWidget& ProverPlookupWid ProverRandomWidget::operator=(other); return *this; } - +/** + * @brief Construct polynomial s and add blinding. Save s in both lagrange and monomial form. + * + * @tparam num_roots_cut_out_of_vanishing_polynomial + * @param transcript + * + * @details Polynomial 's' is the sorted concatenation of witness values and lookup table values. + * It is constructed as s = s_1 + η*s_2 + η²*s_3 + η³*s_4. Blinding is added by setting the last 3 + * elements in the lagrange representation to random values. + */ template -void ProverPlookupWidget::compute_sorted_list_commitment( +void ProverPlookupWidget::compute_sorted_list_polynomial( transcript::StandardTranscript& transcript) { barretenberg::polynomial s_1 = key->polynomial_cache.get("s_1_lagrange"); @@ -52,8 +61,11 @@ void ProverPlookupWidget::compute_sor barretenberg::polynomial s_accum(key->n, key->n); barretenberg::polynomial_arithmetic::copy_polynomial(&s_1[0], &s_accum[0], key->n, key->n); + // Get challenge η const auto eta = fr::serialize_from_buffer(transcript.get_challenge("eta", 0).begin()); + // Construct s = s_1 + η*s_2 + η²*s_3 + η³*s_4 via Horner, i.e. s = s_1 + η(s_2 + η(s_3 + η*s_4)) + // Note: we store 's' in the memory allocated for s_1 ITERATE_OVER_DOMAIN_START(key->small_domain); fr T0 = s_4[i]; T0 *= eta; @@ -74,22 +86,42 @@ void ProverPlookupWidget::compute_sor // Thus, writing `s` into the usual coefficient form, we will have // s(X) = s1.L_1(X) + s2.L_2(X) + ... + s{n-k}.L_{n-k}(X) // Now, the coefficients of lagrange bases (L_{n-k+1}, ..., L_{n}) are empty. We can use them to add randomness - // into `s`. Since we wish to add 3 random scalars, we need k >= 3. In our case, we have set k = 4. - // Thus, we can add 3 random scalars as (s{n-k+1}, s{n-k+2}, s{n-k+3}). + // into `s`. Since we wish to add 3 random scalars, we need k >= 3. In our case, we have set + // num_roots_cut_out_of_vanishing_polynomial = 4. Thus, we can add 3 random scalars as (s{n-k}, s{n-k+1}, + // s{n-k+2}). const size_t s_randomness = 3; ASSERT(s_randomness < num_roots_cut_out_of_vanishing_polynomial); for (size_t k = 0; k < s_randomness; ++k) { s_accum[((key->n - num_roots_cut_out_of_vanishing_polynomial) + 1 + k)] = fr::random_element(); } + // Save the lagrange base representation of s polynomial s_lagrange(s_accum, key->small_domain.size); key->polynomial_cache.put("s_lagrange", std::move(s_lagrange)); + + // Compute the monomial coefficient representation of s s_accum.ifft(key->small_domain); key->polynomial_cache.put("s", std::move(s_accum)); } - +/** + * @brief Compute the blinded lookup grand product polynomial Z_lookup(X) + * + * @tparam num_roots_cut_out_of_vanishing_polynomial + * @param transcript + * + * @brief The lookup grand product polynomial Z_lookup is of the form + * + * ∏(1 + β) ⋅ ∏(q_lookup*f_k + γ) ⋅ ∏(t_k + βt_{k+1} + γ(1 + β)) + * Z_lookup(g^j) = ----------------------------------------------------------------- + * ∏(s_k + βs_{k+1} + γ(1 + β)) + * + * where ∏ := ∏_{k -void ProverPlookupWidget::compute_grand_product_commitment( +void ProverPlookupWidget::compute_grand_product_polynomial( transcript::StandardTranscript& transcript) { const size_t n = key->n; @@ -104,6 +136,7 @@ void ProverPlookupWidget::compute_gra // beyond this calculation we need only the monomial and coset FFT forms of z_lookup, // so z_lookup in lagrange base will not be added to the store. fr* accumulators[4]; + // Note: accumulators[0][i] = z[i + 1] accumulators[0] = &z_lookup[1]; for (size_t k = 1; k < 4; ++k) { accumulators[k] = static_cast(aligned_alloc(64, sizeof(fr) * n)); @@ -121,8 +154,6 @@ void ProverPlookupWidget::compute_gra fr beta = fr::serialize_from_buffer(transcript.get_challenge("beta").begin()); fr gamma = fr::serialize_from_buffer(transcript.get_challenge("beta", 1).begin()); - // gamma = fr(1); - // beta = fr(1); std::array lagrange_base_wires; std::array lagrange_base_tables{ key->polynomial_cache.get("table_value_1_lagrange").get_coefficients(), @@ -132,14 +163,14 @@ void ProverPlookupWidget::compute_gra }; const fr* lookup_selector = key->polynomial_cache.get("table_type_lagrange").get_coefficients(); - const fr* lookup_index_selector = key->polynomial_cache.get("table_index_lagrange").get_coefficients(); + const fr* lookup_index_selector = key->polynomial_cache.get("q_3_lagrange").get_coefficients(); for (size_t i = 0; i < 3; ++i) { lagrange_base_wires[i] = key->polynomial_cache.get("w_" + std::to_string(i + 1) + "_lagrange").get_coefficients(); } - const fr gamma_beta_constant = gamma * (fr(1) + beta); - const fr beta_constant = beta + fr(1); + const fr beta_constant = beta + fr(1); // (1 + β) + const fr gamma_beta_constant = gamma * beta_constant; // γ(1 + β) #ifndef NO_MULTITHREADING #pragma omp parallel @@ -148,19 +179,41 @@ void ProverPlookupWidget::compute_gra #ifndef NO_MULTITHREADING #pragma omp for #endif + // Step 1: Compute polynomials f, t and s and incorporate them into terms that are ultimately needed + // to construct the grand product polynomial Z_lookup(X): + // Note 1: In what follows, 't' is associated with table values (and is not to be confused with the + // quotient polynomial, also refered to as 't' elsewhere). Polynomial 's' is the sorted concatenation + // of the witnesses and the table values. + // Note 2: Evaluation at Xω is indicated explicitly, e.g. 'p(Xω)'; evaluation at X is simply omitted, e.g. 'p' + // + // 1a. Compute f, then set accumulators[0] = (q_lookup*f + γ), where + // + // f = (w_1 + q_2*w_1(Xω)) + η(w_2 + q_m*w_2(Xω)) + η²(w_3 + q_c*w_3(Xω)) + η³q_index. + // Note that q_2, q_m, and q_c are just the selectors from Standard Plonk that have been repurposed + // in the context of the plookup gate to represent 'shift' values. For example, setting each of the + // q_* in f to 2^8 facilitates operations on 32-bit values via four operations on 8-bit values. See + // Ultra documentation for details. + // + // 1b. Compute t, then set accumulators[1] = (t + βt(Xω) + γ(1 + β)), where t = t_1 + ηt_2 + η²t_3 + η³t_4 + // + // 1c. Set accumulators[2] = (1 + β) + // + // 1d. Compute s, then set accumulators[3] = (s + βs(Xω) + γ(1 + β)), where s = s_1 + ηs_2 + η²s_3 + η³s_4 + // for (size_t j = 0; j < key->small_domain.num_threads; ++j) { fr T0; - fr T1; - fr T2; - fr T3; + size_t start = j * key->small_domain.thread_size; size_t end = (j + 1) * key->small_domain.thread_size; - // fr accumulating_beta = beta_constant.pow(start + 1); + + // Note: block_mask is used for efficient modulus, i.e. i % N := i & (N-1), for N = 2^k const size_t block_mask = key->small_domain.size - 1; + // Initialize 't(X)' to be used in an expression of the form t(X) + β*t(Xω) fr next_table = lagrange_base_tables[0][start] + lagrange_base_tables[1][start] * eta + lagrange_base_tables[2][start] * eta_sqr + lagrange_base_tables[3][start] * eta_cube; for (size_t i = start; i < end; ++i) { + // Compute i'th element of f via Horner (see definition of f above) T0 = lookup_index_selector[i]; T0 *= eta; T0 += lagrange_base_wires[2][(i + 1) & block_mask] * column_3_step_size[i]; @@ -173,9 +226,11 @@ void ProverPlookupWidget::compute_gra T0 += lagrange_base_wires[0][i]; T0 *= lookup_selector[i]; + // Set i'th element of polynomial q_lookup*f + γ accumulators[0][i] = T0; accumulators[0][i] += gamma; + // Compute i'th element of t via Horner T0 = lagrange_base_tables[3][(i + 1) & block_mask]; T0 *= eta; T0 += lagrange_base_tables[2][(i + 1) & block_mask]; @@ -184,12 +239,15 @@ void ProverPlookupWidget::compute_gra T0 *= eta; T0 += lagrange_base_tables[0][(i + 1) & block_mask]; + // Set i'th element of polynomial (t + βt(Xω) + γ(1 + β)) accumulators[1][i] = T0 * beta + next_table; next_table = T0; accumulators[1][i] += gamma_beta_constant; + // Set value of this accumulator to (1 + β) accumulators[2][i] = beta_constant; - // accumulating_beta *= (beta_constant); + + // Set i'th element of polynomial (s + βs(Xω) + γ(1 + β)) accumulators[3][i] = s_lagrange[(i + 1) & block_mask]; accumulators[3][i] *= beta; accumulators[3][i] += s_lagrange[i]; @@ -197,8 +255,15 @@ void ProverPlookupWidget::compute_gra } } -// step 2: compute the constituent components of Z(X). This is a small multithreading bottleneck, as we have -// only 4 parallelizable processes +// Step 2: Compute the constituent product components of Z_lookup(X). +// Let ∏ := Prod_{k::compute_gra } } -// step 3: concatenate together the accumulator elements into Z(X) +// Step 3: Combine the accumulator product elements to construct Z_lookup(X). +// +// ∏ (1 + β) ⋅ ∏ (q_lookup*f_k + γ) ⋅ ∏ (t_k + βt_{k+1} + γ(1 + β)) +// Z_lookup(g^j) = -------------------------------------------------------------------------- +// ∏ (s_k + βs_{k+1} + γ(1 + β)) +// +// Note: Montgomery batch inversion is used to efficiently compute the coefficients of Z_lookup +// rather than peforming n individual inversions. I.e. we first compute the double product P_n: +// +// P_n := ∏_{jsmall_domain.num_threads; ++j) { const size_t start = j * key->small_domain.thread_size; + // Set 'end' so its max value is (n-1) thus max value for 'i' is n-2 (N.B. accumulators[0][n-2] = + // z_lookup[n-1]) const size_t end = (j == key->small_domain.num_threads - 1) ? (j + 1) * key->small_domain.thread_size - 1 : (j + 1) * key->small_domain.thread_size; - // const size_t end = - // ((j + 1) * key->small_domain.thread_size) - ((j == key->small_domain.num_threads - 1) ? 1 : 0); + + // Compute * ∏_{j::compute_gra accumulators[0][i] *= inversion_accumulator; inversion_accumulator *= accumulators[3][i]; } - inversion_accumulator = inversion_accumulator.invert(); + inversion_accumulator = inversion_accumulator.invert(); // invert + // Compute [Z_lookup numerator] * ∏_{j / + // ∏_{k::compute_gra const size_t z_randomness = 3; ASSERT(z_randomness < num_roots_cut_out_of_vanishing_polynomial); for (size_t k = 0; k < z_randomness; ++k) { + // Blinding: z_lookup[((n - num_roots_cut_out_of_vanishing_polynomial) + 1 + k)] = fr::random_element(); } + // Compute and add monomial form of z_lookup to the polynomial store z_lookup.ifft(key->small_domain); - - // Add monomial form of z_lookup to the polynomial store key->polynomial_cache.put("z_lookup", std::move(z_lookup)); } +/** + * @brief Compute commitments and FFTs of 's' (round_number == 2) or 'Z_lookup' (round_number == 3) + * + * @tparam num_roots_cut_out_of_vanishing_polynomial + * @param transcript + * @param round_number + * @param queue + */ template void ProverPlookupWidget::compute_round_commitments( transcript::StandardTranscript& transcript, const size_t round_number, work_queue& queue) { if (round_number == 2) { - compute_sorted_list_commitment(transcript); + compute_sorted_list_polynomial(transcript); const polynomial& s = key->polynomial_cache.get("s"); + // Commit to s: queue.add_to_queue({ - work_queue::WorkType::SCALAR_MULTIPLICATION, - s.get_coefficients(), - "S", - barretenberg::fr(0), - 0, + .work_type = work_queue::WorkType::SCALAR_MULTIPLICATION, + .mul_scalars = s.get_coefficients(), + .tag = "S", + .constant = barretenberg::fr(0), + .index = 0, }); + + // Compute the coset FFT of 's' for use in quotient poly construction queue.add_to_queue({ - work_queue::WorkType::FFT, - nullptr, - "s", - barretenberg::fr(0), - 0, + .work_type = work_queue::WorkType::FFT, + .mul_scalars = nullptr, + .tag = "s", + .constant = barretenberg::fr(0), + .index = 0, }); + return; } if (round_number == 3) { - compute_grand_product_commitment(transcript); + compute_grand_product_polynomial(transcript); const polynomial& z = key->polynomial_cache.get("z_lookup"); + // Commit to z_lookup: queue.add_to_queue({ - work_queue::WorkType::SCALAR_MULTIPLICATION, - z.get_coefficients(), - "Z_LOOKUP", - barretenberg::fr(0), - 0, + .work_type = work_queue::WorkType::SCALAR_MULTIPLICATION, + .mul_scalars = z.get_coefficients(), + .tag = "Z_LOOKUP", + .constant = barretenberg::fr(0), + .index = 0, }); + + // Compute the coset FFT of 'z_lookup' for use in quotient poly construction queue.add_to_queue({ - work_queue::WorkType::FFT, - nullptr, - "z_lookup", - barretenberg::fr(0), - 0, + .work_type = work_queue::WorkType::FFT, + .mul_scalars = nullptr, + .tag = "z_lookup", + .constant = barretenberg::fr(0), + .index = 0, }); + return; } } +/** + * @brief Add contibution of z_lookup grand product terms to the quotient polynomial + * + * @tparam num_roots_cut_out_of_vanishing_polynomial + * @param alpha_base + * @param transcript + * @return barretenberg::fr + * + * @details The terms associated with the z_lookup grand product polynomial that must be added + * to the quotient polynomial are as follows: + * z_lookup(X)*[(q_lookup*f + γ) * (t + βt(Xω) + γ(1 + β)) * (1 + β)] ... + * + (z_lookup - 1)*αL_1(X) ... + * - z_lookup(Xω)*(s + βs(Xω) + γ(1 + β)) ... + * + [z_lookup(Xω) - 1/γ(1 + β)^{n-k}]*α²L_1(Xω^k) + * + * These terms attest to the proper construction of Z_lookup. They are analogous to the terms + * associated with the Standard Plonk grand product polynomial Z that also appear in the quotient + * polynomial. See the comments there for more details. The contribution of these terms is + * incorporated into the quotient polynomial via the coset evaluation form (i.e. the evaluation + * on 4nth roots of unity). + * + */ template barretenberg::fr ProverPlookupWidget::compute_quotient_contribution( const fr& alpha_base, const transcript::StandardTranscript& transcript) @@ -311,24 +435,6 @@ barretenberg::fr ProverPlookupWidget: fr beta = fr::serialize_from_buffer(transcript.get_challenge("beta").begin()); fr gamma = fr::serialize_from_buffer(transcript.get_challenge("beta", 1).begin()); - // Our permutation check boils down to two 'grand product' arguments, - // that we represent with a single polynomial Z(X). - // We want to test that Z(X) has been constructed correctly. - // When evaluated at elements of w \in H, the numerator of Z(w) will equal the - // identity permutation grand product, and the denominator will equal the copy permutation grand product. - - // The identity that we need to evaluate is: Z(X.w).(permutation grand product) = Z(X).(identity grand product) - // i.e. The next element of Z is equal to the current element of Z, multiplied by (identity grand product) / - // (permutation grand product) - - // This method computes `Z(X).(identity grand product).{alpha}`. - // The random `alpha` is there to ensure our grand product polynomial identity is linearly independent from the - // other polynomial identities that we are going to roll into the quotient polynomial T(X). - - // Specifically, we want to compute: - // (w_l(X) + \beta.sigma1(X) + \gamma).(w_r(X) + \beta.sigma2(X) + \gamma).(w_o(X) + \beta.sigma3(X) + - // \gamma).Z(X).alpha Once we divide by the vanishing polynomial, this will be a degree 3n polynomial. - std::array wire_ffts{ key->polynomial_cache.get("w_1_fft").get_coefficients(), key->polynomial_cache.get("w_2_fft").get_coefficients(), @@ -349,34 +455,33 @@ barretenberg::fr ProverPlookupWidget: const fr* column_3_step_size = key->polynomial_cache.get("q_c_fft").get_coefficients(); const fr* lookup_fft = key->polynomial_cache.get("table_type_fft").get_coefficients(); - const fr* lookup_index_fft = key->polynomial_cache.get("table_index_fft").get_coefficients(); + const fr* lookup_index_fft = key->polynomial_cache.get("q_3_fft").get_coefficients(); - const fr gamma_beta_constant = gamma * (fr(1) + beta); + const fr gamma_beta_constant = gamma * (fr(1) + beta); // γ(1 + β) const polynomial& l_1 = key->polynomial_cache.get("lagrange_1_fft"); + // delta_factor = [γ(1 + β)]^{n-k} const fr delta_factor = gamma_beta_constant.pow(key->small_domain.size - num_roots_cut_out_of_vanishing_polynomial); const fr alpha_sqr = alpha.sqr(); - const fr beta_constant = beta + fr(1); + const fr beta_constant = beta + fr(1); // (1 + β) const size_t block_mask = key->large_domain.size - 1; - // Step 4: Set the quotient polynomial to be equal to - // (w_l(X) + \beta.sigma1(X) + \gamma).(w_r(X) + \beta.sigma2(X) + \gamma).(w_o(X) + \beta.sigma3(X) + - // \gamma).Z(X).alpha #ifndef NO_MULTITHREADING #pragma omp parallel for #endif + // Add to the quotient polynomial the components associated with z_lookup for (size_t j = 0; j < key->large_domain.num_threads; ++j) { const size_t start = j * key->large_domain.thread_size; const size_t end = (j + 1) * key->large_domain.thread_size; fr T0; fr T1; - fr T2; fr denominator; fr numerator; + // Initialize first four t(X) = t_table(X) for expression t + βt(Xω) + γ(1 + β) std::array next_ts; for (size_t i = 0; i < 4; ++i) { next_ts[i] = table_ffts[3][(start + i) & block_mask]; @@ -388,7 +493,7 @@ barretenberg::fr ProverPlookupWidget: next_ts[i] += table_ffts[0][(start + i) & block_mask]; } for (size_t i = start; i < end; ++i) { - + // Set T0 = f := (w_1 + q_2*w_1(Xω)) + η(w_2 + q_m*w_2(Xω)) + η²(w_3 + q_c*w_3(Xω)) + η³q_index T0 = lookup_index_fft[i]; T0 *= eta; T0 += wire_ffts[2][(i + 4) & block_mask] * column_3_step_size[i]; @@ -400,10 +505,12 @@ barretenberg::fr ProverPlookupWidget: T0 += wire_ffts[0][(i + 4) & block_mask] * column_1_step_size[i]; T0 += wire_ffts[0][i]; + // Set numerator = q_lookup*f + γ numerator = T0; numerator *= lookup_fft[i]; numerator += gamma; + // Set T0 = t(Xω) := t_1(Xω) + ηt_2(Xω) + η²t_3(Xω) + η³t_4(Xω) T0 = table_ffts[3][(i + 4) & block_mask]; T0 *= eta; T0 += table_ffts[2][(i + 4) & block_mask]; @@ -412,42 +519,67 @@ barretenberg::fr ProverPlookupWidget: T0 *= eta; T0 += table_ffts[0][(i + 4) & block_mask]; + // Set T1 = (t + βt(Xω) + γ(1 + β)) T1 = beta; T1 *= T0; T1 += next_ts[i & 0x03UL]; T1 += gamma_beta_constant; + // Set t(X) = t(Xω) for the next time around next_ts[i & 0x03UL] = T0; + // numerator = (q_lookup*f + γ) * (t + βt(Xω) + γ(1 + β)) * (1 + β) numerator *= T1; numerator *= beta_constant; + // Set denominator = (s + βs(Xω) + γ(1 + β)) denominator = s_fft[(i + 4) & block_mask]; denominator *= beta; denominator += s_fft[i]; denominator += gamma_beta_constant; + // Set T0 = αL_1(X) T0 = l_1[i] * alpha; + // Set T1 = α²L_{n-k}(X) = α²L_1(Xω^{-(n-k)+1}) = α²L_1(Xω^{k+1}), k = num roots cut out of Z_H T1 = l_1[(i + 4 + 4 * num_roots_cut_out_of_vanishing_polynomial) & block_mask] * alpha_sqr; + // Set numerator = z_lookup(X)*[(q_lookup*f + γ) * (t + βt(Xω) + γ(1 + β)) * (1 + β)] + (z_lookup - + // 1)*αL_1(X) numerator += T0; numerator *= z_lookup_fft[i]; numerator -= T0; + // Set denominator = z_lookup(Xω)*(s + βs(Xω) + γ(1 + β)) - [z_lookup(Xω) - [γ(1 + β)]^{n-k}]*α²L_{n-k}(X) denominator -= T1; denominator *= z_lookup_fft[(i + 4) & block_mask]; denominator += T1 * delta_factor; - // Combine into quotient polynomial + // Combine into quotient polynomial contribution + // T0 = z_lookup(X)*[(q_lookup*f + γ) * (t + βt(Xω) + γ(1 + β)) * (1 + β)] + (z_lookup - 1)*αL_1(X) ... + // - z_lookup(Xω)*(s + βs(Xω) + γ(1 + β)) + [z_lookup(Xω) - [γ(1 + β)]^{n-k}]*α²L_{n-k}(X) T0 = numerator - denominator; + // key->quotient_large[i] += T0 * alpha_base; // CODY: Luke did this while documenting key->quotient_polynomial_parts[i >> key->small_domain.log2_size][i & (key->n - 1)] += T0 * alpha_base; } } return alpha_base * alpha.sqr() * alpha; } -// This part comes computes r_plookup terms in r(X). More on this can be found in -// https://hackmd.io/vUGG8CO_Rk2iEjruBL_gGw?view#Note-A-Mind-Boggling-Issue-with-Ultra-Plonk +/** + * @brief Computes the evaluation at challenge point 'z' of the terms in the linearization polynomial + * associated with z_lookup. + * + * @tparam num_roots_cut_out_of_vanishing_polynomial + * @param alpha_base + * @param transcript + * @param r + * @return barretenberg::fr + * + * @details This is used in computation of the 'linearization' polynomial r(X). + * Note, however, that the components computed here are not 'linearized'; all terms are + * simply evaluated at 'z'. More on this function can be found in + * https://hackmd.io/vUGG8CO_Rk2iEjruBL_gGw?view#Note-A-Mind-Boggling-Issue-with-Ultra-Plonk + */ template barretenberg::fr ProverPlookupWidget::compute_linear_contribution( const fr& alpha_base, const transcript::StandardTranscript& transcript, polynomial& r) @@ -483,7 +615,7 @@ barretenberg::fr ProverPlookupWidget: fr column_2_step_size = transcript.get_field_element("q_m"); fr column_3_step_size = transcript.get_field_element("q_c"); fr table_type_eval = transcript.get_field_element("table_type"); - fr table_index_eval = transcript.get_field_element("table_index"); + fr table_index_eval = transcript.get_field_element("q_3"); fr s_eval = transcript.get_field_element("s"); fr shifted_s_eval = transcript.get_field_element("s_omega"); @@ -492,16 +624,17 @@ barretenberg::fr ProverPlookupWidget: fr shifted_z_eval = transcript.get_field_element("z_lookup_omega"); fr z = transcript.get_challenge_field_element("z"); - // fr alpha = transcript.get_challenge_field_element("alpha", 0); fr beta = transcript.get_challenge_field_element("beta", 0); fr gamma = transcript.get_challenge_field_element("beta", 1); fr eta = transcript.get_challenge_field_element("eta", 0); fr l_numerator = z.pow(key->n) - fr(1); + // Compute evaluation L_1(z) l_numerator *= key->small_domain.domain_inverse; fr l_1 = l_numerator / (z - fr(1)); - // compute w^{num_roots_cut_out_of_vanishing_polynomial + 1} + // Compute evaluation L_end(z) = L_{n-k}(z) using ω^{-(n - k) + 1} = ω^{k + 1} where + // k = num roots cut out of Z_H fr l_end_root = (num_roots_cut_out_of_vanishing_polynomial & 1) ? key->small_domain.root.sqr() : key->small_domain.root; for (size_t i = 0; i < num_roots_cut_out_of_vanishing_polynomial / 2; ++i) { @@ -510,8 +643,9 @@ barretenberg::fr ProverPlookupWidget: fr l_end = l_numerator / ((z * l_end_root) - fr(1)); const fr one(1); - const fr gamma_beta_constant = gamma * (one + beta); + const fr gamma_beta_constant = gamma * (one + beta); // γ(β + 1) + // delta_factor = γ(β + 1)^{n-k} const fr delta_factor = gamma_beta_constant.pow(key->small_domain.size - num_roots_cut_out_of_vanishing_polynomial); const fr alpha_sqr = alpha.sqr(); @@ -519,10 +653,10 @@ barretenberg::fr ProverPlookupWidget: fr T0; fr T1; - fr T2; fr denominator; fr numerator; + // Set f_eval = f(z) := (w_1(z) + q_2*w_1(zω)) + η(w_2(z) + q_m*w_2(zω)) + η²(w_3(z) + q_c*w_3(zω)) + η³q_index(z) fr f_eval = table_index_eval; f_eval *= eta; f_eval += shifted_wire_evaluations[2] * column_3_step_size; @@ -534,6 +668,7 @@ barretenberg::fr ProverPlookupWidget: f_eval += shifted_wire_evaluations[0] * column_1_step_size; f_eval += wire_evaluations[0]; + // Set table_eval = t(z) fr table_eval = table_evaluations[3]; table_eval *= eta; table_eval += table_evaluations[2]; @@ -542,9 +677,11 @@ barretenberg::fr ProverPlookupWidget: table_eval *= eta; table_eval += table_evaluations[0]; + // Set numerator = q_index(z)*f(z) + γ numerator = f_eval * table_type_eval; numerator += gamma; + // Set T0 = t(zω) T0 = shifted_table_evaluations[3]; T0 *= eta; T0 += shifted_table_evaluations[2]; @@ -553,33 +690,42 @@ barretenberg::fr ProverPlookupWidget: T0 *= eta; T0 += shifted_table_evaluations[0]; + // Set T1 = t(z) + βt(zω) + γ(β + 1) T1 = beta; T1 *= T0; T1 += table_eval; T1 += gamma_beta_constant; + // Set numerator = (q_index*f(z) + γ) * (t(z) + βt(zω) + γ(β + 1)) * (β + 1) numerator *= T1; numerator *= beta_constant; + // Set denominator = s(z) + βs(zω) + γ(β + 1) denominator = shifted_s_eval; denominator *= beta; denominator += s_eval; denominator += gamma_beta_constant; + // Set T0 = αL_1(z), T1 = α²L_end(z) T0 = l_1 * alpha; T1 = l_end * alpha_sqr; + // Set numerator = z_lookup(z)*[(q_index*f(z) + γ) * (t(z) + βt(zω) + γ(β + 1)) * (β + 1)] + (z_lookup(z) - + // 1)*αL_1(z) numerator += T0; numerator *= z_eval; numerator -= T0; + // Set denominator = z_lookup(zω)*[s(z) + βs(zω) + γ(1 + β)] - [z_lookup(zω) - [γ(1 + β)]^{n-k}]*α²L_end(z) denominator -= T1; denominator *= shifted_z_eval; denominator += T1 * delta_factor; + // Set T0 = z_lookup(z)*[(q_index*f(z) + γ) * (t(z) + βt(zω) + γ(β + 1)) * (β + 1)] + (z_lookup(z) - 1)*αL_1(z) ... + // - z_lookup(zω)*[s(z) + βs(zω) + γ(1 + β)] + [z_lookup(zω) - [γ(1 + β)]^{n-k}]*α²L_end(z) + T0 = numerator - denominator; // We need to add the constant term of plookup permutation polynomial in the linearisation // polynomial to ensure that r(z) = 0. - T0 = numerator - denominator; r[0] += T0 * alpha_base; return alpha_base * alpha.sqr() * alpha; @@ -591,11 +737,27 @@ template ::VerifierPlookupWidget() {} +/** + * @brief Computes the evaluation at challenge point 'z' of the terms in the quotient polynomial + * associated with z_lookup. + * + * @tparam Field + * @tparam Group + * @tparam Transcript + * @tparam num_roots_cut_out_of_vanishing_polynomial + * @param key + * @param alpha_base + * @param transcript + * @param r_0 + * @return Field + * + * @brief Used by verifier in computation of quotient polynomial evaluation at challenge point 'z'. + * + */ template Field VerifierPlookupWidget:: compute_quotient_evaluation_contribution( typename Transcript::Key* key, const Field& alpha_base, const Transcript& transcript, Field& r_0, const bool) - { std::array wire_evaluations{ @@ -627,7 +789,7 @@ Field VerifierPlookupWidgetdomain.domain_inverse; Field l_1 = l_numerator / (z - Field(1)); - // compute w^{num_roots_cut_out_of_vanishing_polynomial + 1} + // Compute evaluation L_end(z) = L_{n-k}(z) using ω^{-(n - k) + 1} = ω^{k + 1} where + // k = num roots cut out of Z_H Field l_end_root = (num_roots_cut_out_of_vanishing_polynomial & 1) ? key->domain.root.sqr() : key->domain.root; for (size_t i = 0; i < num_roots_cut_out_of_vanishing_polynomial / 2; ++i) { l_end_root *= key->domain.root.sqr(); } - Field l_end = l_numerator / ((z * l_end_root) - Field(1)); + Field l_end = l_numerator / ((z * l_end_root) - Field(1)); // L_{n-k}(z) const Field one(1); - const Field gamma_beta_constant = gamma * (one + beta); + const Field gamma_beta_constant = gamma * (one + beta); // γ(1 + β) + + // [γ(1 + β)]^{n-k} + const Field delta_factor = gamma_beta_constant.pow(key->domain.domain - num_roots_cut_out_of_vanishing_polynomial); - const Field delta_factor = gamma_beta_constant.pow(key->domain.size - num_roots_cut_out_of_vanishing_polynomial); const Field alpha_sqr = alpha.sqr(); - const Field beta_constant = beta + one; + const Field beta_constant = beta + one; // (1 + β) Field T0; Field T1; - Field T2; Field denominator; Field numerator; + // Set f_eval = f(z) := (w_1(z) + q_2*w_1(zω)) + η(w_2(z) + q_m*w_2(zω)) + η²(w_3(z) + q_c*w_3(zω)) + η³q_index(z) Field f_eval = table_index_eval; f_eval *= eta; f_eval += shifted_wire_evaluations[2] * column_3_step_size; @@ -677,6 +842,7 @@ Field VerifierPlookupWidget class ArithmeticKernel { public: static constexpr size_t num_independent_relations = 1; @@ -14,7 +26,9 @@ template class ArithmeticKe static constexpr uint8_t update_required_challenges = CHALLENGE_BIT_ALPHA; private: + // A structure with various challenges, even though only alpha is used here. typedef containers::challenge_array challenge_array; + // Type for the linear terms of the transition typedef containers::coefficient_array coefficient_array; public: @@ -27,6 +41,16 @@ template class ArithmeticKe return required_polynomial_ids; } + /** + * @brief Computes the linear terms. + * + * @details Multiplies the values at the first and second wire, puts the product and all the wires into the linear + * terms + * + * @param polynomials Polynomials from which the values of wires are obtained + * @param linear_terms Container for results of computation + * @param i Index at which the wire values are sampled. + */ inline static void compute_linear_terms(PolyContainer& polynomials, const challenge_array&, coefficient_array& linear_terms, @@ -45,8 +69,23 @@ template class ArithmeticKe linear_terms[3] = w_3; } + /** + * @brief Not being used in arithmetic_widget because there are none + * + */ inline static void compute_non_linear_terms(PolyContainer&, const challenge_array&, Field&, const size_t = 0) {} + /** + * @brief Scale and sum the linear terms for the final equation. + * + * @details Multiplies the linear terms by selector values and scale the whole sum by alpha before returning + * + * @param polynomials Container with polynomials or their simulation + * @param challenges A structure with various challenges + * @param linear_terms Precomuputed linear terms to be scaled and summed + * @param i The index at which selector/witness values are sampled + * @return Field Scaled sum of values + */ inline static Field sum_linear_terms(PolyContainer& polynomials, const challenge_array& challenges, coefficient_array& linear_terms, @@ -73,6 +112,13 @@ template class ArithmeticKe return result; } + /** + * @brief Compute the scaled values of openings + * + * @param linear_terms The original computed linear terms of the product and wires + * @param scalars A map where we put the values + * @param challenges Challenges where we get the alpha + */ inline static void update_kate_opening_scalars(coefficient_array& linear_terms, std::map& scalars, const challenge_array& challenges) @@ -88,9 +134,23 @@ template class ArithmeticKe } // namespace widget +/** + * @brief Standard plonk arithmetic widget for the prover. Provides standard plonk gate transition + * + * @details ArithmethicKernel provides the logic that implements the standard arithmetic transition + * q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_c=0 + * @tparam Settings + */ template using ProverArithmeticWidget = widget::TransitionWidget; +/** + * @brief Standard plonk arithmetic widget for the verifier. Provides standard plonk gate transition + * + * @details ArithmethicKernel provides the logic that implements the standard arithmetic transition + * q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_c=0 + * @tparam Settings + */ template using VerifierArithmeticWidget = widget::GenericVerifierWidget; diff --git a/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/create_dummy_transcript.hpp b/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/create_dummy_transcript.hpp index 94c04d4cf5..cab3787097 100644 --- a/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/create_dummy_transcript.hpp +++ b/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/create_dummy_transcript.hpp @@ -62,7 +62,7 @@ inline transcript::Manifest create_dummy_ultra_manifest(const size_t num_public_ { "sigma_2", fr_size, false }, { "sigma_3", fr_size, false }, { "q_arith", fr_size, false }, - { "q_ecc_1", fr_size, false }, + { "q_fixed_base", fr_size, false }, { "q_c", fr_size, false }, { "r", fr_size, false }, { "w_1_omega", fr_size, false }, diff --git a/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/elliptic_widget.hpp b/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/elliptic_widget.hpp index 6aa09c5435..2b763b7f0f 100644 --- a/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/elliptic_widget.hpp +++ b/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/elliptic_widget.hpp @@ -5,6 +5,61 @@ namespace waffle { namespace widget { +/** + * @brief Core class implementing elliptic curve point addition. It is enhanced to handle the case where one of the + * points is automatically scaled by the endomorphism constant β or negated + * + * + * @details The basic equation for the elliptic curve in short weierstrass form is y^2 == x^3 + a * x + b. + * + * The addition formulas are: + * λ = (y_2 - y_1) / (x_2 - x_1) + * x_3 = λ^2 - x_2 - x_1 = (y_2 - y_1)^2 / (x_2 - x_1)^2 - x_2 - x_1 = ((y_2 - y_1)^2 - (x_2 - x_1) * (x_2^2 - + * x_1^2)) / (x_2 - x_1)^2 + * + * If we assume that the points being added are distinct and not invereses of each other (so their x coordinates + * differ), then we can rephrase this equality: + * x_3 * (x_2 - x_1)^2 = ((y_2 - y_1)^2 - (x_2 - x_1) * (x_2^2 - x_1^2)) + * Let's say we want to apply the endomorphism to the (x_2, y_2) point at the same time and maybe change the sign of + * y_2: + * + * (x_2, y_2) = (β * x_2', sign * y_2') + * x_3 * (β * x_2' - x_1)^2 = ((sign * y_2' - y_1)^2 - (β * x_2' - x_1) * ((β * x_2')^2 - x_1^2)) + * + * Let's open the brackets and group the terms by β, β^2, sign: + * + * x_2'^2 * x_3 * β^2 - 2 * β * x_1 * x_2' * x_3 - x_1^2 * x_3 = sign^2 * y_2'^2 - 2 * sign * y_1 * y_2 + y_1^2 - β^3 + * * x_2'^3 + β * x_1^2 * x_2' + β^2 * x_1 * x_2'^2 - x_1^3 + * + * β^3 = 1 + * sign^2 = 1 (at least we always expect sign to be set to 1 or -1) + * + * sign * (-2 * y_1 * y_2) + β * (2 * x_1 * x_2' * x_3 +x_1^2 * x_2') + β^2 * (x_1 * x_2'^2 - x_2'^2 * x_3) + (x_1^2 * + * x_3 + y_2'^2 + y_1^2 - x_2'^3 - x_1^3) = 0 + * This is the equation computed in x_identity and scaled by α + * + * Now let's deal with the y equation: + * y_3 = λ * (x_3 - x_1) + y_1 = (y_2 - y_1) * (x_3 - x_1) / (x_2 - x_1) + y_1 = ((y_2 - y_1) * (x_3 - x_1) + y_1 * + * (x_2 - x_1)) / (x_2 - x_1) + * + * (x_2 - x_1) * y_3 = (y_2 - y_1) * (x_3 - x_1) + y_1 * (x_2 - x_1) + * + * Let's substitute (x_2, y_2) = (β * x_2', sign * y_2'): + * + * β * x_2' * y_3 - x_1 * y_3 - sign * y_2' * x_3 + y_1 * x_3 + sign * y_2' * x_1 - y_1 * x_1 - β * y_1 * x_2' + x_1 + * * y_1 = 0 + * + * Let's group: + * + * sign * (-y_2' * x_3 + y_2' * x_1) + β * (x_2' * x_3 + y_1 * x_2') + (-x_1 * y_3 + y_1 * x_3 - x_1 * y_1 + + * x_1 * y_1) = 0 + * + * + * + * @tparam Field Field being used for elements + * @tparam Getters A class that implements retrieval methods for PolyContainer + * @tparam PolyContainer Container with polynomials or their simulation + */ template class EllipticKernel { public: static constexpr size_t num_independent_relations = 4; @@ -21,12 +76,20 @@ template class EllipticKern inline static std::set const& get_required_polynomial_ids() { static const std::set required_polynomial_ids = { - PolynomialIndex::Q_3, PolynomialIndex::Q_4, PolynomialIndex::Q_5, PolynomialIndex::Q_ELLIPTIC, + PolynomialIndex::Q_1, PolynomialIndex::Q_3, PolynomialIndex::Q_4, PolynomialIndex::Q_ELLIPTIC, PolynomialIndex::W_1, PolynomialIndex::W_2, PolynomialIndex::W_3, PolynomialIndex::W_4 }; return required_polynomial_ids; } + /** + * @brief Computes the single linear term for elliptic point addition + * + * @param polynomials Polynomial container or simulator + * @param challenges Challenge array + * @param linear_terms Output array + * @param i Gate index + */ inline static void compute_linear_terms(PolyContainer& polynomials, const challenge_array& challenges, coefficient_array& linear_terms, @@ -41,32 +104,41 @@ template class EllipticKern const Field& x_3 = Getters::template get_value(polynomials, i); const Field& y_3 = Getters::template get_value(polynomials, i); + // Endomorphism coefficient for when we add and multiply by beta at the same time const Field& q_beta = Getters::template get_value(polynomials, i); + // Square of endomorphism coefficient const Field& q_beta_sqr = Getters::template get_value(polynomials, i); + // sign const Field& q_sign = - Getters::template get_value(polynomials, i); - - Field beta_term = -x_2 * x_1 * (x_3 + x_3 + x_1); - Field beta_sqr_term = x_2.sqr(); - Field leftovers = beta_sqr_term; - beta_sqr_term *= (x_3 - x_1); - Field sign_term = y_2 * y_1; - sign_term += sign_term; - beta_term *= q_beta; - beta_sqr_term *= q_beta_sqr; - sign_term *= q_sign; - leftovers *= x_2; - leftovers += x_1.sqr() * (x_3 + x_1); - leftovers -= (y_2.sqr() + y_1.sqr()); - + Getters::template get_value(polynomials, i); + + // TODO: Can this be implemented more efficiently? + // It seems that Zac wanted to group the elements by selectors to use several linear terms initially, + // but in the end we are using one, so there is no reason why we can't optimize computation in another way + + Field beta_term = -x_2 * x_1 * (x_3 + x_3 + x_1); // -x_1 * x_2 * (2 * x_3 + x_1) + Field beta_sqr_term = x_2.sqr(); // x_2^2 + Field leftovers = beta_sqr_term; // x_2^2 + beta_sqr_term *= (x_3 - x_1); // x_2^2 * (x_3 - x_1) + Field sign_term = y_2 * y_1; // y_1 * y_2 + sign_term += sign_term; // 2 * y_1 * y_2 + beta_term *= q_beta; // -β * x_1 * x_2 * (2 * x_3 + x_1) + beta_sqr_term *= q_beta_sqr; // β^2 * x_2^2 * (x_3 - x_1) + sign_term *= q_sign; // 2 * y_1 * y_2 * sign + leftovers *= x_2; // x_2^3 + leftovers += x_1.sqr() * (x_3 + x_1); // x_2^3 + x_1 * (x_3 + x_1) + leftovers -= (y_2.sqr() + y_1.sqr()); // x_2^3 + x_1 * (x_3 + x_1) - y_2^2 - y_1^2 + + // Can be found in class description Field x_identity = beta_term + beta_sqr_term + sign_term + leftovers; x_identity *= challenges.alpha_powers[0]; - beta_term = x_2 * (y_3 + y_1) * q_beta; - sign_term = -y_2 * (x_1 - x_3) * q_sign; - leftovers = -x_1 * (y_3 + y_1) + y_1 * (x_1 - x_3); + beta_term = x_2 * (y_3 + y_1) * q_beta; // β * x_2 * (y_3 + y_1) + sign_term = -y_2 * (x_1 - x_3) * q_sign; // - signt * y_2 * (x_1 - x_3) + // TODO: remove extra additions if we decide to stay with this implementation + leftovers = -x_1 * (y_3 + y_1) + y_1 * (x_1 - x_3); // -x_1 * y_3 - x_1 * y_1 + y_1 * x_1 - y_1 * x_3 Field y_identity = beta_term + sign_term + leftovers; y_identity *= challenges.alpha_powers[1]; @@ -74,6 +146,14 @@ template class EllipticKern linear_terms[0] = x_identity + y_identity; } + /** + * @brief Return the linear term multiplied by elliptic curve addition selector value at gate + * + * @param polynomials Polynomial container or simulator + * @param linear_terms Array of linear terms + * @param i Gate index + * @return Field + */ inline static Field sum_linear_terms(PolyContainer& polynomials, const challenge_array&, coefficient_array& linear_terms, @@ -86,6 +166,12 @@ template class EllipticKern inline static void compute_non_linear_terms(PolyContainer&, const challenge_array&, Field&, const size_t = 0) {} + /** + * @brief Update opening scalars with the linear term from elliptic gate + * + * @param linear_terms Contains input scalar + * @param scalars Output map for updates + */ inline static void update_kate_opening_scalars(coefficient_array& linear_terms, std::map& scalars, const challenge_array&) diff --git a/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_fixed_base_widget.hpp b/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/fixed_base_widget.hpp similarity index 73% rename from cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_fixed_base_widget.hpp rename to cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/fixed_base_widget.hpp index 2b3abf7aac..5c195c3c36 100644 --- a/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_fixed_base_widget.hpp +++ b/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/fixed_base_widget.hpp @@ -71,8 +71,10 @@ namespace widget { * Link: https://hackmd.io/MCmV2bipRYelT1WUNLj02g * **/ -template class TurboFixedBaseKernel { +template class FixedBaseKernel { public: + // UltraComposer only needs 6 independent relations, (α^5 is not added), but we accept the tiny inefficiency of + // computing and storing an extra power of α (we use power 0,1,2,3,4 and 6) to minimize code changes. static constexpr size_t num_independent_relations = 7; // We state the challenges required for linear/nonlinear terms computation static constexpr uint8_t quotient_required_challenges = CHALLENGE_BIT_ALPHA; @@ -88,7 +90,7 @@ template class TurboFixedBa { static const std::set required_polynomial_ids = { PolynomialIndex::Q_1, PolynomialIndex::Q_2, PolynomialIndex::Q_3, PolynomialIndex::Q_4, - PolynomialIndex::Q_5, PolynomialIndex::Q_M, PolynomialIndex::Q_C, PolynomialIndex::Q_FIXED_BASE_SELECTOR, + PolynomialIndex::Q_5, PolynomialIndex::Q_M, PolynomialIndex::Q_C, PolynomialIndex::Q_FIXED_BASE, PolynomialIndex::W_1, PolynomialIndex::W_2, PolynomialIndex::W_3, PolynomialIndex::W_4 }; return required_polynomial_ids; @@ -104,10 +106,10 @@ template class TurboFixedBa inline static bool gate_enabled(PolyContainer& polynomials, const size_t i = 0) { const Field& q_ecc_1 = - Getters::template get_value( - polynomials, i); + Getters::template get_value(polynomials, i); return !q_ecc_1.is_zero(); } + inline static void compute_linear_terms(PolyContainer& polynomials, const challenge_array& challenges, coefficient_array& linear_terms, @@ -130,34 +132,40 @@ template class TurboFixedBa Getters::template get_value(polynomials, i); const Field& q_c = Getters::template get_value(polynomials, i); - const Field& q_ecc_1 = - Getters::template get_value( - polynomials, i); + const Field& q_fixed_base = + Getters::template get_value(polynomials, i); Field delta = w_4_omega - (w_4 + w_4 + w_4 + w_4); Field delta_squared = delta.sqr(); - Field q_1_multiplicand = delta_squared * q_ecc_1 * challenges.alpha_powers[1]; + Field q_1_multiplicand = delta_squared * q_fixed_base * challenges.alpha_powers[1]; - Field q_2_multiplicand = challenges.alpha_powers[1] * q_ecc_1; + Field q_2_multiplicand = challenges.alpha_powers[1] * q_fixed_base; - Field q_3_multiplicand = (w_1_omega - w_1) * delta * w_3_omega * challenges.alpha_powers[3] * q_ecc_1; + Field q_3_multiplicand = (w_1_omega - w_1) * delta * w_3_omega * challenges.alpha_powers[3] * q_fixed_base; Field T1 = delta * w_3_omega * w_2 * challenges.alpha_powers[2]; - q_3_multiplicand = q_3_multiplicand + (T1 + T1) * q_ecc_1; + q_3_multiplicand = q_3_multiplicand + (T1 + T1) * q_fixed_base; - Field q_4_multiplicand = w_3 * q_ecc_1 * q_c * challenges.alpha_powers[5]; + Field q_4_multiplicand; - Field q_5_multiplicand = (Field(1) - w_4) * q_ecc_1 * q_c * challenges.alpha_powers[5]; + Field q_5_multiplicand; - Field q_m_multiplicand = w_3 * q_ecc_1 * q_c * challenges.alpha_powers[6]; + if constexpr (turbo) { // α^5 terms not present in UltraPlonK + q_4_multiplicand = w_3 * q_fixed_base * q_c * challenges.alpha_powers[5]; + q_5_multiplicand = (Field(1) - w_4) * q_fixed_base * q_c * challenges.alpha_powers[5]; + } - linear_terms[0] = q_m_multiplicand; - linear_terms[1] = q_1_multiplicand; - linear_terms[2] = q_2_multiplicand; - linear_terms[3] = q_3_multiplicand; - linear_terms[4] = q_4_multiplicand; - linear_terms[5] = q_5_multiplicand; + Field q_m_multiplicand = w_3 * q_fixed_base * q_c * challenges.alpha_powers[6]; + + linear_terms[0] = q_m_multiplicand; // α^6 q_fixed_base q_c w_3 + linear_terms[1] = q_1_multiplicand; // α q_fixed_base δ^2 + linear_terms[2] = q_2_multiplicand; // α q_fixed_base + linear_terms[3] = q_3_multiplicand; // α^3 q_fixed_base δ * (w_1,ω - w_1) * w_3,ω + if constexpr (turbo) { // α^5 terms not present in UltraPlonK + linear_terms[4] = q_4_multiplicand; // α^5 q_fixed_base q_c w_3 + linear_terms[5] = q_5_multiplicand; // α^5 q_fixed_base q_c (1 - w_4) + } } inline static Field sum_linear_terms(PolyContainer& polynomials, @@ -173,8 +181,6 @@ template class TurboFixedBa Getters::template get_value(polynomials, i); const Field& q_4 = Getters::template get_value(polynomials, i); - const Field& q_5 = - Getters::template get_value(polynomials, i); const Field& q_m = Getters::template get_value(polynomials, i); @@ -182,8 +188,12 @@ template class TurboFixedBa result += (linear_terms[1] * q_1); result += (linear_terms[2] * q_2); result += (linear_terms[3] * q_3); - result += (linear_terms[4] * q_4); - result += (linear_terms[5] * q_5); + if constexpr (turbo) { + const Field& q_5 = + Getters::template get_value(polynomials, i); + result += (linear_terms[4] * q_4); + result += (linear_terms[5] * q_5); + } return result; } @@ -212,9 +222,8 @@ template class TurboFixedBa Getters::template get_value(polynomials, i); const Field& q_c = Getters::template get_value(polynomials, i); - const Field& q_ecc_1 = - Getters::template get_value( - polynomials, i); + const Field& q_fixed_base = + Getters::template get_value(polynomials, i); Field delta = w_4_omega - (w_4 + w_4 + w_4 + w_4); const Field three = Field(3); @@ -223,8 +232,10 @@ template class TurboFixedBa Field T3 = (delta - Field(1)); Field T4 = (delta - three); + // accumulator_identity = (δ + 3)(δ + 1)(δ - 1)(δ - 3) Field accumulator_identity = T1 * T2 * T3 * T4 * challenges.alpha_powers[0]; + // x_alpha_identity = -α w_3,ω Field x_alpha_identity = -(w_3_omega * challenges.alpha_powers[1]); Field T0 = w_1_omega + w_1 + w_3_omega; @@ -236,35 +247,48 @@ template class TurboFixedBa T1 = T1 + T2; T1 = -(T1 + grumpkin_curve_b); - T2 = delta * w_2 * q_ecc_1; + T2 = delta * w_2 * q_fixed_base; T2 = T2 + T2; + // x_accumulator_identity = α^2 * + // [(w_1,ω + w_1 + w_3,ω) * (w_3,ω - w_1)^2 - (b + w_3,ω^3 + w_2^2) + 2δ * w_2 * q_fixed_base] Field x_accumulator_identity = (T0 + T1 + T2) * challenges.alpha_powers[2]; T0 = (w_2_omega + w_2) * (w_3_omega - w_1); T1 = w_1 - w_1_omega; - T2 = w_2 - (q_ecc_1 * delta); + T2 = w_2 - (q_fixed_base * delta); T1 = T1 * T2; + // y_accumulator_identity = α^3 * + // [(w_2,ω + w_2) * (w_3,ω - w_1) + (w_1 - w_1,ω) * (w_2 - q_fixed_base * δ)] Field y_accumulator_identity = (T0 + T1) * challenges.alpha_powers[3]; + // accumulator_init_identity = α^4 * (w_4 - 1)(w_4 - 1 - w_3) T0 = w_4 - Field(1); T1 = T0 - w_3; Field accumulator_init_identity = T0 * T1 * challenges.alpha_powers[4]; - Field x_init_identity = -(w_1 * w_3) * challenges.alpha_powers[5]; + Field x_init_identity; + if constexpr (turbo) { // α^5 terms not present in UltraPlonK + // x_init_identity = -α^5 * w_1 * w_3 + x_init_identity = -(w_1 * w_3) * challenges.alpha_powers[5]; + } + // y_init_identity = α^6 * (q_c * (1 - w_4) - w_2 * w_3) T0 = Field(1) - w_4; T0 = T0 * q_c; T1 = w_2 * w_3; Field y_init_identity = (T0 - T1) * challenges.alpha_powers[6]; - Field gate_identity = accumulator_init_identity + x_init_identity + y_init_identity; + Field gate_identity = accumulator_init_identity + y_init_identity; + if constexpr (turbo) { // α^5 terms not present in UltraPlonK + gate_identity += x_init_identity; + } gate_identity = gate_identity * q_c; gate_identity = gate_identity + accumulator_identity + x_alpha_identity + x_accumulator_identity + y_accumulator_identity; - gate_identity = gate_identity * q_ecc_1; + gate_identity = gate_identity * q_fixed_base; quotient += gate_identity; } @@ -277,18 +301,31 @@ template class TurboFixedBa scalars["Q_1"] += linear_terms[1]; scalars["Q_2"] += linear_terms[2]; scalars["Q_3"] += linear_terms[3]; - scalars["Q_4"] += linear_terms[4]; - scalars["Q_5"] += linear_terms[5]; + if constexpr (turbo) { // α^5 terms not present in UltraPlonK + scalars["Q_4"] += linear_terms[4]; + scalars["Q_5"] += linear_terms[5]; + } } }; +template +using TurboFixedBaseKernel = FixedBaseKernel; + +template +using UltraFixedBaseKernel = FixedBaseKernel; } // namespace widget template using ProverTurboFixedBaseWidget = widget::TransitionWidget; +template +using ProverUltraFixedBaseWidget = widget::TransitionWidget; + template using VerifierTurboFixedBaseWidget = widget::GenericVerifierWidget; +template +using VerifierUltraFixedBaseWidget = + widget::GenericVerifierWidget; } // namespace waffle \ No newline at end of file diff --git a/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/genperm_sort_widget.hpp b/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/genperm_sort_widget.hpp index 1e8fa26952..1efad15001 100644 --- a/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/genperm_sort_widget.hpp +++ b/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/genperm_sort_widget.hpp @@ -20,12 +20,10 @@ template class GenPermSortK public: inline static std::set const& get_required_polynomial_ids() { - static const std::set required_polynomial_ids = { PolynomialIndex::Q_SORT_SELECTOR, - PolynomialIndex::W_1, - PolynomialIndex::W_2, - PolynomialIndex::W_3, - PolynomialIndex::W_4, - PolynomialIndex::Z }; + static const std::set required_polynomial_ids = { + PolynomialIndex::Q_SORT, PolynomialIndex::W_1, PolynomialIndex::W_2, + PolynomialIndex::W_3, PolynomialIndex::W_4, PolynomialIndex::Z + }; return required_polynomial_ids; } @@ -107,7 +105,7 @@ template class GenPermSortK const size_t i = 0) { const Field& q_sort = - Getters::template get_value(polynomials, i); + Getters::template get_value(polynomials, i); return linear_terms[0] * q_sort; } @@ -116,7 +114,7 @@ template class GenPermSortK std::map& scalars, const challenge_array&) { - scalars["Q_SORT_SELECTOR"] += linear_terms[0]; + scalars["Q_SORT"] += linear_terms[0]; } }; diff --git a/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/plookup_arithmetic_widget.hpp b/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/plookup_arithmetic_widget.hpp new file mode 100644 index 0000000000..cd809c4f39 --- /dev/null +++ b/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/plookup_arithmetic_widget.hpp @@ -0,0 +1,208 @@ +#pragma once + +#include "./transition_widget.hpp" + +namespace waffle { +namespace widget { + +/** + * @brief Core class implementing the arithmetic gate in Turbo plonk + * + * @details ArithmethicKernel provides the logic that can implement one of several transitions. The whole formula + * without alpha scaling is: + * + * q_arith * ( ( (-1/2) * (q_arith - 3) * q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c ) + + * (q_arith - 1)*( α² * (q_arith - 2) * (w_1 + w_4 - w_1_omega + q_m) + w_4_omega) ) = 0 + * + * This formula results in several cases depending on q_arith: + * 1. q_arith == 0: Arithmetic gate is completely disabled + * + * 2. q_arith == 1: Everything in the minigate on the right is disabled. The equation is just a standard plonk equation + * with extra wires: q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c = 0 + * + * 3. q_arith == 2: The (w_1 + w_4 - ...) term is disabled. THe equation is: + * (1/2) * q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + w_4_omega = 0 + * It allows defining w_4 at next index (w_4_omega) in terms of current wire values + * + * 4. q_arith == 3: The product of w_1 and w_2 is disabled, but a mini addition gate is enabled. α² allows us to split + * the equation into two: + * + * q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + 2 * w_4_omega = 0 + * + * w_1 + w_4 - w_1_omega + q_m = 0 (we are reusing q_m here) + * + * 5. q_arith > 3: The product of w_1 and w_2 is scaled by (q_arith - 3), while the w_4_omega term is scaled by (q_arith + * - 1). The equation can be split into two: + * + * (q_arith - 3)* q_m * w_1 * w_ 2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + (q_arith - 1) * w_4_omega = 0 + * + * w_1 + w_4 - w_1_omega + q_m = 0 + * + * The problem that q_m is used both in both equations can be dealt with by appropriately changing selector values at + * the next gate. Then we can treat (q_arith - 1) as a simulated q_6 selector and scale q_m to handle (q_arith - 3) at + * product. + * + * Uses only the alpha challenge + * + * @tparam Field The basic field in which the elements operates + * @tparam Getters The class providing functions that access evaluations of polynomials at indices + * @tparam PolyContainer Container for the polynomials or their simulation + */ +template class PlookupArithmeticKernel { + public: + static constexpr bool use_quotient_mid = false; + static constexpr size_t num_independent_relations = 2; + // We state the challenges required for linear/nonlinear terms computation + static constexpr uint8_t quotient_required_challenges = CHALLENGE_BIT_ALPHA; + // We state the challenges required for updating kate opening scalars + static constexpr uint8_t update_required_challenges = CHALLENGE_BIT_ALPHA; + + private: + // A structure with various challenges, even though only alpha is used here. + typedef containers::challenge_array challenge_array; + // Type for the linear terms of the transition (not actually used here) + typedef containers::coefficient_array coefficient_array; + + public: + inline static std::set const& get_required_polynomial_ids() + { + static const std::set required_polynomial_ids = { + PolynomialIndex::Q_1, PolynomialIndex::Q_2, PolynomialIndex::Q_3, PolynomialIndex::Q_4, + PolynomialIndex::Q_5, PolynomialIndex::Q_M, PolynomialIndex::Q_C, PolynomialIndex::Q_ARITHMETIC, + PolynomialIndex::W_1, PolynomialIndex::W_2, PolynomialIndex::W_3, PolynomialIndex::W_4 + }; + return required_polynomial_ids; + } + + /** + * @brief Stub for computing linear terms. Not used in plookup artihmetic gate + * + */ + inline static void compute_linear_terms(PolyContainer&, + const challenge_array&, + coefficient_array&, + const size_t = 0) + {} + /** + * @brief Computes the full identity for the arithmetic gate in plookup to be added to the quotient. All the logic + * is explained in class description + * + * @param polynomials Container for polynomials or their simpulation + * @param challenges Challenge array (we only need powers of alpha here) + * @param quotient Quotient reference to add the result to + * @param i Gate index + */ + inline static void compute_non_linear_terms(PolyContainer& polynomials, + const challenge_array& challenges, + Field& quotient, + const size_t i = 0) + { + // For subgroup element i, this term evaluates to W_4(i \omega) * 2 iff Q_ARITH(i \omega) = 2 + const Field& q_arith = + Getters::template get_value(polynomials, i); + const Field& w_1 = + Getters::template get_value(polynomials, i); + const Field& w_2 = + Getters::template get_value(polynomials, i); + const Field& w_3 = + Getters::template get_value(polynomials, i); + const Field& w_4 = + Getters::template get_value(polynomials, i); + const Field& w_1_omega = + Getters::template get_value(polynomials, i); + const Field& w_4_omega = + Getters::template get_value(polynomials, i); + const Field& q_1 = + Getters::template get_value(polynomials, i); + const Field& q_2 = + Getters::template get_value(polynomials, i); + const Field& q_3 = + Getters::template get_value(polynomials, i); + const Field& q_4 = + Getters::template get_value(polynomials, i); + const Field& q_m = + Getters::template get_value(polynomials, i); + const Field& q_c = + Getters::template get_value(polynomials, i); + + const Field& alpha_base = challenges.alpha_powers[0]; + const Field& alpha = challenges.alpha_powers[1]; + + // basic arithmetic gate identity + // (w_1 . w_2 . q_m) + (w_1 . q_1) + (w_2 . q_2) + (w_3 . q_3) + (w_4 . q_4) + q_c = 0 + // q_m is turned off if q_arith == 3 + Field arithmetic_gate_identity = w_2; + arithmetic_gate_identity *= q_m; + arithmetic_gate_identity *= (q_arith - 3); + + // TODO: if we multiply all q_m values by `-1/2` we can remove the need for this extra multiplication + if constexpr (std::is_same::value) { + static constexpr barretenberg::fr neg_half = barretenberg::fr(-2).invert(); + arithmetic_gate_identity *= neg_half; + } else { + static const Field neg_half = Field(-2).invert(); + arithmetic_gate_identity *= neg_half; + } + arithmetic_gate_identity += q_1; + arithmetic_gate_identity *= w_1; + arithmetic_gate_identity += (w_2 * q_2); + arithmetic_gate_identity += (w_3 * q_3); + arithmetic_gate_identity += (w_4 * q_4); + arithmetic_gate_identity += q_c; + + // if q_arith == 2 OR q_arith == 3 we add the 4th wire of the NEXT gate into the arithmetic identity + // N.B. if q_arith > 2, this wire value will be scaled by (q_arith - 1) relative to the other gate wires! + const Field next_wire_in_arithmetic_gate_identity = w_4_omega; + + // if q_arith == 3 we evaluate an additional mini addition gate (on top of the regular one), where: + // w_1 + w_4 - w_1_omega + q_m = 0 + // we use this gate to save an addition gate when adding or subtracting non-native field elements + Field extra_small_addition_gate_identity = (w_1 + w_4 - w_1_omega + q_m); + extra_small_addition_gate_identity *= alpha; + extra_small_addition_gate_identity *= (q_arith - 2); + + Field identity = extra_small_addition_gate_identity + next_wire_in_arithmetic_gate_identity; + identity *= (q_arith - 1); + identity += arithmetic_gate_identity; + identity *= q_arith; + identity *= alpha_base; + + quotient += identity; + } + + inline static Field sum_linear_terms(PolyContainer&, const challenge_array&, coefficient_array&, const size_t = 0) + { + return Field(0); + } + + /** + * @brief Stub for updating opening scalars, since not using linear terms + * + */ + inline static void update_kate_opening_scalars(coefficient_array&, + std::map&, + const challenge_array&) + {} +}; + +} // namespace widget + +/** + * @brief Ultra plonk arithmetic widget for the prover. It's quite complex, so for details better look at the kernel + * class description + * @tparam Settings + */ +template +using ProverPlookupArithmeticWidget = + widget::TransitionWidget; + +/** + * @brief Ultra plonk arithmetic widget for the verifier. It's quite complex, so for details better look at the kernel + * class description + * @tparam Settings + */ +template +using VerifierPlookupArithmeticWidget = + widget::GenericVerifierWidget; + +} // namespace waffle \ No newline at end of file diff --git a/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/plookup_auxiliary_widget.hpp b/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/plookup_auxiliary_widget.hpp new file mode 100644 index 0000000000..121f321432 --- /dev/null +++ b/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/plookup_auxiliary_widget.hpp @@ -0,0 +1,256 @@ +#pragma once + +#include "./transition_widget.hpp" + +namespace waffle { +namespace widget { + +/** + * Plookup Auxiliary Widget + * + * Evaluates polynomial identities associated with the following UltraPlonk custom gates: + * + * RAM/ROM read-write consistency check + * RAM timestamp difference consistency check + * RAM/ROM index difference consistency check + * Bigfield product evaluation (3 in total) + * Bigfield limb accumulation (2 in total) + * + **/ +template class PlookupAuxiliaryKernel { + public: + static constexpr bool use_quotient_mid = false; + static constexpr size_t num_independent_relations = 3; + // We state the challenges required for linear/nonlinear terms computation + static constexpr uint8_t quotient_required_challenges = CHALLENGE_BIT_ALPHA; + // We state the challenges required for updating kate opening scalars + static constexpr uint8_t update_required_challenges = CHALLENGE_BIT_ALPHA; + + private: + typedef containers::challenge_array challenge_array; + typedef containers::coefficient_array coefficient_array; + + public: + inline static std::set const& get_required_polynomial_ids() + { + static const std::set required_polynomial_ids = { PolynomialIndex::Q_1, PolynomialIndex::Q_2, + PolynomialIndex::Q_3, PolynomialIndex::Q_4, + PolynomialIndex::Q_M, PolynomialIndex::Q_AUX, + PolynomialIndex::W_1, PolynomialIndex::W_2, + PolynomialIndex::W_3, PolynomialIndex::W_4 }; + return required_polynomial_ids; + } + + inline static void compute_linear_terms(PolyContainer&, + const challenge_array&, + coefficient_array&, + const size_t = 0) + {} + + inline static void compute_non_linear_terms(PolyContainer& polynomials, + const challenge_array& challenges, + Field& quotient, + const size_t i = 0) + { + constexpr barretenberg::fr LIMB_SIZE(uint256_t(1) << 68); + constexpr barretenberg::fr SUBLIMB_SHIFT(uint256_t(1) << 14); + + const Field& w_1 = + Getters::template get_value(polynomials, i); + const Field& w_2 = + Getters::template get_value(polynomials, i); + const Field& w_3 = + Getters::template get_value(polynomials, i); + const Field& w_4 = + Getters::template get_value(polynomials, i); + const Field& w_1_omega = + Getters::template get_value(polynomials, i); + const Field& w_2_omega = + Getters::template get_value(polynomials, i); + const Field& w_3_omega = + Getters::template get_value(polynomials, i); + const Field& w_4_omega = + Getters::template get_value(polynomials, i); + + const Field& alpha_base = challenges.alpha_powers[0]; + const Field& alpha = challenges.elements[ChallengeIndex::ALPHA]; + const Field& eta = challenges.elements[ChallengeIndex::ETA]; + + const Field& q_aux = + Getters::template get_value(polynomials, i); + const Field& q_1 = + Getters::template get_value(polynomials, i); + const Field& q_2 = + Getters::template get_value(polynomials, i); + const Field& q_3 = + Getters::template get_value(polynomials, i); + const Field& q_4 = + Getters::template get_value(polynomials, i); + const Field& q_m = + Getters::template get_value(polynomials, i); + + /** + * Non native field arithmetic gate 2 + * + * _ _ + * / _ _ _ 14 \ + * q_2 . q_4 | (w_1 . w_2) + (w_1 . w_2) + (w_1 . w_4 + w_2 . w_3 - w_3) . 2 - w_3 - w_4 | + * \_ _/ + * + **/ + Field limb_subproduct = w_1 * w_2_omega + w_1_omega * w_2; + Field non_native_field_gate_2 = (w_1 * w_4 + w_2 * w_3 - w_3_omega); + non_native_field_gate_2 *= LIMB_SIZE; + non_native_field_gate_2 -= w_4_omega; + non_native_field_gate_2 += limb_subproduct; + non_native_field_gate_2 *= q_4; + + limb_subproduct *= LIMB_SIZE; + limb_subproduct += (w_1_omega * w_2_omega); + Field non_native_field_gate_1 = limb_subproduct; + non_native_field_gate_1 -= (w_3 + w_4); + non_native_field_gate_1 *= q_3; + + Field non_native_field_gate_3 = limb_subproduct; + non_native_field_gate_3 += w_4; + non_native_field_gate_3 -= (w_3_omega + w_4_omega); + non_native_field_gate_3 *= q_m; + + Field non_native_field_identity = non_native_field_gate_1 + non_native_field_gate_2 + non_native_field_gate_3; + non_native_field_identity *= q_2; + + Field limb_accumulator_1 = w_2_omega; + limb_accumulator_1 *= SUBLIMB_SHIFT; + limb_accumulator_1 += w_1_omega; + limb_accumulator_1 *= SUBLIMB_SHIFT; + limb_accumulator_1 += w_3; + limb_accumulator_1 *= SUBLIMB_SHIFT; + limb_accumulator_1 += w_2; + limb_accumulator_1 *= SUBLIMB_SHIFT; + limb_accumulator_1 += w_1; + limb_accumulator_1 -= w_4; + limb_accumulator_1 *= q_4; + + Field limb_accumulator_2 = w_3_omega; + limb_accumulator_2 *= SUBLIMB_SHIFT; + limb_accumulator_2 += w_2_omega; + limb_accumulator_2 *= SUBLIMB_SHIFT; + limb_accumulator_2 += w_1_omega; + limb_accumulator_2 *= SUBLIMB_SHIFT; + limb_accumulator_2 += w_4; + limb_accumulator_2 *= SUBLIMB_SHIFT; + limb_accumulator_2 += w_3; + limb_accumulator_2 -= w_4_omega; + limb_accumulator_2 *= q_m; + + Field limb_accumulator_identity = limb_accumulator_1 + limb_accumulator_2; + limb_accumulator_identity *= q_3; + + limb_subproduct = w_1_omega - w_1; + + /** + * MEMORY + * + * A memory record contains a tuple of the following fields: + * * i: `index` of memory cell being accessed + * * t: `tiemstamp` of memory cell being accessed (used for RAM, set to 0 for ROM) + * * v: `value` of memory cell being accessed + * * r: `record` of memory cell. record = index + timestamp * eta + value * eta^2 + * + * A record gate is structured such that each of the above 4 fields maps to wires 1, 2, 3, 4 + **/ + + /** + * RAM SORTED LIST CHECK + * + * Validate the following at gate `j`: + * + * 1. (i_{j+1} - i_j)^2 - (i_{j+1} - i_j) = 0 (index increases by 0 or 1) + * 2. (r = i + t * eta + v * eta^2) + * + * Used for sorted RAM records iff gate `j` is a RAM read and gate `j + 1` is a RAM write + **/ + Field memory_sorted_list_check = limb_subproduct.sqr() - limb_subproduct; + + /** + * ROM CONSISTENT SORTED LIST CHECK + * + * Validate the following at gate `j`: + * + * 1. (i_{j+1} - i_j)^2 - (i_{j+1} - i_j) = 0 (index increases by 0 or 1) + * 2. (r = i + t * eta + v * eta^2) + * 3. (1 - (i_{j+1} - i_j)) * (r_{j+1} - r_j) (if index does not change, neither does + *value) + * + * Used for sorted ROM records + **/ + // TODO: MAKE this work for ram records iff gate `j` is a RAM write and gate `j + 1` is a + // *RAM read + // for RAM we want to compare across the value field (column 3) + // but for ROM, if we compare across the record field (column 4) we can lookup 2 ROM values per gate + Field memory_consistent_sorted_list_check = Field(1) - limb_subproduct; // 1 - (w_1_omega - w_1) + // Field memory_consistent_sorted_list_RAM_check = memory_consistent_sorted_list_check * (w_3_omega - w_3); + memory_consistent_sorted_list_check *= (w_4_omega - w_4); // (1 - (w_1_omega - w_1)) * (w_4_omega - w_4) + + /** + * RAM TIMESTAMP CHECK + * + * Validate the following at gate `j`: + * + * 1. \delta = (1 - ((i_{j+1} - i_j)^2 - (i_{j+1} - i_j))(t_{j+1} - t_j) + * + * i.e. If index does not change, `\delta` = timestamp difference, eles `\delta` = 0 + * + * Used for RAM records to validate consistency between read/writes into the same cell. + * Timestamp check is performed in a separate gate to the checks against the sorted list (with copy constraints + *to map between the two gates) + **/ + Field memory_timestamp_check = memory_consistent_sorted_list_check - w_2; + + Field memory_record_check = w_3; + memory_record_check *= eta; + memory_record_check += w_2; + memory_record_check *= eta; + memory_record_check += w_1; + memory_record_check -= w_4; + + memory_sorted_list_check *= alpha; + memory_sorted_list_check += memory_record_check; + + memory_consistent_sorted_list_check += (memory_sorted_list_check * alpha); + + Field memory_identity = memory_consistent_sorted_list_check * q_2; + memory_identity += memory_sorted_list_check * q_3; + memory_identity += memory_timestamp_check * q_4; + memory_identity += memory_record_check * q_m; + memory_identity *= q_1; + + Field auxiliary_identity = memory_identity + non_native_field_identity + limb_accumulator_identity; + auxiliary_identity *= q_aux; + auxiliary_identity *= alpha_base; + + quotient += (auxiliary_identity); + } + + inline static Field sum_linear_terms(PolyContainer&, const challenge_array&, coefficient_array&, const size_t = 0) + { + return Field(0); + } + + inline static void update_kate_opening_scalars(coefficient_array&, + std::map&, + const challenge_array&) + {} +}; + +} // namespace widget + +template +using ProverPlookupAuxiliaryWidget = + widget::TransitionWidget; + +template +using VerifierPlookupAuxiliaryWidget = + widget::GenericVerifierWidget; + +} // namespace waffle \ No newline at end of file diff --git a/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/transition_widget.hpp b/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/transition_widget.hpp index c3c4128ec7..34ab006298 100644 --- a/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/transition_widget.hpp +++ b/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/transition_widget.hpp @@ -52,7 +52,25 @@ template using coefficient_array = std::array class BaseGetter { protected: typedef containers::challenge_array challenge_array; @@ -92,18 +110,17 @@ template class EvaluationGetter : public BaseGetter { protected: @@ -151,7 +177,13 @@ class EvaluationGetter : public BaseGetter class TurboArithmeticKernel { public: static constexpr size_t num_independent_relations = 2; @@ -14,7 +33,9 @@ template class TurboArithme static constexpr uint8_t update_required_challenges = CHALLENGE_BIT_ALPHA; private: + // A structure with various challenges, even though only alpha is used here. typedef containers::challenge_array challenge_array; + // Type for the linear terms of the transition typedef containers::coefficient_array coefficient_array; public: @@ -28,20 +49,34 @@ template class TurboArithme inline static bool gate_enabled(PolyContainer& polynomials, const size_t i = 0) { const Field& q_arith = - Getters::template get_value( - polynomials, i); + Getters::template get_value(polynomials, i); return !q_arith.is_zero(); } + inline static std::set const& get_required_polynomial_ids() { static const std::set required_polynomial_ids = { PolynomialIndex::Q_1, PolynomialIndex::Q_2, PolynomialIndex::Q_3, PolynomialIndex::Q_4, - PolynomialIndex::Q_5, PolynomialIndex::Q_M, PolynomialIndex::Q_C, PolynomialIndex::Q_ARITHMETIC_SELECTOR, + PolynomialIndex::Q_5, PolynomialIndex::Q_M, PolynomialIndex::Q_C, PolynomialIndex::Q_ARITHMETIC, PolynomialIndex::W_1, PolynomialIndex::W_2, PolynomialIndex::W_3, PolynomialIndex::W_4 }; return required_polynomial_ids; } + /** + * @brief Computes the linear terms. + * + * @details Multiplies the values at the first and second wire, puts the product and all the wires into the + * linear terms multiplied by appropriate selector values, the fifth linear term is polynomial vanishing on + * values {0, 1, 2} for w_4 + * + * Uses only the alpha challenge + * + * @param polynomials Polynomials from which the values of wires are obtained + * @param challenges Challenge array, but only alpha challenge is used + * @param linear_terms Container for results of computation + * @param i Index at which the wire values are sampled. + */ inline static void compute_linear_terms(PolyContainer& polynomials, const challenge_array& challenges, coefficient_array& linear_terms, @@ -59,8 +94,7 @@ template class TurboArithme Getters::template get_value(polynomials, i); const Field& q_arith = - Getters::template get_value( - polynomials, i); + Getters::template get_value(polynomials, i); Field T0; Field T1; @@ -93,6 +127,15 @@ template class TurboArithme linear_terms[6] = q_arith; } + /** + * @brief Compute the non-linear term that is enabled by q_arith==2 and allows getting information about whether the + * highest bit is set in w_3 - 4*w_4 + * + * @param polynomials Containers with polynomials or their simulation + * @param challenges Challenge arrey (we are only using alpha) + * @param quotient Reference to quotient, which will be updated with the non-linear term + * @param i Index at which the wires/seclectors are evaluated + */ inline static void compute_non_linear_terms(PolyContainer& polynomials, const challenge_array& challenges, Field& quotient, @@ -105,8 +148,7 @@ template class TurboArithme const Field& w_4 = Getters::template get_value(polynomials, i); const Field& q_arith = - Getters::template get_value( - polynomials, i); + Getters::template get_value(polynomials, i); const Field& alpha_base = challenges.alpha_powers[0]; Field T1; @@ -179,6 +221,16 @@ template class TurboArithme quotient += T1; } + /** + * @brief Scales all the linear terms by appropriate selectors, sums the, scales by alpha and returns the result + * + * @param polynomials Container with polynomials or their simulation + * @param challenges A structure with various challenges + * @param linear_terms Precomputed linear terms to be scaled and summed + * @param i The index at which selector/witness values are sampled + * @return Field Scaled sum of values + * + */ inline static Field sum_linear_terms(PolyContainer& polynomials, const challenge_array& challenges, coefficient_array& linear_terms, @@ -211,6 +263,13 @@ template class TurboArithme return result; } + /** + * @brief Compute the scaled values of openings + * + * @param linear_terms The original computed linear terms of the product and wires + * @param scalars A map where we put the values + * @param challenges Challenges where we get the alpha + */ inline static void update_kate_opening_scalars(coefficient_array& linear_terms, std::map& scalars, const challenge_array& challenges) @@ -229,9 +288,19 @@ template class TurboArithme } // namespace widget +/** + * @brief Turbo plonk arithmetic widget for the prover. Provides standard plonk gate transition + * + * @tparam Settings + */ template using ProverTurboArithmeticWidget = widget::TransitionWidget; +/** + * @brief Turbo plonk arithmetic widget for the verifier. Provides standard plonk gate transition + * + * @tparam Settings + */ template using VerifierTurboArithmeticWidget = widget::GenericVerifierWidget; diff --git a/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_logic_widget.hpp b/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_logic_widget.hpp index 8c6c8f906d..642034cc11 100644 --- a/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_logic_widget.hpp +++ b/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_logic_widget.hpp @@ -21,9 +21,8 @@ template class TurboLogicKe inline static std::set const& get_required_polynomial_ids() { static const std::set required_polynomial_ids = { - PolynomialIndex::Q_C, PolynomialIndex::Q_LOGIC_SELECTOR, - PolynomialIndex::W_1, PolynomialIndex::W_2, - PolynomialIndex::W_3, PolynomialIndex::W_4 + PolynomialIndex::Q_C, PolynomialIndex::Q_LOGIC, PolynomialIndex::W_1, + PolynomialIndex::W_2, PolynomialIndex::W_3, PolynomialIndex::W_4 }; return required_polynomial_ids; } @@ -38,9 +37,10 @@ template class TurboLogicKe inline static bool gate_enabled(PolyContainer& polynomials, const size_t i = 0) { const Field& q_logic = - Getters::template get_value(polynomials, i); + Getters::template get_value(polynomials, i); return !q_logic.is_zero(); } + inline static void compute_linear_terms(PolyContainer& polynomials, const challenge_array& challenges, coefficient_array& linear_terms, @@ -212,7 +212,7 @@ template class TurboLogicKe const size_t i = 0) { const Field& q_logic = - Getters::template get_value(polynomials, i); + Getters::template get_value(polynomials, i); return linear_terms[0] * q_logic; } @@ -221,7 +221,7 @@ template class TurboLogicKe std::map& scalars, const challenge_array&) { - scalars["Q_LOGIC_SELECTOR"] += linear_terms[0]; + scalars["Q_LOGIC"] += linear_terms[0]; } }; diff --git a/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_range_widget.hpp b/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_range_widget.hpp index ac057341e6..71027aec54 100644 --- a/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_range_widget.hpp +++ b/cpp/src/aztec/plonk/proof_system/widgets/transition_widgets/turbo_range_widget.hpp @@ -85,12 +85,13 @@ template class TurboRangeKe inline static bool gate_enabled(PolyContainer& polynomials, const size_t i = 0) { const Field& q_range = - Getters::template get_value(polynomials, i); + Getters::template get_value(polynomials, i); return !q_range.is_zero(); } + inline static std::set const& get_required_polynomial_ids() { - static const std::set required_polynomial_ids = { PolynomialIndex::Q_RANGE_SELECTOR, + static const std::set required_polynomial_ids = { PolynomialIndex::Q_RANGE, PolynomialIndex::W_1, PolynomialIndex::W_2, PolynomialIndex::W_3, @@ -187,7 +188,7 @@ template class TurboRangeKe const size_t i = 0) { const Field& q_range = - Getters::template get_value(polynomials, i); + Getters::template get_value(polynomials, i); return linear_terms[0] * q_range; } @@ -196,7 +197,7 @@ template class TurboRangeKe std::map& scalars, const challenge_array&) { - scalars["Q_RANGE_SELECTOR"] += linear_terms[0]; + scalars["Q_RANGE"] += linear_terms[0]; } }; diff --git a/cpp/src/aztec/plonk/transcript/manifest.hpp b/cpp/src/aztec/plonk/transcript/manifest.hpp index fbb1f4e960..545f0d120e 100644 --- a/cpp/src/aztec/plonk/transcript/manifest.hpp +++ b/cpp/src/aztec/plonk/transcript/manifest.hpp @@ -10,9 +10,8 @@ namespace transcript { * */ class Manifest { public: - /** - * ManifestEntry describes one piece of data that is used + * ManifestEntry describes one piece of data that is used * in a particular round of the protocol * */ struct ManifestEntry { @@ -23,15 +22,16 @@ class Manifest { }; /** - * The RoundManifest describes the data used in one round of the protocol - * and the challenge(s) created from that data. - * */ + * The RoundManifest describes the data used in one round of the protocol + * and the challenge(s) created from that data. + * */ struct RoundManifest { - /** + /** * @param element_names Data used in the round. * @param challenge_name The name of the challenge (alpha, beta, etc..) - * @param num_challenges_in The number of challenges to generate (sometimes we need more than one, e.g in permutation_widget) + * @param num_challenges_in The number of challenges to generate (sometimes we need more than one, e.g in + * permutation_widget) * @param map_challenges_in Whether to put elements in a challenge_map in the transcript. * */ RoundManifest(std::initializer_list element_names, @@ -46,9 +46,9 @@ class Manifest { /** * Checks if there is an element in the list with such name. - * + * * @param element_name The name to search for. - * + * * @return true if found, false if not. * */ bool includes_element(const std::string& element_name) @@ -66,6 +66,7 @@ class Manifest { size_t num_challenges; bool map_challenges; }; + Manifest(std::initializer_list _round_manifests) : round_manifests(_round_manifests) , num_rounds(round_manifests.size()){}; diff --git a/cpp/src/aztec/plonk/transcript/transcript.cpp b/cpp/src/aztec/plonk/transcript/transcript.cpp index af3806f8c0..565c3113f5 100644 --- a/cpp/src/aztec/plonk/transcript/transcript.cpp +++ b/cpp/src/aztec/plonk/transcript/transcript.cpp @@ -3,9 +3,10 @@ #include #include #include -#include +#include #include #include +#include #include #include #include @@ -31,9 +32,9 @@ std::array Keccak256Hasher::hash(std return result; } -std::array Blake2sHasher::hash(std::vector const& buffer) +std::array Blake3sHasher::hash(std::vector const& buffer) { - std::vector hash_result = blake2::blake2s(buffer); + std::vector hash_result = blake3::blake3s(buffer); std::array result; for (size_t i = 0; i < PRNG_OUTPUT_SIZE; ++i) { result[i] = hash_result[i]; @@ -116,14 +117,20 @@ void Transcript::add_element(const std::string& element_name, const std::vector< * */ void Transcript::apply_fiat_shamir(const std::string& challenge_name /*, const bool debug*/) { + // For reference, see the relevant manifest, which is defined in + // plonk/composer/[standard/turbo/ultra]_composer.hpp ASSERT(current_round <= manifest.get_num_rounds()); ASSERT(challenge_name == manifest.get_round_manifest(current_round).challenge); + const size_t num_challenges = manifest.get_round_manifest(current_round).num_challenges; if (num_challenges == 0) { ++current_round; return; } + // Combine the very last challenge from the previous fiat-shamir round (which is, inductively, a hash containing the + // manifest data of all previous rounds), plus the manifest data for this round, into a buffer. This buffer will + // ultimately be hashed, to form this round's fiat-shamir challenge(s). std::vector buffer; if (current_round > 0) { buffer.insert(buffer.end(), current_challenge.data.begin(), current_challenge.data.end()); @@ -145,9 +152,14 @@ void Transcript::apply_fiat_shamir(const std::string& challenge_name /*, const b base_hash = Keccak256Hasher::hash(buffer); break; } - case HashType::PedersenBlake2s: { + case HashType::PedersenBlake3s: { std::vector compressed_buffer = to_buffer(crypto::pedersen::compress_native(buffer)); - base_hash = Blake2sHasher::hash(compressed_buffer); + base_hash = Blake3sHasher::hash(compressed_buffer); + break; + } + case HashType::PlookupPedersenBlake3s: { + std::vector compressed_buffer = crypto::pedersen::lookup::compress_native(buffer); + base_hash = Blake3sHasher::hash(compressed_buffer); break; } default: { @@ -155,14 +167,20 @@ void Transcript::apply_fiat_shamir(const std::string& challenge_name /*, const b } } + // Depending on the settings, we might be able to chunk the bytes of a single hash across multiple challenges: const size_t challenges_per_hash = PRNG_OUTPUT_SIZE / num_challenge_bytes; for (size_t j = 0; j < challenges_per_hash; ++j) { if (j < num_challenges) { + // Each challenge still occupies PRNG_OUTPUT_SIZE number of bytes, but only num_challenge_bytes rhs bytes + // are nonzero. std::array challenge{}; std::copy(base_hash.begin() + (j * num_challenge_bytes), base_hash.begin() + (j + 1) * num_challenge_bytes, - challenge.begin() + (PRNG_OUTPUT_SIZE - num_challenge_bytes)); // HMM? + challenge.begin() + + (PRNG_OUTPUT_SIZE - + num_challenge_bytes)); // Left-pad the challenge with zeros, and then copy the next + // num_challange_bytes slice of the hash to the rhs of the challenge. round_challenges.push_back({ challenge }); } } @@ -170,11 +188,15 @@ void Transcript::apply_fiat_shamir(const std::string& challenge_name /*, const b std::vector rolling_buffer(base_hash.begin(), base_hash.end()); rolling_buffer.push_back(0); + // Compute how many hashes we need so that we have enough distinct chunks of 'random' bytes to distribute + // across the num_challenges. size_t num_hashes = (num_challenges / challenges_per_hash); if (num_hashes * challenges_per_hash != num_challenges) { ++num_hashes; } + for (size_t i = 1; i < num_hashes; ++i) { + // Compute hash_output = hash(base_hash, i); rolling_buffer[rolling_buffer.size() - 1] = static_cast(i); std::array hash_output{}; switch (hasher) { @@ -182,8 +204,12 @@ void Transcript::apply_fiat_shamir(const std::string& challenge_name /*, const b hash_output = Keccak256Hasher::hash(rolling_buffer); break; } - case HashType::PedersenBlake2s: { - hash_output = Blake2sHasher::hash(rolling_buffer); + case HashType::PedersenBlake3s: { + hash_output = Blake3sHasher::hash(rolling_buffer); + break; + } + case HashType::PlookupPedersenBlake3s: { + hash_output = Blake3sHasher::hash(rolling_buffer); break; } default: { @@ -191,6 +217,7 @@ void Transcript::apply_fiat_shamir(const std::string& challenge_name /*, const b } } for (size_t j = 0; j < challenges_per_hash; ++j) { + // Only produce as many challenges as we need. if (challenges_per_hash * i + j < num_challenges) { std::array challenge{}; std::copy(hash_output.begin() + (j * num_challenge_bytes), @@ -201,6 +228,8 @@ void Transcript::apply_fiat_shamir(const std::string& challenge_name /*, const b } } + // Remember the very last challenge, as it will be included in the buffer of the next fiat-shamir round (since this + // challenge is effectively a hash of _all_ previous rounds' manifest data). current_challenge = round_challenges[round_challenges.size() - 1]; challenges.insert({ challenge_name, round_challenges }); diff --git a/cpp/src/aztec/plonk/transcript/transcript.hpp b/cpp/src/aztec/plonk/transcript/transcript.hpp index 8cc08ca72e..1b20a519f5 100644 --- a/cpp/src/aztec/plonk/transcript/transcript.hpp +++ b/cpp/src/aztec/plonk/transcript/transcript.hpp @@ -17,21 +17,21 @@ struct Keccak256Hasher { static std::array hash(std::vector const& buffer); }; -struct Blake2sHasher { +struct Blake3sHasher { static constexpr size_t SECURITY_PARAMETER_SIZE = 16; static constexpr size_t PRNG_OUTPUT_SIZE = 32; static std::array hash(std::vector const& input); }; -enum HashType { Keccak256, PedersenBlake2s }; +enum HashType { Keccak256, PedersenBlake3s, PlookupPedersenBlake3s }; /** * Transcript is used by the Prover to store round values * and derive challenges. The verifier uses it to parse serialized * values and get the data and challenges back. * - * */ + */ class Transcript { static constexpr size_t PRNG_OUTPUT_SIZE = 32; struct challenge { @@ -48,7 +48,7 @@ class Transcript { * @param hash_type The hash to use for Fiat-Shamir. * @param challenge_bytes The number of bytes per challenge to generate. * - * */ + */ Transcript(const Manifest input_manifest, const HashType hash_type = HashType::Keccak256, const size_t challenge_bytes = 32) @@ -70,7 +70,7 @@ class Transcript { * @param hash_type The hash used for Fiat-Shamir * @param challenge_bytes The number of bytes per challenge to generate. * - * */ + */ Transcript(const std::vector& input_transcript, const Manifest input_manifest, const HashType hash_type = HashType::Keccak256, diff --git a/cpp/src/aztec/plonk/transcript/transcript_wrappers.hpp b/cpp/src/aztec/plonk/transcript/transcript_wrappers.hpp index 37f64d39c7..c1b4ce80de 100644 --- a/cpp/src/aztec/plonk/transcript/transcript_wrappers.hpp +++ b/cpp/src/aztec/plonk/transcript/transcript_wrappers.hpp @@ -6,18 +6,18 @@ namespace transcript { /** - * Transcript extended with functions for easy + * Transcript extended with functions for easy * field element setting/getting - * */ + */ class StandardTranscript : public Transcript { public: /** * Create a new standard transcript for Prover based on the manifest. - * - * @param input_manifes The manifest with round descriptions. + * + * @param input_manifest The manifest with round descriptions. * @param hash_type The hash to use for Fiat-Shamir. * @param challenge_bytes The number of bytes per challenge to generate. - * + * * */ StandardTranscript(const Manifest input_manifest, const HashType hash_type = HashType::Keccak256, @@ -25,14 +25,14 @@ class StandardTranscript : public Transcript { : Transcript(input_manifest, hash_type, challenge_bytes) {} /** - * Parse a serialized version of an input_transcript into a deserialized + * Parse a serialized version of an input_transcript into a deserialized * one based on the manifest. - * + * * @param input_transcript Serialized transcript. * @param input_manifest The manifest which governs the parsing. * @param hash_type The hash used for Fiat-Shamir * @param challenge_bytes The number of bytes per challenge to generate. - * + * * */ StandardTranscript(const std::vector& input_transcript, const Manifest input_manifest, @@ -55,11 +55,3 @@ class StandardTranscript : public Transcript { }; } // namespace transcript - // template class RecursiveTranscript : public TranscriptBase { - // public: - // void add_field_element(const std::string& element_name.const plonk::stdlib::field_t& element); - -// plonk::stdlib::field_t get_field_element(const std::string& element_name) const; - -// plonk::stdlib::field_t get_challenge_field_element(const std::string& challenge_name) const; -// } \ No newline at end of file diff --git a/cpp/src/aztec/polynomials/evaluation_domain.cpp b/cpp/src/aztec/polynomials/evaluation_domain.cpp index d3b93fd5ed..ea69277f8e 100644 --- a/cpp/src/aztec/polynomials/evaluation_domain.cpp +++ b/cpp/src/aztec/polynomials/evaluation_domain.cpp @@ -15,7 +15,6 @@ using namespace barretenberg; namespace { constexpr size_t MIN_GROUP_PER_THREAD = 4; - size_t compute_num_threads(const size_t size) { #ifndef NO_MULTITHREADING @@ -26,7 +25,7 @@ size_t compute_num_threads(const size_t size) if (size <= (num_threads * MIN_GROUP_PER_THREAD)) { num_threads = 1; } - + return num_threads; } @@ -68,6 +67,7 @@ evaluation_domain::evaluation_domain(const size_t domain_size, const size_t targ , domain_inverse(domain.invert()) , generator(fr::coset_generator(0)) , generator_inverse(fr::coset_generator(0).invert()) + , four_inverse(fr(4).invert()) , roots(nullptr) { ASSERT((1UL << log2_size) == size || (size == 0)); @@ -89,6 +89,7 @@ evaluation_domain::evaluation_domain(const evaluation_domain& other) , domain_inverse(domain.invert()) , generator(other.generator) , generator_inverse(other.generator_inverse) + , four_inverse(other.four_inverse) { ASSERT((1UL << log2_size) == size); ASSERT((1UL << log2_thread_size) == thread_size); @@ -124,6 +125,7 @@ evaluation_domain::evaluation_domain(evaluation_domain&& other) , domain_inverse(domain.invert()) , generator(other.generator) , generator_inverse(other.generator_inverse) + , four_inverse(other.four_inverse) { roots = other.roots; round_roots = std::move(other.round_roots); @@ -146,6 +148,7 @@ evaluation_domain& evaluation_domain::operator=(evaluation_domain&& other) fr::__copy(other.domain_inverse, domain_inverse); fr::__copy(other.generator, generator); fr::__copy(other.generator_inverse, generator_inverse); + fr::__copy(other.four_inverse, four_inverse); if (roots != nullptr) { aligned_free(roots); } diff --git a/cpp/src/aztec/polynomials/evaluation_domain.hpp b/cpp/src/aztec/polynomials/evaluation_domain.hpp index eb48a869db..4141c98294 100644 --- a/cpp/src/aztec/polynomials/evaluation_domain.hpp +++ b/cpp/src/aztec/polynomials/evaluation_domain.hpp @@ -19,6 +19,7 @@ class evaluation_domain { , domain_inverse(fr::zero()) , generator(fr::zero()) , generator_inverse(fr::zero()) + , four_inverse(fr::zero()) , roots(nullptr){}; evaluation_domain(const size_t domain_size, const size_t target_generator_size = 0); @@ -36,23 +37,28 @@ class evaluation_domain { const std::vector& get_round_roots() const { return round_roots; }; const std::vector& get_inverse_round_roots() const { return inverse_round_roots; } - size_t size; - size_t num_threads; + size_t size; // n, always a power of 2 + size_t num_threads; // num_threads * thread_size = size size_t thread_size; size_t log2_size; size_t log2_thread_size; size_t log2_num_threads; size_t generator_size; - barretenberg::fr root; - barretenberg::fr root_inverse; - barretenberg::fr domain; - barretenberg::fr domain_inverse; + barretenberg::fr root; // omega; the nth root of unity + barretenberg::fr root_inverse; // omega^{-1} + barretenberg::fr domain; // n; same as size + barretenberg::fr domain_inverse; // n^{-1} barretenberg::fr generator; barretenberg::fr generator_inverse; + barretenberg::fr four_inverse; private: - std::vector round_roots; + std::vector round_roots; // An entry for each of the log(n) rounds: each entry is a pointer to + // the subset of the roots of unity required for that fft round. + // E.g. round_roots[0] = [1, ω^(n/2 - 1)], + // round_roots[1] = [1, ω^(n/4 - 1), ω^(n/2 - 1), ω^(3n/4 - 1)] + // ... std::vector inverse_round_roots; barretenberg::fr* roots; diff --git a/cpp/src/aztec/polynomials/polynomial.cpp b/cpp/src/aztec/polynomials/polynomial.cpp index 66993b6e0b..523e67f090 100644 --- a/cpp/src/aztec/polynomials/polynomial.cpp +++ b/cpp/src/aztec/polynomials/polynomial.cpp @@ -328,6 +328,16 @@ void polynomial::fft(const evaluation_domain& domain) size = domain.size; } +void polynomial::partial_fft(const evaluation_domain& domain, fr constant, bool is_coset) +{ + if (domain.size > max_size) { + bump_memory(domain.size); + } + + polynomial_arithmetic::partial_fft(coefficients, domain, constant, is_coset); + size = domain.size; +} + void polynomial::coset_fft(const evaluation_domain& domain) { ASSERT(!empty()); @@ -433,4 +443,11 @@ fr polynomial::compute_barycentric_evaluation(const barretenberg::fr& z, const e return polynomial_arithmetic::compute_barycentric_evaluation(coefficients, domain.size, z, domain); } +fr polynomial::evaluate_from_fft(const evaluation_domain& large_domain, + const fr& z, + const evaluation_domain& small_domain) +{ + return polynomial_arithmetic::evaluate_from_fft(coefficients, large_domain, z, small_domain); +} + } // namespace barretenberg \ No newline at end of file diff --git a/cpp/src/aztec/polynomials/polynomial.hpp b/cpp/src/aztec/polynomials/polynomial.hpp index d08088e9ce..509bfb3580 100644 --- a/cpp/src/aztec/polynomials/polynomial.hpp +++ b/cpp/src/aztec/polynomials/polynomial.hpp @@ -100,7 +100,12 @@ class polynomial { barretenberg::fr evaluate(const barretenberg::fr& z, const size_t target_size) const; barretenberg::fr compute_barycentric_evaluation(const barretenberg::fr& z, const evaluation_domain& domain); + barretenberg::fr evaluate_from_fft(const evaluation_domain& large_domain, + const fr& z, + const evaluation_domain& small_domain); + void fft(const evaluation_domain& domain); + void partial_fft(const evaluation_domain& domain, fr constant = 1, bool is_coset = false); void coset_fft(const evaluation_domain& domain); void coset_fft(const evaluation_domain& domain, const evaluation_domain& large_domain, @@ -131,18 +136,22 @@ class polynomial { private: void free(); void zero_memory(const size_t zero_size); - const static size_t DEFAULT_SIZE_HINT = 1 << 12; - const static size_t DEFAULT_PAGE_SPILL = 20; + const static size_t DEFAULT_SIZE_HINT = 1 << 12; // DOCTODO: justify this number. + const static size_t DEFAULT_PAGE_SPILL = 20; // DOCTODO: explain this, or rename. void add_coefficient_internal(const barretenberg::fr& coefficient); void bump_memory(const size_t new_size); public: bool mapped; + /** + * Note: depending on the `representation` of this polynomial, `coefficients` will either contain 'monomial + * coefficients', 'lagrange evaluations', or 'coset evaluations'. + */ barretenberg::fr* coefficients; Representation representation; size_t initial_size; - size_t size; - size_t page_size; + size_t size; // This is the size() of the `coefficients` vector. + size_t page_size; // DOCTODO: what does 'page' mean? Explain this. size_t max_size; size_t allocated_pages; }; diff --git a/cpp/src/aztec/polynomials/polynomial_arithmetic.cpp b/cpp/src/aztec/polynomials/polynomial_arithmetic.cpp index e1d8491af1..161f989fc3 100644 --- a/cpp/src/aztec/polynomials/polynomial_arithmetic.cpp +++ b/cpp/src/aztec/polynomials/polynomial_arithmetic.cpp @@ -174,7 +174,6 @@ void fft_inner_parallel(std::vector coeffs, const fr&, const std::vector& root_table) { - // hmm // fr* scratch_space = (fr*)aligned_alloc(64, sizeof(fr) * domain.size); fr* scratch_space = get_scratch_space(domain.size); const size_t num_polys = coeffs.size(); @@ -311,7 +310,6 @@ void fft_inner_parallel(std::vector coeffs, void fft_inner_parallel( fr* coeffs, fr* target, const evaluation_domain& domain, const fr&, const std::vector& root_table) { - // hmm // fr* scratch_space = (fr*)aligned_alloc(64, sizeof(fr) * domain.size); #ifndef NO_MULTITHREADING #pragma omp parallel #endif @@ -403,28 +401,139 @@ void fft_inner_parallel( // Finally, we want to treat the final round differently from the others, // so that we can reduce out of our 'coarse' reduction and store the output in `coeffs` instead of // `scratch_space` - if (m != (domain.size >> 1)) { - for (size_t i = start; i < end; ++i) { - size_t k1 = (i & index_mask) << 1; - size_t j1 = i & block_mask; - temp = round_roots[j1] * target[k1 + j1 + m]; - target[k1 + j1 + m] = target[k1 + j1] - temp; - target[k1 + j1] += temp; - } - } else { - for (size_t i = start; i < end; ++i) { - size_t k1 = (i & index_mask) << 1; - size_t j1 = i & block_mask; - temp = round_roots[j1] * target[k1 + j1 + m]; - target[k1 + j1 + m] = target[k1 + j1] - temp; - target[k1 + j1] = target[k1 + j1] + temp; - } + for (size_t i = start; i < end; ++i) { + size_t k1 = (i & index_mask) << 1; + size_t j1 = i & block_mask; + temp = round_roots[j1] * target[k1 + j1 + m]; + target[k1 + j1 + m] = target[k1 + j1] - temp; + target[k1 + j1] += temp; } } } } } +void partial_fft_serial_inner(fr* coeffs, + fr* target, + const evaluation_domain& domain, + const std::vector& root_table) +{ + // We wish to compute a partial modified FFT of 2 rounds from given coefficients. + // We need a 2-round modified FFT for commiting to the 4n-sized quotient polynomial for + // the PLONK prover. + // + // We assume that the number of coefficients is a multiplicand of 4, since the domain size + // we use in PLONK would always be a power of 2, this is a reasonable assumption. + // Let n = N / 4 where N is the input domain size, we wish to compute + // R_{i,s} = \sum_{j=0}^{3} Y_{i + jn} * \omega^{(i + jn)(s + 1)} + // + // We should store the result in the following way: + // (R_{0,3} , R_{1,3}, R_{3,3}, ..., R_{n, 3}) {coefficients of X^0} + // (R_{0,2} , R_{1,2}, R_{3,2}, ..., R_{n, 2}) {coefficients of X^1} + // (R_{0,1} , R_{1,1}, R_{3,1}, ..., R_{n, 1}) {coefficients of X^2} + // (R_{0,0} , R_{1,0}, R_{3,0}, ..., R_{n, 0}) {coefficients of X^3} + size_t n = domain.size >> 2; + size_t index = 0; + size_t full_mask = domain.size - 1; + size_t m = domain.size >> 1; + size_t half_mask = m - 1; + const fr* round_roots = root_table[static_cast(numeric::get_msb(m)) - 1]; + size_t root_index = 0; + + // iterate for s = 0, 1, 2, 3 to compute R_{i,s} + for (size_t i = 0; i < n; ++i) { + for (size_t s = 0; s < 4; s++) { + target[(3 - s) * n + i] = 0; + for (size_t j = 0; j < 4; ++j) { + index = i + j * n; + root_index = (index * (s + 1)) & full_mask; + target[(3 - s) * n + i] += + (root_index < m ? fr::one() : -fr::one()) * coeffs[index] * round_roots[root_index & half_mask]; + } + } + } +} + +void partial_fft_parellel_inner( + fr* coeffs, const evaluation_domain& domain, const std::vector& root_table, fr constant, bool is_coset) +{ + // We wish to compute a partial modified FFT of 2 rounds from given coefficients. + // We need a 2-round modified FFT for commiting to the 4n-sized quotient polynomial for + // the PLONK prover. + // + // We assume that the number of coefficients is a multiplicand of 4, since the domain size + // we use in PLONK would always be a power of 2, this is a reasonable assumption. + // Let n = N / 4 where N is the input domain size, we wish to compute + // R_{i,s} = \sum_{j=0}^{3} Y_{i + jn} * \omega^{(i + jn)(s + 1)} + // + // Input `coeffs` is the evaluation form (FFT) of a polynomial. + // (Y_{0,0} , Y_{1,0}, Y_{3,0}, ..., Y_{n, 0}) + // (Y_{0,1} , Y_{1,1}, Y_{3,1}, ..., Y_{n, 1}) + // (Y_{0,2} , Y_{1,2}, Y_{3,2}, ..., Y_{n, 2}) + // (Y_{0,3} , Y_{1,3}, Y_{3,3}, ..., Y_{n, 3}) + // + // We should store the result in the following way: + // (R_{0,3} , R_{1,3}, R_{3,3}, ..., R_{n, 3}) {coefficients of X^0} + // (R_{0,2} , R_{1,2}, R_{3,2}, ..., R_{n, 2}) {coefficients of X^1} + // (R_{0,1} , R_{1,1}, R_{3,1}, ..., R_{n, 1}) {coefficients of X^2} + // (R_{0,0} , R_{1,0}, R_{3,0}, ..., R_{n, 0}) {coefficients of X^3} + + size_t n = domain.size >> 2; + size_t full_mask = domain.size - 1; + size_t m = domain.size >> 1; + size_t half_mask = m - 1; + const fr* round_roots = root_table[static_cast(numeric::get_msb(m)) - 1]; + + evaluation_domain small_domain(n); + + // iterate for s = 0, 1, 2, 3 to compute R_{i,s} + ITERATE_OVER_DOMAIN_START(small_domain); + fr temp[4]; + temp[0] = coeffs[i]; + temp[1] = coeffs[i + n]; + temp[2] = coeffs[i + 2 * n]; + temp[3] = coeffs[i + 3 * n]; + coeffs[i] = 0; + coeffs[i + n] = 0; + coeffs[i + 2 * n] = 0; + coeffs[i + 3 * n] = 0; + + size_t index, root_index; + fr temp_constant = constant; + fr root_multiplier = 1; + + for (size_t s = 0; s < 4; s++) { + for (size_t j = 0; j < 4; ++j) { + index = i + j * n; + root_index = (index * (s + 1)); + if (is_coset) { + root_index -= 4 * i; + } + root_index &= full_mask; + root_multiplier = round_roots[root_index & half_mask]; + if (root_index >= m) { + root_multiplier = -round_roots[root_index & half_mask]; + } + coeffs[(3 - s) * n + i] += root_multiplier * temp[j]; + } + if (is_coset) { + temp_constant *= domain.generator; + coeffs[(3 - s) * n + i] *= temp_constant; + } + } + ITERATE_OVER_DOMAIN_END; +} + +void partial_fft_serial(fr* coeffs, fr* target, const evaluation_domain& domain) +{ + partial_fft_serial_inner(coeffs, target, domain, domain.get_round_roots()); +} + +void partial_fft(fr* coeffs, const evaluation_domain& domain, fr constant, bool is_coset) +{ + partial_fft_parellel_inner(coeffs, domain, domain.get_round_roots(), constant, is_coset); +} + void fft(fr* coeffs, const evaluation_domain& domain) { fft_inner_parallel({ coeffs }, domain, domain.root, domain.get_round_roots()); @@ -699,89 +808,87 @@ fr evaluate(const std::vector coeffs, const fr& z, const size_t large_n) return r; } -// For L_1(X) = (X^{n} - 1 / (X - 1)) * (1 / n) -// Compute the 2n-fft of L_1(X) -// We can use this to compute the 2n-fft evaluations of any L_i(X). -// We can consider `l_1_coefficients` to be a 2n-sized vector of the evaluations of L_1(X), -// for all X = 2n'th roots of unity. -// To compute the vector for the 2n-fft transform of L_i(X), we perform a (2i)-left-shift of this vector +/** + * @brief Compute evaluations of lagrange polynomial L_1(X) on the specified domain + * + * @param l_1_coefficients + * @param src_domain + * @param target_domain + * @details Let the size of the target domain be k*n, where k is a power of 2. + * Evaluate L_1(X) = (X^{n} - 1 / (X - 1)) * (1 / n) at the k*n points X_i = w'^i.g, + * i = 0, 1,..., k*n-1, where w' is the target domain (kn'th) root of unity, and g is the + * source domain multiplicative generator. The evaluation domain is taken to be the coset + * w'^i.g, rather than just the kn'th roots, to avoid division by zero in L_1(X). + * The computation is done in three steps: + * Step 1) (Parallelized) Compute the evaluations of 1/denominator of L_1 at X_i using + * Montgomery batch inversion. + * Step 2) Compute the evaluations of the numerator of L_1 using the fact that (X_i)^n forms + * a subgroup of order k. + * Step 3) (Parallelized) Construct the evaluations of L_1 on X_i using the numerator and + * denominator evaluations from Steps 1 and 2. + * + * Note 1: Let w = n'th root of unity. When evaluated at the k*n'th roots of unity, the term + * X^{n} forms a subgroup of order k, since (w'^i)^n = w^{in/k} = w^{1/k}. Similarly, for X_i + * we have (X_i)^n = (w'^i.g)^n = w^{in/k}.g^n = w^{1/k}.g^n. + * For example, if k = 2: + * for even powers of w', X^{n} = w^{2in/2} = 1 + * for odd powers of w', X = w^{i}w^{n/2} -> X^{n} = w^{in}w^{n/2} = -1 + * The numerator term, therefore, can only take two values (for k = 2): + * For even indices: (X^{n} - 1)/n = (g^n - 1)/n + * For odd indices: (X^{n} - 1)/n = (-g^n - 1)/n + * + * Note 2: We can use the evaluations of L_1 to compute the k*n-fft evaluations of any L_i(X). + * We can consider `l_1_coefficients` to be a k*n-sized vector of the evaluations of L_1(X), + * for all X = k*n'th roots of unity. To compute the vector for the k*n-fft transform of + * L_i(X), we perform a (k*i)-left-shift of this vector. + */ void compute_lagrange_polynomial_fft(fr* l_1_coefficients, const evaluation_domain& src_domain, const evaluation_domain& target_domain) { - // L_1(X) = (X^{n} - 1 / (X - 1)) * (1 / n) - // when evaluated at the 2n'th roots of unity, the term X^{n} forms a subgroup of order 2 - // w = n'th root of unity - // w' = 2n'th root of unity = w^{1/2} - // for even powers of w', X^{n} = w^{2in/2} = 1 - // for odd powers of w', X = w^{i}w^{n/2} -> X^{n} = w^{in}w^{n/2} = -1 - - // We also want to compute fft using subgroup union a coset (the multiplicative generator g), so we're not dividing - // by zero - - // Step 1: compute the denominator for each evaluation: 1 / (X.g - 1) - // fr work_root; - fr multiplicand = target_domain.root; + // Step 1: Compute the 1/denominator for each evaluation: 1 / (X_i - 1) + fr multiplicand = target_domain.root; // kn'th root of unity w' #ifndef NO_MULTITHREADING #pragma omp parallel for #endif + // First compute X_i - 1, i = 0,...,kn-1 for (size_t j = 0; j < target_domain.num_threads; ++j) { const fr root_shift = multiplicand.pow(static_cast(j * target_domain.thread_size)); - fr work_root = src_domain.generator * root_shift; + fr work_root = src_domain.generator * root_shift; // g.(w')^{j*thread_size} size_t offset = j * target_domain.thread_size; for (size_t i = offset; i < offset + target_domain.thread_size; ++i) { - l_1_coefficients[i] = work_root - fr::one(); - work_root *= multiplicand; + l_1_coefficients[i] = work_root - fr::one(); // (w')^{j*thread_size + i}.g - 1 + work_root *= multiplicand; // (w')^{j*thread_size + i + 1} } } - // use Montgomery's trick to invert all of these at once + // Compute 1/(X_i - 1) using Montgomery batch inversion fr::batch_invert(l_1_coefficients, target_domain.size); - // next: compute numerator multiplicand: w'^{n}.g^n - // Here, w' is the primitive 2n'th root of unity - // and w is the primitive n'th root of unity - // i.e. w' = w^{1/2} - // The polynomial X^n, when evaluated at all 2n'th roots of unity, forms a subgroup of order 2. - // For even powers of w', X^n = w'^{2in} = w^{in} = 1 - // For odd powers of w', X^n = w'^{1 + 2in} = w^{n/2}w^{in} = w^{n/2} = -1 - - // The numerator term, therefore, can only take two values - // For even indices: (X^{n} - 1)/n = (g^n - 1)/n - // For odd indices: (X^{n} - 1)/n = (-g^n - 1)/n - - size_t log2_subgroup_size = target_domain.log2_size - src_domain.log2_size; - size_t subgroup_size = 1UL << log2_subgroup_size; + // Step 2: Compute numerator (1/n)*(X_i^n - 1) + // First compute X_i^n (which forms a multiplicative subgroup of order k) + size_t log2_subgroup_size = target_domain.log2_size - src_domain.log2_size; // log_2(k) + size_t subgroup_size = 1UL << log2_subgroup_size; // k ASSERT(target_domain.log2_size >= src_domain.log2_size); - fr* subgroup_roots = new fr[subgroup_size]; compute_multiplicative_subgroup(log2_subgroup_size, src_domain, &subgroup_roots[0]); - // Each element of `subgroup_roots[i]` contains some root wi^n - // want to compute (1/n)(wi^n - 1) + // Subtract 1 and divide by n to get the k elements (1/n)*(X_i^n - 1) for (size_t i = 0; i < subgroup_size; ++i) { subgroup_roots[i] -= fr::one(); subgroup_roots[i] *= src_domain.domain_inverse; } - // TODO: this is disgusting! Fix it fix it fix it fix it... - if (subgroup_size >= target_domain.thread_size) { - for (size_t i = 0; i < target_domain.size; i += subgroup_size) { - for (size_t j = 0; j < subgroup_size; ++j) { - l_1_coefficients[i + j] *= subgroup_roots[j]; - } - } - } else { + // Step 3: Construct L_1(X_i) by multiplying the 1/denominator evaluations in + // l_1_coefficients by the numerator evaluations in subgroup_roots + size_t subgroup_mask = subgroup_size - 1; #ifndef NO_MULTITHREADING #pragma omp parallel for #endif - for (size_t k = 0; k < target_domain.num_threads; ++k) { - size_t offset = k * target_domain.thread_size; - for (size_t i = offset; i < offset + target_domain.thread_size; i += subgroup_size) { - for (size_t j = 0; j < subgroup_size; ++j) { - l_1_coefficients[i + j] *= subgroup_roots[j]; - } - } + for (size_t i = 0; i < target_domain.num_threads; ++i) { + for (size_t j = 0; j < target_domain.thread_size; ++j) { + size_t eval_idx = i * target_domain.thread_size + j; + l_1_coefficients[eval_idx] *= subgroup_roots[eval_idx & subgroup_mask]; } } delete[] subgroup_roots; @@ -923,68 +1030,89 @@ fr compute_kate_opening_coefficients(const fr* src, fr* dest, const fr& z, const return f; } +/** + * @param zeta - the name given (in our code) to the evaluation challenge ʓ from the Plonk paper. + */ barretenberg::polynomial_arithmetic::lagrange_evaluations get_lagrange_evaluations( - const fr& z, const evaluation_domain& domain, const size_t num_roots_cut_out_of_vanishing_polynomial) + const fr& zeta, const evaluation_domain& domain, const size_t num_roots_cut_out_of_vanishing_polynomial) { - // compute Z_H*(z), l_start(z), l_{end}(z) + // Compute Z_H*(ʓ), l_start(ʓ), l_{end}(ʓ) // Note that as we modify the vanishing polynomial by cutting out some roots, we must simultaneously ensure that - // the lagrange polynomials we require would be l_1(z) and l_{n-k}(z) where k = + // the lagrange polynomials we require would be l_1(ʓ) and l_{n-k}(ʓ) where k = // num_roots_cut_out_of_vanishing_polynomial. For notational simplicity, we call l_1 as l_start and l_{n-k} as // l_end. // // NOTE: If in future, there arises a need to cut off more zeros, this method will not require any changes. // - fr z_pow = z; + fr z_pow_n = zeta; for (size_t i = 0; i < domain.log2_size; ++i) { - z_pow.self_sqr(); + z_pow_n.self_sqr(); } - fr numerator = z_pow - fr::one(); + fr numerator = z_pow_n - fr::one(); fr denominators[3]; - // compute denominator of Z_H*(z) - // (z - w^{n-1})(z - w^{n-2})...(z - w^{n - num_roots_cut_out_of_vanishing_poly}) + // Compute the denominator of Z_H*(ʓ) + // (ʓ - ω^{n-1})(ʓ - ω^{n-2})...(ʓ - ω^{n - num_roots_cut_out_of_vanishing_poly}) + // = (ʓ - ω^{ -1})(ʓ - ω^{ -2})...(ʓ - ω^{ - num_roots_cut_out_of_vanishing_poly}) fr work_root = domain.root_inverse; denominators[0] = fr::one(); for (size_t i = 0; i < num_roots_cut_out_of_vanishing_polynomial; ++i) { - denominators[0] *= (z - work_root); + denominators[0] *= (zeta - work_root); work_root *= domain.root_inverse; } // The expressions of the lagrange polynomials are: - // (X^n - 1) - // L_1(X) = ----------- - // X - 1 // - // L_{i}(X) = L_1(X.w^{-i}) - // (X^n - 1) - // => L_{n-k}(X) = L_1(X.w^{k-n}) = L_1(X.w^{k + 1}) = ---------------- - // (X.w^{k+1} - 1) + // ω^0.(X^n - 1) (X^n - 1) + // L_1(X) = --------------- = ----------- + // n.(X - ω^0) n.(X - 1) // - denominators[1] = z - fr::one(); + // Notice: here (in this comment), the index i of L_i(X) counts from 1 (not from 0). So L_1 corresponds to the + // _first_ root of unity ω^0, and not to the 1-th root of unity ω^1. + // + // + // ω^{i-1}.(X^n - 1) X^n - 1 X^n.(ω^{-i+1})^n - 1 + // L_{i}(X) = ------------------ = -------------------- = -------------------- = L_1(X.ω^{-i+1}) + // n.(X - ω^{i-1}) n.(X.ω^{-(i-1)} - 1) | n.(X.ω^{-i+1} - 1) + // | + // since (ω^{-i+1})^n = 1 trivially + // + // (X^n - 1) + // => L_{n-k}(X) = L_1(X.ω^{k-n+1}) = L_1(X.ω^{k+1}) = ----------------- + // n.(X.ω^{k+1} - 1) + // + denominators[1] = zeta - fr::one(); - // compute w^{num_roots_cut_out_of_vanishing_polynomial + 1} + // Compute ω^{num_roots_cut_out_of_vanishing_polynomial + 1} fr l_end_root = (num_roots_cut_out_of_vanishing_polynomial & 1) ? domain.root.sqr() : domain.root; for (size_t i = 0; i < num_roots_cut_out_of_vanishing_polynomial / 2; ++i) { l_end_root *= domain.root.sqr(); } - denominators[2] = (z * l_end_root) - fr::one(); + denominators[2] = (zeta * l_end_root) - fr::one(); fr::batch_invert(denominators, 3); barretenberg::polynomial_arithmetic::lagrange_evaluations result; - result.vanishing_poly = numerator * denominators[0]; - numerator = numerator * domain.domain_inverse; - result.l_start = numerator * denominators[1]; - result.l_end = numerator * denominators[2]; + result.vanishing_poly = numerator * denominators[0]; // (ʓ^n - 1) / (ʓ-ω^{-1}).(ʓ-ω^{-2})...(ʓ-ω^{-k}) =: Z_H*(ʓ) + numerator = numerator * domain.domain_inverse; // (ʓ^n - 1) / n + result.l_start = numerator * denominators[1]; // (ʓ^n - 1) / (n.(ʓ - 1)) =: L_1(ʓ) + result.l_end = numerator * denominators[2]; // (ʓ^n - 1) / (n.(ʓ.ω^{k+1} - 1)) =: L_{n-k}(ʓ) return result; } -// computes r = \sum_{i=0}^{num_coeffs}(L_i(z).f_i) -// start with L_1(z) = ((z^n - 1)/n).(1 / z - 1) -// L_i(z) = L_1(z.w^{1-i}) = ((z^n - 1) / n).(1 / z.w^{1-i} - 1) +// Computes r = \sum_{i=0}^{num_coeffs-1} (L_{i+1}(ʓ).f_i) +// +// (ʓ^n - 1) +// Start with L_1(ʓ) = --------- +// n.(ʓ - 1) +// +// ʓ^n - 1 +// L_i(z) = L_1(ʓ.ω^{1-i}) = ------------------ +// n.(ʓ.ω^{1-i)} - 1) +// fr compute_barycentric_evaluation(const fr* coeffs, const size_t num_coeffs, const fr& z, @@ -997,13 +1125,15 @@ fr compute_barycentric_evaluation(const fr* coeffs, numerator.self_sqr(); } numerator -= fr::one(); - numerator *= domain.domain_inverse; + numerator *= domain.domain_inverse; // (ʓ^n - 1) / n denominators[0] = z - fr::one(); - fr work_root = domain.root_inverse; + fr work_root = domain.root_inverse; // ω^{-1} for (size_t i = 1; i < num_coeffs; ++i) { - denominators[i] = work_root * z; - denominators[i] -= fr::one(); + denominators[i] = + work_root * z; // denominators[i] will correspond to L_[i+1] (since our 'commented maths' notation indexes + // L_i from 1). So ʓ.ω^{-i} = ʓ.ω^{1-(i+1)} is correct for L_{i+1}. + denominators[i] -= fr::one(); // ʓ.ω^{-i} - 1 work_root *= domain.root_inverse; } @@ -1012,11 +1142,14 @@ fr compute_barycentric_evaluation(const fr* coeffs, fr result = fr::zero(); for (size_t i = 0; i < num_coeffs; ++i) { - fr temp = coeffs[i] * denominators[i]; + fr temp = coeffs[i] * denominators[i]; // f_i * 1/(ʓ.ω^{-i} - 1) result = result + temp; } - result = result * numerator; + result = result * + numerator; // \sum_{i=0}^{num_coeffs-1} f_i * [ʓ^n - 1]/[n.(ʓ.ω^{-i} - 1)] + // = \sum_{i=0}^{num_coeffs-1} f_i * L_{i+1} + // (with our somewhat messy 'commented maths' convention that L_1 corresponds to the 0th coeff). aligned_free(denominators); @@ -1035,5 +1168,186 @@ void compress_fft(const fr* src, fr* dest, const size_t cur_size, const size_t c } } +fr evaluate_from_fft(const fr* poly_coset_fft, + const evaluation_domain& large_domain, + const fr& z, + const evaluation_domain& small_domain) +{ + size_t n = small_domain.size; + fr* small_poly_coset_fft = static_cast(aligned_alloc(64, sizeof(fr) * n)); + for (size_t i = 0; i < n; ++i) { + small_poly_coset_fft[i] = poly_coset_fft[4 * i]; + } + + fr zeta_by_g = z * large_domain.generator_inverse; + + const auto result = compute_barycentric_evaluation(small_poly_coset_fft, n, zeta_by_g, small_domain); + aligned_free(small_poly_coset_fft); + return result; +} + +// This function computes sum of all scalars in a given array. +fr compute_sum(const fr* src, const size_t n) +{ + fr result = 0; + for (size_t i = 0; i < n; ++i) { + result += src[i]; + } + return result; +} + +// This function computes the polynomial (x - a)(x - b)(x - c)... given n distinct roots (a, b, c, ...). +void compute_linear_polynomial_product(const fr* roots, fr* dest, const size_t n) +{ + fr* scratch_space = get_scratch_space(n); + memcpy((void*)scratch_space, (void*)roots, n * sizeof(fr)); + + dest[n] = 1; + dest[n - 1] = -compute_sum(scratch_space, n); + + fr temp; + fr constant = 1; + for (size_t i = 0; i < n - 1; ++i) { + temp = 0; + for (size_t j = 0; j < n - 1 - i; ++j) { + scratch_space[j] = roots[j] * compute_sum(&scratch_space[j + 1], n - 1 - i - j); + temp += scratch_space[j]; + } + dest[n - 2 - i] = temp * constant; + constant *= fr::neg_one(); + } +} + +fr compute_linear_polynomial_product_evaluation(const fr* roots, const fr z, const size_t n) +{ + fr result = 1; + for (size_t i = 0; i < n; ++i) { + result *= (z - roots[i]); + } + return result; +} + +void fft_linear_polynomial_product( + const fr* roots, fr* dest, const size_t n, const evaluation_domain& domain, const bool is_coset) +{ + size_t m = domain.size >> 1; + const fr* round_roots = domain.get_round_roots()[static_cast(numeric::get_msb(m)) - 1]; + + fr current_root = 0; + for (size_t i = 0; i < m; ++i) { + current_root = round_roots[i]; + current_root *= (is_coset ? domain.generator : 1); + dest[i] = 1; + dest[i + m] = 1; + for (size_t j = 0; j < n; ++j) { + dest[i] *= (current_root - roots[j]); + dest[i + m] *= (-current_root - roots[j]); + } + } +} + +void compute_interpolation(const fr* src, fr* dest, const fr* evaluation_points, const size_t n) +{ + std::vector local_roots; + fr local_polynomial[n]; + fr denominator = 1; + fr multiplicand; + fr temp_dest[n]; + + if (n == 1) { + temp_dest[0] = src[0]; + return; + } + + // Initialize dest + for (size_t i = 0; i < n; ++i) { + temp_dest[i] = 0; + } + + for (size_t i = 0; i < n; ++i) { + + // fill in local roots + denominator = 1; + for (size_t j = 0; j < n; ++j) { + if (j == i) { + continue; + } + local_roots.push_back(evaluation_points[j]); + denominator *= (evaluation_points[i] - evaluation_points[j]); + } + + // bring local roots to coefficient form + compute_linear_polynomial_product(&local_roots[0], local_polynomial, n - 1); + + // store the resulting coefficients + multiplicand = src[i] / denominator; + for (size_t j = 0; j < n; ++j) { + temp_dest[j] += multiplicand * local_polynomial[j]; + } + + // clear up local roots + local_roots.clear(); + } + + memcpy((void*)dest, (void*)temp_dest, n * sizeof(fr)); +} + +void compute_efficient_interpolation(const fr* src, fr* dest, const fr* evaluation_points, const size_t n) +{ + /* + We use Lagrange technique to compute polynomial interpolation. + Given: (x_i, y_i) for i ∈ {0, 1, ..., n} =: [n] + Compute function f(X) such that f(x_i) = y_i for all i ∈ [n]. + (X - x1)(X - x2)...(X - xn) (X - x0)(X - x2)...(X - xn) + F(X) = y0-------------------------------- + y1---------------------------------- + ... + (x0 - x_1)(x0 - x_2)...(x0 - xn) (x1 - x_0)(x1 - x_2)...(x1 - xn) + We write this as: + [ yi ] + F(X) = N(X) * |∑_i --------------- | + [ (X - xi) * di ] + where: + N(X) = ∏_{i \in [n]} (X - xi), + di = ∏_{j != i} (xi - xj) + For division of N(X) by (X - xi), we use the same trick that was used in compute_opening_polynomial() + function in the kate commitment scheme. + */ + fr numerator_polynomial[n + 1]; + polynomial_arithmetic::compute_linear_polynomial_product(evaluation_points, numerator_polynomial, n); + + fr roots_and_denominators[2 * n]; + fr temp_src[n]; + for (size_t i = 0; i < n; ++i) { + roots_and_denominators[i] = -evaluation_points[i]; + temp_src[i] = src[i]; + dest[i] = 0; + + // compute constant denominator + roots_and_denominators[n + i] = 1; + for (size_t j = 0; j < n; ++j) { + if (j == i) { + continue; + } + roots_and_denominators[n + i] *= (evaluation_points[i] - evaluation_points[j]); + } + } + + fr::batch_invert(roots_and_denominators, 2 * n); + + fr z, multiplier; + fr temp_dest[n]; + for (size_t i = 0; i < n; ++i) { + z = roots_and_denominators[i]; + multiplier = temp_src[i] * roots_and_denominators[n + i]; + temp_dest[0] = multiplier * numerator_polynomial[0]; + temp_dest[0] *= z; + dest[0] += temp_dest[0]; + for (size_t j = 1; j < n; ++j) { + temp_dest[j] = multiplier * numerator_polynomial[j] - temp_dest[j - 1]; + temp_dest[j] *= z; + dest[j] += temp_dest[j]; + } + } +} + } // namespace polynomial_arithmetic } // namespace barretenberg diff --git a/cpp/src/aztec/polynomials/polynomial_arithmetic.hpp b/cpp/src/aztec/polynomials/polynomial_arithmetic.hpp index b471e53c61..983203cdf6 100644 --- a/cpp/src/aztec/polynomials/polynomial_arithmetic.hpp +++ b/cpp/src/aztec/polynomials/polynomial_arithmetic.hpp @@ -49,17 +49,30 @@ void ifft_with_constant(fr* coeffs, const evaluation_domain& domain, const fr& v void coset_ifft(fr* coeffs, const evaluation_domain& domain); void coset_ifft(std::vector coeffs, const evaluation_domain& domain); +void partial_fft_serial_inner(fr* coeffs, + fr* target, + const evaluation_domain& domain, + const std::vector& root_table); +void partial_fft_parellel_inner(fr* coeffs, + const evaluation_domain& domain, + const std::vector& root_table, + fr constant = 1, + bool is_coset = false); + +void partial_fft_serial(fr* coeffs, fr* target, const evaluation_domain& domain); +void partial_fft(fr* coeffs, const evaluation_domain& domain, fr constant = 1, bool is_coset = false); + void add(const fr* a_coeffs, const fr* b_coeffs, fr* r_coeffs, const evaluation_domain& domain); void sub(const fr* a_coeffs, const fr* b_coeffs, fr* r_coeffs, const evaluation_domain& domain); void mul(const fr* a_coeffs, const fr* b_coeffs, fr* r_coeffs, const evaluation_domain& domain); // For L_1(X) = (X^{n} - 1 / (X - 1)) * (1 / n) -// Compute the 2n-fft of L_1(X) -// We can use this to compute the 2n-fft evaluations of any L_i(X). -// We can consider `l_1_coefficients` to be a 2n-sized vector of the evaluations of L_1(X), -// for all X = 2n'th roots of unity. -// To compute the vector for the 2n-fft transform of L_i(X), we perform a (2i)-left-shift of this vector +// Compute the size k*n-fft of L_1(X), where k is determined by the target domain (e.g. large_domain -> 4*n) +// We can use this to compute the k*n-fft evaluations of any L_i(X). +// We can consider `l_1_coefficients` to be a k*n-sized vector of the evaluations of L_1(X), +// for all X = k*n'th roots of unity. +// To compute the vector for the k*n-fft transform of L_i(X), we perform a (k*i)-left-shift of this vector void compute_lagrange_polynomial_fft(fr* l_1_coefficients, const evaluation_domain& src_domain, const evaluation_domain& target_domain); @@ -84,5 +97,35 @@ fr compute_barycentric_evaluation(const fr* coeffs, const evaluation_domain& domain); // Convert an fft with `current_size` point evaluations, to one with `current_size >> compress_factor` point evaluations void compress_fft(const fr* src, fr* dest, const size_t current_size, const size_t compress_factor); + +fr evaluate_from_fft(const fr* poly_coset_fft, + const evaluation_domain& large_domain, + const fr& z, + const evaluation_domain& small_domain); + +// This function computes sum of all scalars in a given array. +fr compute_sum(const fr* src, const size_t n); + +// This function computes the polynomial (x - a)(x - b)(x - c)... given n distinct roots (a, b, c, ...). +void compute_linear_polynomial_product(const fr* roots, fr* dest, const size_t n); + +// This function evaluates the polynomial (x - a)(x - b)(x - c)... given n distinct roots (a, b, c, ...) +// at x = z. +fr compute_linear_polynomial_product_evaluation(const fr* roots, const fr z, const size_t n); + +// This function computes the lagrange (or coset-lagrange) form of the polynomial (x - a)(x - b)(x - c)... +// given n distinct roots (a, b, c, ...). +void fft_linear_polynomial_product( + const fr* roots, fr* dest, const size_t n, const evaluation_domain& domain, const bool is_coset = false); + +// This function interpolates from points {(z_1, f(z_1)), (z_2, f(z_2)), ...}. +// `src` contains {f(z_1), f(z_2), ...} +void compute_interpolation(const fr* src, fr* dest, const fr* evaluation_points, const size_t n); + +// This function interpolates from points {(z_1, f(z_1)), (z_2, f(z_2)), ...} +// using a single scalar inversion and Lagrange polynomial interpolation. +// `src` contains {f(z_1), f(z_2), ...} +void compute_efficient_interpolation(const fr* src, fr* dest, const fr* evaluation_points, const size_t n); + } // namespace polynomial_arithmetic } // namespace barretenberg diff --git a/cpp/src/aztec/polynomials/polynomial_arithmetic.test.cpp b/cpp/src/aztec/polynomials/polynomial_arithmetic.test.cpp index 6298767f5c..503f7b2572 100644 --- a/cpp/src/aztec/polynomials/polynomial_arithmetic.test.cpp +++ b/cpp/src/aztec/polynomials/polynomial_arithmetic.test.cpp @@ -295,6 +295,9 @@ TEST(polynomials, fft_coset_ifft_cross_consistency) } } +/** + * @brief Test function compute_lagrange_polynomial_fft() on medium domain (size 2 * n) + */ TEST(polynomials, compute_lagrange_polynomial_fft) { size_t n = 256; @@ -353,6 +356,80 @@ TEST(polynomials, compute_lagrange_polynomial_fft) } } +/** + * @brief Test function compute_lagrange_polynomial_fft() on large domain (size 4 * n) + * @details Compute L_1 in monomial form by 1) compute_lagrange_polynomial_fft() then + * 2) coset_ifft. Evaluate L_1 at the shifted random point z*ω^2. Show that this gives + * the same result as 1) manually shifting coset FFT of L_1, then 2) calling + * coset_ifft and evaluating the result (L_{n-1} monomial) at z. + * Finally, verify that L_1 and L_{n-1} have indeed been computed correctly by checking + * that they evaluate to one at the correct location and are zero elsewhere. + */ +TEST(polynomials, compute_lagrange_polynomial_fft_large_domain) +{ + size_t n = 256; // size of small_domain + size_t M = 4; // size of large_domain == M * n + evaluation_domain small_domain = evaluation_domain(n); + evaluation_domain large_domain = evaluation_domain(M * n); + small_domain.compute_lookup_table(); + large_domain.compute_lookup_table(); + + fr l_1_coefficients[M * n]; + // Scratch memory needs additional space (M*2) to allow for 'shift' later on + fr scratch_memory[M * n + (M * 2)]; + for (size_t i = 0; i < M * n; ++i) { + l_1_coefficients[i] = fr::zero(); + scratch_memory[i] = fr::zero(); + } + // Compute FFT on target domain + polynomial_arithmetic::compute_lagrange_polynomial_fft(l_1_coefficients, small_domain, large_domain); + + // Copy L_1 FFT into scratch space and shift it to get FFT of L_{n-1} + polynomial_arithmetic::copy_polynomial(l_1_coefficients, scratch_memory, M * n, M * n); + // Manually 'shift' L_1 FFT in scratch memory by M*2 + for (size_t i = 0; i < M * 2; ++i) { + fr::__copy(scratch_memory[i], scratch_memory[M * n + i]); + } + fr* l_n_minus_one_coefficients = &scratch_memory[M * 2]; + + // Recover monomial forms of L_1 and L_{n-1} (from manually shifted L_1 FFT) + polynomial_arithmetic::coset_ifft(l_1_coefficients, large_domain); + polynomial_arithmetic::coset_ifft(l_n_minus_one_coefficients, large_domain); + + // Compute shifted random eval point z*ω^2 + fr z = fr::random_element(); + fr shifted_z; // z*ω^2 + shifted_z = z * small_domain.root; // z*ω + shifted_z *= small_domain.root; // z*ω^2 + + // Compute L_1(z_shifted) and L_{n-1}(z) + fr eval = polynomial_arithmetic::evaluate(l_1_coefficients, shifted_z, small_domain.size); + fr shifted_eval = polynomial_arithmetic::evaluate(l_n_minus_one_coefficients, z, small_domain.size); + + // Check L_1(z_shifted) = L_{n-1}(z) + EXPECT_EQ((eval == shifted_eval), true); + + // Compute evaluation forms of L_1 and L_{n-1} and check that they have + // a one in the right place and zeros elsewhere + polynomial_arithmetic::fft(l_1_coefficients, small_domain); + polynomial_arithmetic::fft(l_n_minus_one_coefficients, small_domain); + + EXPECT_EQ((l_1_coefficients[0] == fr::one()), true); + + for (size_t i = 1; i < n; ++i) { + EXPECT_EQ((l_1_coefficients[i] == fr::zero()), true); + } + + EXPECT_EQ(l_n_minus_one_coefficients[n - 2] == fr::one(), true); + + for (size_t i = 0; i < n; ++i) { + if (i == (n - 2)) { + continue; + } + EXPECT_EQ((l_n_minus_one_coefficients[i] == fr::zero()), true); + } +} + TEST(polynomials, divide_by_pseudo_vanishing_polynomial) { size_t n = 256; @@ -577,7 +654,6 @@ TEST(polynomials, divide_by_vanishing_polynomial) polynomial R_copy(2 * n, 2 * n); R_copy = R; - // polynomial R_copy(R); polynomial_arithmetic::divide_by_pseudo_vanishing_polynomial({ &R[0] }, small_domain, large_domain, 3); R.coset_ifft(large_domain); @@ -598,4 +674,4 @@ TEST(polynomials, divide_by_vanishing_polynomial) fr Z_H_vanishing_eval = (z.pow(16) - 1); rhs = r_eval * Z_H_vanishing_eval; EXPECT_EQ((lhs == rhs), false); -} \ No newline at end of file +} diff --git a/cpp/src/aztec/rollup/CMakeLists.txt b/cpp/src/aztec/rollup/CMakeLists.txt index 56600568f8..5045b4231a 100644 --- a/cpp/src/aztec/rollup/CMakeLists.txt +++ b/cpp/src/aztec/rollup/CMakeLists.txt @@ -1,4 +1,4 @@ -if(NOT WASM) +if(NOT WASM) link_libraries(leveldb) if (NOT FUZZING) add_subdirectory(db_cli) @@ -8,5 +8,4 @@ if(NOT WASM) endif() endif() -add_subdirectory(proofs) -add_subdirectory(ci_failsafe) \ No newline at end of file +add_subdirectory(proofs) \ No newline at end of file diff --git a/cpp/src/aztec/rollup/ci_failsafe/CMakeLists.txt b/cpp/src/aztec/rollup/ci_failsafe/CMakeLists.txt deleted file mode 100644 index 2f84ba21ad..0000000000 --- a/cpp/src/aztec/rollup/ci_failsafe/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -barretenberg_module(ci_failsafe) \ No newline at end of file diff --git a/cpp/src/aztec/rollup/ci_failsafe/failsafe.test.cpp b/cpp/src/aztec/rollup/ci_failsafe/failsafe.test.cpp deleted file mode 100644 index 6e9cba7b2d..0000000000 --- a/cpp/src/aztec/rollup/ci_failsafe/failsafe.test.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "../constants.hpp" -#include - -/** - * @brief This test detects if the circuit change expected constant is disabled. It is used so that developers can - * safely change stuff in circuits and run tests in PRs, but there is one last failsafe that doesn't allow them to merge - * it. - * - */ -TEST(ci_failsafe, detect_circuit_change_disabled) -{ - EXPECT_EQ(rollup::circuit_gate_count::is_circuit_change_expected, 0); -} \ No newline at end of file diff --git a/cpp/src/aztec/rollup/constants.hpp b/cpp/src/aztec/rollup/constants.hpp index 9275e9c82a..31ae0b4581 100644 --- a/cpp/src/aztec/rollup/constants.hpp +++ b/cpp/src/aztec/rollup/constants.hpp @@ -23,49 +23,6 @@ constexpr size_t ALIAS_HASH_BIT_LENGTH = 224; constexpr uint32_t NUM_BRIDGE_CALLS_PER_BLOCK = 32; constexpr uint32_t NUM_INTERACTION_RESULTS_PER_BLOCK = 32; -namespace circuit_gate_count { - -/* -The boolean is_circuit_change_expected should be set to 0 by default. When there is an expected circuit change, the -developer can quickly check whether the circuit gate counts are in allowed range i.e., below the next power of two -limit, by setting it to one. However, while merging the corresponding PR, the developer should set -is_circuit_change_expected to zero and change the modified circuit gate counts accordingly. -*/ -constexpr bool is_circuit_change_expected = 0; -/* The below constants are only used for regression testing; to identify accidental changes to circuit - constraints. They need to be changed when there is a circuit change. */ -constexpr uint32_t ACCOUNT = 23958; -constexpr uint32_t JOIN_SPLIT = 64000; -constexpr uint32_t CLAIM = 22684; -constexpr uint32_t ROLLUP = 1173221; -constexpr uint32_t ROOT_ROLLUP = 5481327; -constexpr uint32_t ROOT_VERIFIER = 7435892; -}; // namespace circuit_gate_count - -namespace circuit_gate_next_power_of_two { -/* The below constants are used in tests to detect undesirable circuit changes. They should not be changed unless we -want to exceed the next power of two limit. */ -constexpr uint32_t ACCOUNT = 32768; -constexpr uint32_t JOIN_SPLIT = 65536; -constexpr uint32_t CLAIM = 32768; -constexpr uint32_t ROLLUP = 2097152; -constexpr uint32_t ROOT_ROLLUP = 8388608; -constexpr uint32_t ROOT_VERIFIER = 8388608; -}; // namespace circuit_gate_next_power_of_two - -namespace circuit_vk_hash { -/* These below constants are only used for regression testing; to identify accidental changes to circuit - constraints. They need to be changed when there is a circuit change. Note that they are written in the reverse order - to comply with the from_buffer<>() method. */ -constexpr auto ACCOUNT = uint256_t(0x78ebf096ab73e440, 0xaa1dc7c26a125f6e, 0x488a97e465b96964, 0xf9d3e501b89bf466); -constexpr auto JOIN_SPLIT = uint256_t(0x5e67a4a4503ebf25, 0xb3c070c061e76d1a, 0xb18c6c6a5bcad5fb, 0xe0d5f46cafb33ecf); -constexpr auto CLAIM = uint256_t(0x878301ebba40ab60, 0x931466762c62d661, 0x40aad71ec3496905, 0x9f47aaa109759d0a); -constexpr auto ROLLUP = uint256_t(0x160731cc44173fdc, 0x6a6d55e46bf198bd, 0x9ce1d4608ae26fb0, 0x865ced5c16cb6152); -constexpr auto ROOT_ROLLUP = uint256_t(0xd77e82eae9e6efc7, 0x2b5ddf767012a4cf, 0x8b5982bb3d64616f, 0x20b515f5a9c78048); -constexpr auto ROOT_VERIFIER = - uint256_t(0x8e8313d6015ca626, 0x62ccf70b81c4e099, 0x33bee0072a20f36a, 0x44bd24daa009cd59); -}; // namespace circuit_vk_hash - namespace ProofIds { enum { PADDING = 0, DEPOSIT = 1, WITHDRAW = 2, SEND = 3, ACCOUNT = 4, DEFI_DEPOSIT = 5, DEFI_CLAIM = 6 }; }; diff --git a/cpp/src/aztec/rollup/proofs/CMakeLists.txt b/cpp/src/aztec/rollup/proofs/CMakeLists.txt index 1d6bf27430..b317f1319f 100644 --- a/cpp/src/aztec/rollup/proofs/CMakeLists.txt +++ b/cpp/src/aztec/rollup/proofs/CMakeLists.txt @@ -3,6 +3,7 @@ add_subdirectory(claim) add_subdirectory(inner_proof_data) add_subdirectory(join_split) add_subdirectory(notes) +add_subdirectory(mock) if(NOT (WASM OR FUZZING)) add_subdirectory(rollup) diff --git a/cpp/src/aztec/rollup/proofs/account/account.cpp b/cpp/src/aztec/rollup/proofs/account/account.cpp index 03173f5c27..9b53d15e87 100644 --- a/cpp/src/aztec/rollup/proofs/account/account.cpp +++ b/cpp/src/aztec/rollup/proofs/account/account.cpp @@ -4,8 +4,6 @@ #include "../notes/constants.hpp" #include "../add_zero_public_inputs.hpp" #include -#include -#include #include #include #include @@ -18,7 +16,7 @@ namespace proofs { namespace account { using namespace plonk; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; using namespace notes::circuit::account; static std::shared_ptr proving_key; @@ -96,7 +94,7 @@ void account_circuit(Composer& composer, account_tx const& tx) // Check signature. { - bool composerAlreadyFailed = composer.failed; + bool composerAlreadyFailed = composer.failed(); std::vector to_compress = { alias_hash.value, account_public_key.x, new_account_public_key.x, @@ -106,9 +104,9 @@ void account_circuit(Composer& composer, account_tx const& tx) nullifier_2 }; const byte_array_ct message = pedersen::compress(to_compress); stdlib::schnorr::verify_signature(message, signer, signature); - if (composer.failed && !composerAlreadyFailed) { + if (composer.failed() && !composerAlreadyFailed) { // only assign this error if an error hasn't already been assigned. - composer.err = "verify signature failed"; + composer.set_err("verify signature failed"); } } @@ -186,6 +184,7 @@ void init_proving_key(std::shared_ptr const& crs tx.new_signing_pub_key_2 = grumpkin::g1::affine_one; tx.signing_pub_key = grumpkin::g1::affine_one; tx.account_note_path.resize(32); + tx.signature = { { 1 }, { 1 } }; Composer composer(crs_factory); account_circuit(composer, tx); @@ -217,7 +216,9 @@ void init_verification_key(std::shared_ptr const // Patch the 'nothing' reference string fed to init_proving_key. proving_key->reference_string = crs_factory->get_prover_crs(proving_key->n + 1); } - verification_key = waffle::turbo_composer::compute_verification_key(proving_key, crs_factory->get_verifier_crs()); + + verification_key = + plonk::stdlib::types::Composer::compute_verification_key_base(proving_key, crs_factory->get_verifier_crs()); } void init_verification_key(std::shared_ptr const& crs, @@ -231,8 +232,8 @@ UnrolledProver new_account_prover(account_tx const& tx, bool mock) Composer composer(proving_key, nullptr); account_circuit(composer, tx); - if (composer.failed) { - std::string error = format("composer logic failed: ", composer.err); + if (composer.failed()) { + std::string error = format("composer logic failed: ", composer.err()); throw_or_abort(error); } number_of_gates = composer.get_num_gates(); @@ -254,8 +255,8 @@ bool verify_proof(waffle::plonk_proof const& proof) UnrolledVerifier verifier(verification_key, Composer::create_unrolled_manifest(verification_key->num_public_inputs)); - std::unique_ptr> kate_commitment_scheme = - std::make_unique>(); + std::unique_ptr> kate_commitment_scheme = + std::make_unique>(); verifier.commitment_scheme = std::move(kate_commitment_scheme); return verifier.verify_proof(proof); diff --git a/cpp/src/aztec/rollup/proofs/account/account.hpp b/cpp/src/aztec/rollup/proofs/account/account.hpp index 7f2a0c4f8b..62c9efde15 100644 --- a/cpp/src/aztec/rollup/proofs/account/account.hpp +++ b/cpp/src/aztec/rollup/proofs/account/account.hpp @@ -1,14 +1,14 @@ #pragma once #include "account_tx.hpp" #include -#include -#include +#include +#include namespace rollup { namespace proofs { namespace account { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; void init_proving_key(std::shared_ptr const& crs_factory, bool mock); diff --git a/cpp/src/aztec/rollup/proofs/account/account.test.cpp b/cpp/src/aztec/rollup/proofs/account/account.test.cpp index 12fca78d8f..f11cd2b9b3 100644 --- a/cpp/src/aztec/rollup/proofs/account/account.test.cpp +++ b/cpp/src/aztec/rollup/proofs/account/account.test.cpp @@ -12,8 +12,15 @@ #include #include +#ifdef CI +constexpr bool CIRCUIT_CHANGE_EXPECTED = false; +#else +// During development, if the circuit vk hash/gate count is expected to change, set the following to true. +constexpr bool CIRCUIT_CHANGE_EXPECTED = false; +#endif + using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; using namespace plonk::stdlib::merkle_tree; using namespace rollup; using namespace rollup::proofs; @@ -145,10 +152,10 @@ class account_tests : public ::testing::Test { { Composer composer(get_proving_key(), nullptr); account_circuit(composer, tx); - if (composer.failed) { - info("Circuit logic failed: " + composer.err); + if (composer.failed()) { + info("Circuit logic failed: " + composer.err()); } - return { !composer.failed, composer.err }; + return { !composer.failed(), composer.err() }; } rollup::fixtures::user_context alice; @@ -355,27 +362,25 @@ TEST_F(account_tests, test_create_account_full_proof_and_detect_circuit_change) EXPECT_EQ(data.allow_chain, uint256_t(0)); EXPECT_TRUE(verify_proof(proof)); + // The below part detects change in the account circuit + constexpr uint32_t CIRCUIT_GATE_COUNT = 23958; + constexpr uint32_t GATES_NEXT_POWER_OF_TWO = 32768; + const uint256_t VK_HASH("e0a3d137687cf0d8e0fd1975351051a63592ae71dcd7649399a3590fb411cc59"); + size_t number_of_gates_acc = get_number_of_gates(); auto vk_hash_acc = get_verification_key()->sha256_hash(); - // If the below assertions fail, consider changing the variable is_circuit_change_expected to 1 in - // rollup/constants.hpp and see if atleast the next power of two limit is not exceeded. Please change the constant - // values accordingly and set is_circuit_change_expected to 0 in rollup/constants.hpp before merging. - if (!(circuit_gate_count::is_circuit_change_expected)) { - EXPECT_EQ(number_of_gates_acc, circuit_gate_count::ACCOUNT) - << "The gate count for the account circuit is changed."; - EXPECT_EQ(from_buffer(vk_hash_acc), circuit_vk_hash::ACCOUNT) + + if (!CIRCUIT_CHANGE_EXPECTED) { + EXPECT_EQ(number_of_gates_acc, CIRCUIT_GATE_COUNT) << "The gate count for the account circuit is changed."; + EXPECT_EQ(from_buffer(vk_hash_acc), VK_HASH) << "The verification key hash for the account circuit is changed: " << from_buffer(vk_hash_acc); - // For the next power of two limit, we need to consider that we reserve four gates for adding - // randomness/zero-knowledge - EXPECT_LE(number_of_gates_acc, - circuit_gate_next_power_of_two::ACCOUNT - waffle::ComposerBase::NUM_RESERVED_GATES) - << "You have exceeded the next power of two limit for the account circuit."; - } else { - EXPECT_LE(number_of_gates_acc, - circuit_gate_next_power_of_two::ACCOUNT - waffle::ComposerBase::NUM_RESERVED_GATES) - << "You have exceeded the next power of two limit for the account circuit."; } + + // For the next power of two limit, we need to consider that we reserve four gates for adding + // randomness/zero-knowledge + EXPECT_LE(number_of_gates_acc, GATES_NEXT_POWER_OF_TWO - waffle::ComposerBase::NUM_RESERVED_GATES) + << "You have exceeded the next power of two limit for the account circuit."; } TEST_F(account_tests, test_migrate_account_full_proof) diff --git a/cpp/src/aztec/rollup/proofs/account/account_tx.hpp b/cpp/src/aztec/rollup/proofs/account/account_tx.hpp index 4c9dbbc097..3df812384d 100644 --- a/cpp/src/aztec/rollup/proofs/account/account_tx.hpp +++ b/cpp/src/aztec/rollup/proofs/account/account_tx.hpp @@ -1,7 +1,7 @@ #pragma once #include #include -#include +#include namespace rollup { namespace proofs { diff --git a/cpp/src/aztec/rollup/proofs/account/account_tx.test.cpp b/cpp/src/aztec/rollup/proofs/account/account_tx.test.cpp index e2e0909342..74c49912d6 100644 --- a/cpp/src/aztec/rollup/proofs/account/account_tx.test.cpp +++ b/cpp/src/aztec/rollup/proofs/account/account_tx.test.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include using namespace barretenberg; diff --git a/cpp/src/aztec/rollup/proofs/account/c_bind.cpp b/cpp/src/aztec/rollup/proofs/account/c_bind.cpp index 7d8d052e77..b917813f48 100644 --- a/cpp/src/aztec/rollup/proofs/account/c_bind.cpp +++ b/cpp/src/aztec/rollup/proofs/account/c_bind.cpp @@ -6,12 +6,12 @@ #include #include #include -#include +#include #include #include using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; using namespace rollup::proofs::account; #define WASM_EXPORT __attribute__((visibility("default"))) diff --git a/cpp/src/aztec/rollup/proofs/account/create_proof.hpp b/cpp/src/aztec/rollup/proofs/account/create_proof.hpp index 1f56ae8fb6..25dba586c9 100644 --- a/cpp/src/aztec/rollup/proofs/account/create_proof.hpp +++ b/cpp/src/aztec/rollup/proofs/account/create_proof.hpp @@ -19,8 +19,8 @@ inline std::vector create_proof(account_tx& tx, account_circuit(composer, tx); - if (composer.failed) { - info("Account circuit logic failed: ", composer.err); + if (composer.failed()) { + info("Account circuit logic failed: ", composer.err()); } auto prover = composer.create_unrolled_prover(); diff --git a/cpp/src/aztec/rollup/proofs/account/verify.hpp b/cpp/src/aztec/rollup/proofs/account/verify.hpp index ba469c480e..b0e71dd1af 100644 --- a/cpp/src/aztec/rollup/proofs/account/verify.hpp +++ b/cpp/src/aztec/rollup/proofs/account/verify.hpp @@ -2,13 +2,13 @@ #include "../verify.hpp" #include "./compute_circuit_data.hpp" #include "./account.hpp" -#include +#include namespace rollup { namespace proofs { namespace account { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; verify_result verify_logic(account_tx& tx, circuit_data const& cd); diff --git a/cpp/src/aztec/rollup/proofs/add_zero_public_inputs.hpp b/cpp/src/aztec/rollup/proofs/add_zero_public_inputs.hpp index 3ef3595a54..7fa609c72d 100644 --- a/cpp/src/aztec/rollup/proofs/add_zero_public_inputs.hpp +++ b/cpp/src/aztec/rollup/proofs/add_zero_public_inputs.hpp @@ -1,10 +1,10 @@ #pragma once -#include +#include namespace rollup { namespace proofs { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; inline void add_zero_public_inputs(Composer& composer, size_t num) { diff --git a/cpp/src/aztec/rollup/proofs/claim/claim.test.cpp b/cpp/src/aztec/rollup/proofs/claim/claim.test.cpp index 4c618832c0..fcd838f2fd 100644 --- a/cpp/src/aztec/rollup/proofs/claim/claim.test.cpp +++ b/cpp/src/aztec/rollup/proofs/claim/claim.test.cpp @@ -12,12 +12,19 @@ namespace proofs { namespace claim { using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; using namespace plonk::stdlib::merkle_tree; using namespace rollup::proofs::notes::native; using namespace rollup::proofs::notes::native::claim; namespace { +#ifdef CI +constexpr bool CIRCUIT_CHANGE_EXPECTED = false; +#else +// During development, if the circuit vk hash/gate count is expected to change, set the following to true. +constexpr bool CIRCUIT_CHANGE_EXPECTED = false; +#endif + std::shared_ptr srs; circuit_data cd; auto& engine = numeric::random::get_debug_engine(); @@ -99,27 +106,25 @@ TEST_F(claim_tests, test_claim_and_detect_circuit_change) claim_tx tx = create_claim_tx(note1, 0, 0, note2); EXPECT_TRUE(verify_logic(tx, cd).logic_verified); + // The below part detects changes in the claim circuit + constexpr uint32_t CIRCUIT_GATE_COUNT = 22684; + constexpr uint32_t GATES_NEXT_POWER_OF_TWO = 32768; + const uint256_t VK_HASH("11b5c8e9d3eb55a0d92e0a7a1b6b2cfc123fd1347a78adb8d487ffb2728516ad"); + size_t number_of_gates_claim = get_number_of_gates(); auto vk_hash_claim = get_verification_key()->sha256_hash(); - // If the below assertions fail, consider changing the variable is_circuit_change_expected to 1 in - // rollup/constants.hpp and see if atleast the next power of two limit is not exceeded. Please change the constant - // values accordingly and set is_circuit_change_expected to 0 in rollup/constants.hpp before merging. - if (!(circuit_gate_count::is_circuit_change_expected)) { - EXPECT_EQ(number_of_gates_claim, circuit_gate_count::CLAIM) - << "The gate count for the claim circuit is changed."; - EXPECT_EQ(from_buffer(vk_hash_claim), circuit_vk_hash::CLAIM) + + if (!CIRCUIT_CHANGE_EXPECTED) { + EXPECT_EQ(number_of_gates_claim, CIRCUIT_GATE_COUNT) << "The gate count for the claim circuit is changed."; + EXPECT_EQ(from_buffer(vk_hash_claim), VK_HASH) << "The verification key hash for the claim circuit is changed: " << from_buffer(vk_hash_claim); // For the next power of two limit, we need to consider that we reserve four gates for adding // randomness/zero-knowledge - EXPECT_LE(number_of_gates_claim, - circuit_gate_next_power_of_two::CLAIM - waffle::ComposerBase::NUM_RESERVED_GATES) - << "You have exceeded the next power of two limit for the claim circuit."; - } else { - EXPECT_LE(number_of_gates_claim, - circuit_gate_next_power_of_two::CLAIM - waffle::ComposerBase::NUM_RESERVED_GATES) - << "You have exceeded the next power of two limit for the claim circuit."; } + + EXPECT_LE(number_of_gates_claim, GATES_NEXT_POWER_OF_TWO - waffle::ComposerBase::NUM_RESERVED_GATES) + << "You have exceeded the next power of two limit for the claim circuit."; } TEST_F(claim_tests, test_theft_via_field_overflow_fails_1) diff --git a/cpp/src/aztec/rollup/proofs/claim/claim_circuit.hpp b/cpp/src/aztec/rollup/proofs/claim/claim_circuit.hpp index 2e2d60ac29..43f0b46a62 100644 --- a/cpp/src/aztec/rollup/proofs/claim/claim_circuit.hpp +++ b/cpp/src/aztec/rollup/proofs/claim/claim_circuit.hpp @@ -1,12 +1,12 @@ #pragma once #include "claim_tx.hpp" -#include +#include namespace rollup { namespace proofs { namespace claim { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; void claim_circuit(Composer& composer, claim_tx const& tx); diff --git a/cpp/src/aztec/rollup/proofs/claim/claim_tx.hpp b/cpp/src/aztec/rollup/proofs/claim/claim_tx.hpp index da4f735e84..22ab2da42c 100644 --- a/cpp/src/aztec/rollup/proofs/claim/claim_tx.hpp +++ b/cpp/src/aztec/rollup/proofs/claim/claim_tx.hpp @@ -6,13 +6,13 @@ #include "../notes/native/defi_interaction/note.hpp" #include "../notes/native/defi_interaction/compute_nullifier.hpp" #include -#include +#include namespace rollup { namespace proofs { namespace claim { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; struct claim_tx { fr data_root; diff --git a/cpp/src/aztec/rollup/proofs/claim/create_proof.hpp b/cpp/src/aztec/rollup/proofs/claim/create_proof.hpp index b372775f60..94cb44d567 100644 --- a/cpp/src/aztec/rollup/proofs/claim/create_proof.hpp +++ b/cpp/src/aztec/rollup/proofs/claim/create_proof.hpp @@ -12,8 +12,8 @@ inline std::vector create_proof(claim_tx& tx, circuit_data const& cd) claim_circuit(composer, tx); - if (composer.failed) { - info("Claim circuit logic failed: ", composer.err); + if (composer.failed()) { + info("Claim circuit logic failed: ", composer.err()); } auto prover = composer.create_unrolled_prover(); diff --git a/cpp/src/aztec/rollup/proofs/claim/ratio_check.hpp b/cpp/src/aztec/rollup/proofs/claim/ratio_check.hpp index f90b0fba2b..47757c2566 100644 --- a/cpp/src/aztec/rollup/proofs/claim/ratio_check.hpp +++ b/cpp/src/aztec/rollup/proofs/claim/ratio_check.hpp @@ -1,12 +1,12 @@ #pragma once #include "claim_tx.hpp" -#include +#include namespace rollup { namespace proofs { namespace claim { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; struct ratios { field_ct a1; diff --git a/cpp/src/aztec/rollup/proofs/claim/ratio_check.test.cpp b/cpp/src/aztec/rollup/proofs/claim/ratio_check.test.cpp index 406ed5e68e..ef0c164816 100644 --- a/cpp/src/aztec/rollup/proofs/claim/ratio_check.test.cpp +++ b/cpp/src/aztec/rollup/proofs/claim/ratio_check.test.cpp @@ -3,7 +3,7 @@ #include using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; using namespace rollup::proofs::claim; namespace { @@ -28,7 +28,7 @@ TEST(ratio_check, product_check) uint512_t test_right = uint512_t(a2) * uint512_t(b2); EXPECT_EQ(test_left, test_right); - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_ct left1(witness_ct(&composer, a1)); field_ct right1(witness_ct(&composer, b1)); @@ -38,8 +38,8 @@ TEST(ratio_check, product_check) auto result = product_check(composer, left1, right1, left2, right2, witness_ct(&composer, 0)); result.assert_equal(true); - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool proof_result = verifier.verify_proof(proof); EXPECT_EQ(proof_result, true); @@ -53,7 +53,7 @@ TEST(ratio_check, product_check_with_zeros) uint256_t c = 5; uint256_t d = 0; - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_ct a1(witness_ct(&composer, a)); field_ct b1(witness_ct(&composer, b)); @@ -63,8 +63,8 @@ TEST(ratio_check, product_check_with_zeros) auto result = product_check(composer, a1, b1, a2, b2, witness_ct(&composer, 0)); result.assert_equal(true); - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool proof_result = verifier.verify_proof(proof); EXPECT_EQ(proof_result, true); @@ -91,7 +91,7 @@ TEST(ratio_check, ratio_check) const uint256_t d = ((uint512_t(a) * uint512_t(b)) / uint512_t(c)).lo; - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_ct a1(witness_ct(&composer, a)); field_ct a2(witness_ct(&composer, c)); @@ -105,8 +105,8 @@ TEST(ratio_check, ratio_check) auto result = ratio_check(composer, ratios); result.assert_equal(true); - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool proof_result = verifier.verify_proof(proof); EXPECT_EQ(proof_result, true); @@ -119,7 +119,7 @@ TEST(ratio_check, bad_ratio_check) uint256_t c = 200; uint256_t d = 21; - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_ct a1(witness_ct(&composer, a)); field_ct a2(witness_ct(&composer, b)); @@ -130,8 +130,8 @@ TEST(ratio_check, bad_ratio_check) auto result = ratio_check(composer, ratios); result.assert_equal(false); - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool proof_result = verifier.verify_proof(proof); EXPECT_EQ(proof_result, true); @@ -144,7 +144,7 @@ TEST(ratio_check, zero_denominator_a2_returns_false) uint256_t c = 5; uint256_t d = 0; - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_ct a1(witness_ct(&composer, a)); field_ct a2(witness_ct(&composer, d)); @@ -155,8 +155,8 @@ TEST(ratio_check, zero_denominator_a2_returns_false) auto result = ratio_check(composer, ratios); result.assert_equal(false); - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool proof_result = verifier.verify_proof(proof); EXPECT_EQ(proof_result, true); @@ -169,7 +169,7 @@ TEST(ratio_check, zero_denominator_b2_returns_false) uint256_t c = 5; uint256_t d = 1; - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_ct a1(witness_ct(&composer, a)); field_ct a2(witness_ct(&composer, d)); @@ -180,8 +180,8 @@ TEST(ratio_check, zero_denominator_b2_returns_false) auto result = ratio_check(composer, ratios); result.assert_equal(false); - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool proof_result = verifier.verify_proof(proof); EXPECT_EQ(proof_result, true); @@ -194,7 +194,7 @@ TEST(ratio_check, zero_denominator_both_returns_false) uint256_t c = 5; uint256_t d = 0; - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_ct a1(witness_ct(&composer, a)); field_ct a2(witness_ct(&composer, d)); @@ -205,8 +205,8 @@ TEST(ratio_check, zero_denominator_both_returns_false) auto result = ratio_check(composer, ratios); result.assert_equal(false); - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool proof_result = verifier.verify_proof(proof); EXPECT_EQ(proof_result, true); @@ -220,7 +220,7 @@ TEST(ratio_check, field_modulus_overflow_fails) // uint256_t d = 10944121435919637611123202872628637544274182200208017171849102093287904247809; // = 2^(-1) uint256_t d(0xA1F0FAC9F8000001ULL, 0x9419F4243CDCB848ULL, 0xDC2822DB40C0AC2EULL, 0x183227397098D014ULL); // = 2^(-1) - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_ct a1(witness_ct(&composer, a)); field_ct a2(witness_ct(&composer, d)); @@ -233,8 +233,8 @@ TEST(ratio_check, field_modulus_overflow_fails) auto result = ratio_check(composer, ratios); result.assert_equal(false); - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool proof_result = verifier.verify_proof(proof); EXPECT_EQ(proof_result, true); @@ -250,7 +250,7 @@ TEST(ratio_check, field_modulus_overflow_with_biggest_numbers_possible_fails) uint256_t c = r - 1; uint256_t d = r - 1; - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_ct a1(witness_ct(&composer, a)); field_ct a2(witness_ct(&composer, d)); @@ -263,8 +263,8 @@ TEST(ratio_check, field_modulus_overflow_with_biggest_numbers_possible_fails) auto result = ratio_check(composer, ratios); result.assert_equal(false); - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool proof_result = verifier.verify_proof(proof); EXPECT_EQ(proof_result, true); diff --git a/cpp/src/aztec/rollup/proofs/claim/verify.hpp b/cpp/src/aztec/rollup/proofs/claim/verify.hpp index 5208ec6b61..b65c3fbff6 100644 --- a/cpp/src/aztec/rollup/proofs/claim/verify.hpp +++ b/cpp/src/aztec/rollup/proofs/claim/verify.hpp @@ -1,13 +1,13 @@ #pragma once #include "../verify.hpp" #include "./get_circuit_data.hpp" -#include +#include namespace rollup { namespace proofs { namespace claim { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; verify_result verify_logic(claim_tx& tx, circuit_data const& cd); diff --git a/cpp/src/aztec/rollup/proofs/compute_circuit_data.hpp b/cpp/src/aztec/rollup/proofs/compute_circuit_data.hpp index ed9c2edbc1..72a8b91cff 100644 --- a/cpp/src/aztec/rollup/proofs/compute_circuit_data.hpp +++ b/cpp/src/aztec/rollup/proofs/compute_circuit_data.hpp @@ -9,9 +9,9 @@ #include #define GET_COMPOSER_NAME_STRING(composer) \ - (typeid(composer) == typeid(waffle::StandardComposer) \ - ? "StandardPlonk" \ - : typeid(composer) == typeid(waffle::TurboComposer) ? "TurboPlonk" : "NULLPlonk") + (typeid(composer) == typeid(waffle::StandardComposer) ? "StandardPlonk" \ + : typeid(composer) == typeid(waffle::TurboComposer) ? "TurboPlonk" \ + : "NULLPlonk") namespace rollup { namespace proofs { @@ -220,8 +220,8 @@ circuit_data get_circuit_data(std::string const& name, } else if (data.proving_key) { info(name, ": Computing padding proof..."); - if (composer.failed) { - info(name, ": Composer logic failed: ", composer.err); + if (composer.failed()) { + info(name, ": Composer logic failed: ", composer.err()); info(name, ": Warning, padding proof can only be used to aid upstream pk construction!"); } diff --git a/cpp/src/aztec/rollup/proofs/join_split/c_bind.cpp b/cpp/src/aztec/rollup/proofs/join_split/c_bind.cpp index 802b72248d..e1cdb48be5 100644 --- a/cpp/src/aztec/rollup/proofs/join_split/c_bind.cpp +++ b/cpp/src/aztec/rollup/proofs/join_split/c_bind.cpp @@ -7,12 +7,12 @@ #include #include #include -#include +#include #include #include using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; using namespace rollup::proofs::join_split; #define WASM_EXPORT __attribute__((visibility("default"))) diff --git a/cpp/src/aztec/rollup/proofs/join_split/compute_circuit_data.cpp b/cpp/src/aztec/rollup/proofs/join_split/compute_circuit_data.cpp index 8475dbd000..05d766661c 100644 --- a/cpp/src/aztec/rollup/proofs/join_split/compute_circuit_data.cpp +++ b/cpp/src/aztec/rollup/proofs/join_split/compute_circuit_data.cpp @@ -9,7 +9,7 @@ namespace proofs { namespace join_split { using namespace rollup::proofs::join_split; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; using namespace rollup::proofs::notes::native; using namespace plonk::stdlib::merkle_tree; diff --git a/cpp/src/aztec/rollup/proofs/join_split/create_noop_join_split_proof.cpp b/cpp/src/aztec/rollup/proofs/join_split/create_noop_join_split_proof.cpp index ab34fc44ef..bfc19dde86 100644 --- a/cpp/src/aztec/rollup/proofs/join_split/create_noop_join_split_proof.cpp +++ b/cpp/src/aztec/rollup/proofs/join_split/create_noop_join_split_proof.cpp @@ -1,7 +1,7 @@ #include "create_noop_join_split_proof.hpp" #include "join_split_circuit.hpp" #include -#include +#include #include namespace rollup { @@ -9,7 +9,7 @@ namespace proofs { namespace join_split { using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; using namespace plonk::stdlib::merkle_tree; std::vector create_noop_join_split_proof(circuit_data const& circuit_data, @@ -24,8 +24,8 @@ std::vector create_noop_join_split_proof(circuit_data const& circuit_da Composer composer = Composer(circuit_data.proving_key, circuit_data.verification_key, circuit_data.num_gates); join_split_circuit(composer, tx); - if (composer.failed) { - info("join split logic failed: ", composer.err); + if (composer.failed()) { + info("join split logic failed: ", composer.err()); } if (!mock) { diff --git a/cpp/src/aztec/rollup/proofs/join_split/create_proof.hpp b/cpp/src/aztec/rollup/proofs/join_split/create_proof.hpp index 18092fd9e5..67ff6c5fc3 100644 --- a/cpp/src/aztec/rollup/proofs/join_split/create_proof.hpp +++ b/cpp/src/aztec/rollup/proofs/join_split/create_proof.hpp @@ -16,8 +16,8 @@ inline std::vector create_proof(join_split_tx const& tx, composer.rand_engine = rand_engine; join_split_circuit(composer, tx); - if (composer.failed) { - info("Join-split circuit logic failed: ", composer.err); + if (composer.failed()) { + info("Join-split circuit logic failed: ", composer.err()); } auto prover = composer.create_unrolled_prover(); diff --git a/cpp/src/aztec/rollup/proofs/join_split/join_split.cpp b/cpp/src/aztec/rollup/proofs/join_split/join_split.cpp index c3cb387950..dfa0a31def 100644 --- a/cpp/src/aztec/rollup/proofs/join_split/join_split.cpp +++ b/cpp/src/aztec/rollup/proofs/join_split/join_split.cpp @@ -1,7 +1,6 @@ #include "join_split.hpp" #include "join_split_circuit.hpp" #include "compute_circuit_data.hpp" -#include #include namespace rollup { @@ -54,7 +53,9 @@ void init_verification_key(std::unique_ptr&& crs } // Patch the 'nothing' reference string fed to init_proving_key. proving_key->reference_string = crs_factory->get_prover_crs(proving_key->n + 1); - verification_key = waffle::turbo_composer::compute_verification_key(proving_key, crs_factory->get_verifier_crs()); + + verification_key = + plonk::stdlib::types::Composer::compute_verification_key_base(proving_key, crs_factory->get_verifier_crs()); } void init_verification_key(std::shared_ptr const& crs, @@ -68,8 +69,8 @@ UnrolledProver new_join_split_prover(join_split_tx const& tx, bool mock) Composer composer(proving_key, nullptr); join_split_circuit(composer, tx); - if (composer.failed) { - std::string error = format("composer logic failed: ", composer.err); + if (composer.failed()) { + std::string error = format("composer logic failed: ", composer.err()); throw_or_abort(error); } diff --git a/cpp/src/aztec/rollup/proofs/join_split/join_split.hpp b/cpp/src/aztec/rollup/proofs/join_split/join_split.hpp index ac75b13245..5ac0f484d4 100644 --- a/cpp/src/aztec/rollup/proofs/join_split/join_split.hpp +++ b/cpp/src/aztec/rollup/proofs/join_split/join_split.hpp @@ -1,14 +1,14 @@ #pragma once #include "join_split_tx.hpp" -#include -#include +#include +#include namespace rollup { namespace proofs { namespace join_split { using namespace plonk::stdlib::merkle_tree; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; void init_proving_key(std::shared_ptr const& crs_factory, bool mock); diff --git a/cpp/src/aztec/rollup/proofs/join_split/join_split.test.cpp b/cpp/src/aztec/rollup/proofs/join_split/join_split.test.cpp index fe11637dde..1dcddd3536 100644 --- a/cpp/src/aztec/rollup/proofs/join_split/join_split.test.cpp +++ b/cpp/src/aztec/rollup/proofs/join_split/join_split.test.cpp @@ -11,8 +11,15 @@ namespace rollup { namespace proofs { namespace join_split { +#ifdef CI +constexpr bool CIRCUIT_CHANGE_EXPECTED = false; +#else +// During development, if the circuit vk hash/gate count is expected to change, set the following to true. +constexpr bool CIRCUIT_CHANGE_EXPECTED = false; +#endif + using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; using namespace plonk::stdlib::merkle_tree; using namespace rollup::proofs::notes::native; using key_pair = rollup::fixtures::grumpkin_key_pair; @@ -273,10 +280,10 @@ class join_split_tests : public ::testing::Test { { Composer composer(get_proving_key(), nullptr); join_split_circuit(composer, tx); - if (composer.failed) { - std::cout << "Logic failed: " << composer.err << std::endl; + if (composer.failed()) { + std::cout << "Logic failed: " << composer.err() << std::endl; } - return { !composer.failed, composer.err, composer.get_public_inputs(), composer.get_num_gates() }; + return { !composer.failed(), composer.err(), composer.get_public_inputs(), composer.get_num_gates() }; } verify_result sign_and_verify_logic(join_split_tx& tx, key_pair const& signing_key) @@ -688,28 +695,26 @@ TEST_F(join_split_tests, test_0_input_notes_and_detect_circuit_change) auto result = sign_and_verify_logic(tx, user.owner); EXPECT_TRUE(result.valid); + // The below part detects any changes in the join-split circuit + constexpr uint32_t CIRCUIT_GATE_COUNT = 64000; + constexpr uint32_t GATES_NEXT_POWER_OF_TWO = 65536; + const uint256_t VK_HASH("bb2062d006d31d3234766277711eb28577d5f6082d0f484b87e8235628f8e864"); + auto number_of_gates_js = result.number_of_gates; auto vk_hash_js = get_verification_key()->sha256_hash(); - // If the below assertions fail, consider changing the variable is_circuit_change_expected to 1 in - // rollup/constants.hpp and see if atleast the next power of two limit is not exceeded. Please change the constant - // values accordingly and set is_circuit_change_expected to 0 in rollup/constants.hpp before merging. - if (!(circuit_gate_count::is_circuit_change_expected)) { - EXPECT_EQ(number_of_gates_js, circuit_gate_count::JOIN_SPLIT) - << "The gate count for the join_split circuit is changed."; - EXPECT_EQ(from_buffer(vk_hash_js), circuit_vk_hash::JOIN_SPLIT) + + if (!CIRCUIT_CHANGE_EXPECTED) { + EXPECT_EQ(number_of_gates_js, CIRCUIT_GATE_COUNT) << "The gate count for the join_split circuit is changed."; + EXPECT_EQ(from_buffer(vk_hash_js), VK_HASH) << "The verification key hash for the join_split circuit is changed: " << from_buffer(vk_hash_js); - // For the next power of two limit, we need to consider that we reserve four gates for adding - // randomness/zero-knowledge - EXPECT_LE(number_of_gates_js, - circuit_gate_next_power_of_two::JOIN_SPLIT - waffle::ComposerBase::NUM_RESERVED_GATES) - << "You have exceeded the next power of two limit for the join_split circuit."; - } else { - EXPECT_LE(number_of_gates_js, - circuit_gate_next_power_of_two::JOIN_SPLIT - waffle::ComposerBase::NUM_RESERVED_GATES) - << "You have exceeded the next power of two limit for the join_split circuit."; } + + // For the next power of two limit, we need to consider that we reserve four gates for adding + // randomness/zero-knowledge + EXPECT_LE(number_of_gates_js, GATES_NEXT_POWER_OF_TWO - waffle::ComposerBase::NUM_RESERVED_GATES) + << "You have exceeded the next power of two limit for the join_split circuit."; } // Bespoke test seeking bug. @@ -1025,7 +1030,7 @@ TEST_F(join_split_tests, test_total_output_value_larger_than_total_input_value_f TEST_F(join_split_tests, test_different_input_note_owners_fails) { join_split_tx tx = simple_setup({ 1, 2 }); - tx.input_note[0].owner = grumpkin::g1::affine_element::hash_to_curve(1).second; + tx.input_note[0].owner = grumpkin::g1::affine_element::hash_to_curve(1); auto result = sign_and_verify_logic(tx, user.owner); EXPECT_FALSE(result.valid); @@ -2507,7 +2512,7 @@ TEST_F(join_split_tests, serialzed_proving_key_size) { uint8_t* ptr; auto len = join_split__get_new_proving_key_data(&ptr); - EXPECT_LE(len, 170 * 1024 * 1024); + EXPECT_LE(len, 2 * 170 * 1024 * 1024); } } // namespace join_split diff --git a/cpp/src/aztec/rollup/proofs/join_split/join_split_circuit.cpp b/cpp/src/aztec/rollup/proofs/join_split/join_split_circuit.cpp index 468a43e698..fb8c384c5b 100644 --- a/cpp/src/aztec/rollup/proofs/join_split/join_split_circuit.cpp +++ b/cpp/src/aztec/rollup/proofs/join_split/join_split_circuit.cpp @@ -6,7 +6,6 @@ #include "../notes/circuit/claim/claim_note.hpp" #include "verify_signature.hpp" #include -#include // #pragma GCC diagnostic ignored "-Wunused-variable" // #pragma GCC diagnostic ignored "-Wunused-parameter" diff --git a/cpp/src/aztec/rollup/proofs/join_split/join_split_circuit.hpp b/cpp/src/aztec/rollup/proofs/join_split/join_split_circuit.hpp index 7f8781e2c0..cfb95360c8 100644 --- a/cpp/src/aztec/rollup/proofs/join_split/join_split_circuit.hpp +++ b/cpp/src/aztec/rollup/proofs/join_split/join_split_circuit.hpp @@ -3,13 +3,13 @@ #include "../notes/circuit/value/witness_data.hpp" #include "../notes/circuit/claim/witness_data.hpp" #include -#include +#include namespace rollup { namespace proofs { namespace join_split { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; struct join_split_inputs { field_ct proof_id; diff --git a/cpp/src/aztec/rollup/proofs/join_split/join_split_js_parity.test.cpp b/cpp/src/aztec/rollup/proofs/join_split/join_split_js_parity.test.cpp index 73d3db42d3..d6fa710c61 100644 --- a/cpp/src/aztec/rollup/proofs/join_split/join_split_js_parity.test.cpp +++ b/cpp/src/aztec/rollup/proofs/join_split/join_split_js_parity.test.cpp @@ -13,7 +13,7 @@ namespace proofs { namespace join_split { using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +// using namespace plonk::stdlib::types::turbo; using namespace plonk::stdlib::merkle_tree; using namespace rollup::proofs::notes::native; using key_pair = rollup::fixtures::grumpkin_key_pair; diff --git a/cpp/src/aztec/rollup/proofs/join_split/join_split_tx.hpp b/cpp/src/aztec/rollup/proofs/join_split/join_split_tx.hpp index 5ce60783fb..48ff93dfe6 100644 --- a/cpp/src/aztec/rollup/proofs/join_split/join_split_tx.hpp +++ b/cpp/src/aztec/rollup/proofs/join_split/join_split_tx.hpp @@ -3,13 +3,13 @@ #include "../notes/native/value/value_note.hpp" #include #include -#include +#include namespace rollup { namespace proofs { namespace join_split { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; struct join_split_tx { uint32_t proof_id; diff --git a/cpp/src/aztec/rollup/proofs/join_split/join_split_tx.test.cpp b/cpp/src/aztec/rollup/proofs/join_split/join_split_tx.test.cpp index 019ea29269..8969916e22 100644 --- a/cpp/src/aztec/rollup/proofs/join_split/join_split_tx.test.cpp +++ b/cpp/src/aztec/rollup/proofs/join_split/join_split_tx.test.cpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include diff --git a/cpp/src/aztec/rollup/proofs/mock/CMakeLists.txt b/cpp/src/aztec/rollup/proofs/mock/CMakeLists.txt new file mode 100644 index 0000000000..7695dfdc80 --- /dev/null +++ b/cpp/src/aztec/rollup/proofs/mock/CMakeLists.txt @@ -0,0 +1,6 @@ +barretenberg_module( + rollup_proofs_mock + stdlib_blake2s + stdlib_sha256 + stdlib_pedersen + stdlib_primitives) \ No newline at end of file diff --git a/cpp/src/aztec/rollup/proofs/mock/mock_circuit.test.cpp b/cpp/src/aztec/rollup/proofs/mock/mock_circuit.test.cpp new file mode 100644 index 0000000000..4a369377d2 --- /dev/null +++ b/cpp/src/aztec/rollup/proofs/mock/mock_circuit.test.cpp @@ -0,0 +1,38 @@ +#include "mock_circuit.hpp" +#include "../join_split/join_split_tx.hpp" +#include +#include + +using namespace plonk::stdlib::types; + +namespace rollup { +namespace proofs { +namespace mock { + +TEST(mock_circuit_tests, test_simple_circuit) +{ + // Dummy public inputs + std::vector public_inputs; + for (size_t i = 0; i < 16; i++) { + public_inputs.push_back(fr::random_element()); + } + + Composer composer = Composer("../srs_db/ignition"); + mock_circuit(composer, public_inputs); + + UnrolledProver prover = composer.create_unrolled_prover(); + waffle::plonk_proof proof = prover.construct_proof(); + + std::cout << "gates: " << composer.get_num_gates() << std::endl; + std::cout << "proof size: " << proof.proof_data.size() << std::endl; + std::cout << "public inputs size: " << composer.public_inputs.size() << std::endl; + + auto verifier = composer.create_unrolled_verifier(); + bool result = verifier.verify_proof(proof); + + EXPECT_TRUE(result); +} + +} // namespace mock +} // namespace proofs +} // namespace rollup \ No newline at end of file diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/account/account_note.hpp b/cpp/src/aztec/rollup/proofs/notes/circuit/account/account_note.hpp index 0851409bc1..9af9642037 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/account/account_note.hpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/account/account_note.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include "commit.hpp" namespace rollup { @@ -8,7 +8,7 @@ namespace notes { namespace circuit { namespace account { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; struct account_note { field_ct account_alias_hash; diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/account/commit.hpp b/cpp/src/aztec/rollup/proofs/notes/circuit/account/commit.hpp index 7a865226b0..cf8e0f2236 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/account/commit.hpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/account/commit.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include "../../constants.hpp" namespace rollup { @@ -8,7 +8,7 @@ namespace notes { namespace circuit { namespace account { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; inline auto commit(field_ct const& account_alias_hash, point_ct const& account_public_key, diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/asset_id.cpp b/cpp/src/aztec/rollup/proofs/notes/circuit/asset_id.cpp index 0393ea4306..e8bf886875 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/asset_id.cpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/asset_id.cpp @@ -1,9 +1,9 @@ -#include +#include #include "../constants.hpp" namespace rollup::proofs::notes::circuit { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; std::pair deflag_asset_id(suint_ct const& asset_id) { diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/asset_id.hpp b/cpp/src/aztec/rollup/proofs/notes/circuit/asset_id.hpp index c977f10bc3..3b792ab9bd 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/asset_id.hpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/asset_id.hpp @@ -1,9 +1,9 @@ #pragma once -#include +#include namespace rollup::proofs::notes::circuit { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; std::pair deflag_asset_id(suint_ct const& asset_id); diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/bridge_call_data.hpp b/cpp/src/aztec/rollup/proofs/notes/circuit/bridge_call_data.hpp index e47dd919dd..49af294142 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/bridge_call_data.hpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/bridge_call_data.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include "../native/bridge_call_data.hpp" #include "./asset_id.hpp" #include "../constants.hpp" @@ -9,7 +9,7 @@ namespace proofs { namespace notes { namespace circuit { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; constexpr uint32_t input_asset_id_a_shift = DEFI_BRIDGE_ADDRESS_ID_LEN; constexpr uint32_t input_asset_id_b_shift = input_asset_id_a_shift + DEFI_BRIDGE_INPUT_A_ASSET_ID_LEN; diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/claim/claim_note.hpp b/cpp/src/aztec/rollup/proofs/notes/circuit/claim/claim_note.hpp index 5f94d14952..ada073d819 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/claim/claim_note.hpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/claim/claim_note.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include "../bridge_call_data.hpp" #include "witness_data.hpp" #include "../value/create_partial_commitment.hpp" @@ -12,7 +12,7 @@ namespace notes { namespace circuit { namespace claim { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; struct partial_claim_note { suint_ct deposit_value; diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/claim/complete_partial_commitment.hpp b/cpp/src/aztec/rollup/proofs/notes/circuit/claim/complete_partial_commitment.hpp index 53796108d6..4e2ec8b144 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/claim/complete_partial_commitment.hpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/claim/complete_partial_commitment.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include "../../constants.hpp" namespace rollup { @@ -8,7 +8,7 @@ namespace notes { namespace circuit { namespace claim { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; inline auto complete_partial_commitment(field_ct const& partial_commitment, field_ct const& interaction_nonce, diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/claim/compute_nullifier.hpp b/cpp/src/aztec/rollup/proofs/notes/circuit/claim/compute_nullifier.hpp index b193e5f5f2..29814d92f0 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/claim/compute_nullifier.hpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/claim/compute_nullifier.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include #include "../../constants.hpp" @@ -9,7 +9,7 @@ namespace notes { namespace circuit { namespace claim { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; inline field_ct compute_nullifier(field_ct const& note_commitment) { diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/claim/create_partial_commitment.hpp b/cpp/src/aztec/rollup/proofs/notes/circuit/claim/create_partial_commitment.hpp index d713f22f96..922ef25c7d 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/claim/create_partial_commitment.hpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/claim/create_partial_commitment.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include #include "../../constants.hpp" @@ -9,7 +9,7 @@ namespace notes { namespace circuit { namespace claim { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; inline auto create_partial_commitment(field_ct const& deposit_value, field_ct const& bridge_call_data, diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/claim/witness_data.hpp b/cpp/src/aztec/rollup/proofs/notes/circuit/claim/witness_data.hpp index be86892916..ded297f3fc 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/claim/witness_data.hpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/claim/witness_data.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include "../../native/claim/claim_note.hpp" #include "../../native/claim/claim_note_tx_data.hpp" #include "../../constants.hpp" @@ -11,7 +11,7 @@ namespace notes { namespace circuit { namespace claim { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; /** * Convert native claim note data into circuit witness data. diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/defi_interaction/compute_nullifier.hpp b/cpp/src/aztec/rollup/proofs/notes/circuit/defi_interaction/compute_nullifier.hpp index 36e9ef65a4..7ae0d9e4a1 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/defi_interaction/compute_nullifier.hpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/defi_interaction/compute_nullifier.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include "../../constants.hpp" namespace rollup { @@ -8,7 +8,7 @@ namespace notes { namespace circuit { namespace defi_interaction { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; /** * nonce - randomness provided by the user (sdk) to ensure uniqueness. diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/defi_interaction/note.hpp b/cpp/src/aztec/rollup/proofs/notes/circuit/defi_interaction/note.hpp index 41727ff70a..fe5e316701 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/defi_interaction/note.hpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/defi_interaction/note.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include "../../native/defi_interaction/note.hpp" #include "witness_data.hpp" @@ -9,7 +9,7 @@ namespace notes { namespace circuit { namespace defi_interaction { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; struct note { diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/defi_interaction/witness_data.hpp b/cpp/src/aztec/rollup/proofs/notes/circuit/defi_interaction/witness_data.hpp index 607a0b4521..9664b0f68c 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/defi_interaction/witness_data.hpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/defi_interaction/witness_data.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include "../../native/defi_interaction/note.hpp" #include "../bridge_call_data.hpp" @@ -9,7 +9,7 @@ namespace notes { namespace circuit { namespace defi_interaction { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; struct witness_data { bridge_call_data bridge_call_data_local; diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/value/complete_partial_commitment.hpp b/cpp/src/aztec/rollup/proofs/notes/circuit/value/complete_partial_commitment.hpp index c7969a29c3..ecc1c21238 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/value/complete_partial_commitment.hpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/value/complete_partial_commitment.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include #include "../../constants.hpp" @@ -9,7 +9,7 @@ namespace notes { namespace circuit { namespace value { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; inline auto complete_partial_commitment(field_ct const& value_note_partial_commitment, suint_ct const& value, diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/value/compute_nullifier.cpp b/cpp/src/aztec/rollup/proofs/notes/circuit/value/compute_nullifier.cpp index f51938342a..9802c14e22 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/value/compute_nullifier.cpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/value/compute_nullifier.cpp @@ -1,6 +1,6 @@ #include "compute_nullifier.hpp" #include "../../constants.hpp" -#include +#include namespace rollup { namespace proofs { @@ -8,7 +8,7 @@ namespace notes { namespace circuit { using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; field_ct compute_nullifier(field_ct const& note_commitment, field_ct const& account_private_key, diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/value/compute_nullifier.hpp b/cpp/src/aztec/rollup/proofs/notes/circuit/value/compute_nullifier.hpp index e9221dcc47..116c228609 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/value/compute_nullifier.hpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/value/compute_nullifier.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include namespace rollup { namespace proofs { @@ -7,7 +7,7 @@ namespace notes { namespace circuit { using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; field_ct compute_nullifier(field_ct const& note_commitment, field_ct const& account_private_key, diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/value/compute_nullifier.test.cpp b/cpp/src/aztec/rollup/proofs/notes/circuit/value/compute_nullifier.test.cpp index 4737c67fd4..4f7f914b7b 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/value/compute_nullifier.test.cpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/value/compute_nullifier.test.cpp @@ -4,10 +4,10 @@ #include "./value_note.hpp" #include "../../native/value/compute_nullifier.hpp" #include "../../native/value/value_note.hpp" -#include +#include using namespace rollup::proofs::notes; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; TEST(compute_nullifier_circuit, native_consistency) { diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/value/create_partial_commitment.hpp b/cpp/src/aztec/rollup/proofs/notes/circuit/value/create_partial_commitment.hpp index 67679e76f8..045fa6e9d6 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/value/create_partial_commitment.hpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/value/create_partial_commitment.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include #include "../../constants.hpp" @@ -9,7 +9,7 @@ namespace notes { namespace circuit { namespace value { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; inline auto create_partial_commitment(field_ct const& secret, point_ct const& owner, diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/value/value_note.hpp b/cpp/src/aztec/rollup/proofs/notes/circuit/value/value_note.hpp index fde6b5d3cf..37067866ac 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/value/value_note.hpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/value/value_note.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include "witness_data.hpp" #include "commit.hpp" @@ -9,7 +9,7 @@ namespace notes { namespace circuit { namespace value { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; struct value_note { point_ct owner; diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/value/value_note.test.cpp b/cpp/src/aztec/rollup/proofs/notes/circuit/value/value_note.test.cpp index b46720eccd..da8809c488 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/value/value_note.test.cpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/value/value_note.test.cpp @@ -5,7 +5,7 @@ #include using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; using namespace rollup::proofs::notes; using namespace rollup::proofs::notes::circuit::value; @@ -30,11 +30,11 @@ TEST(value_note, commits) auto result = circuit_note.commitment; result.assert_equal(expected); - waffle::TurboProver prover = composer.create_prover(); + Prover prover = composer.create_prover(); - EXPECT_FALSE(composer.failed); + EXPECT_FALSE(composer.failed()); printf("composer gates = %zu\n", composer.get_num_gates()); - waffle::TurboVerifier verifier = composer.create_verifier(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); @@ -64,11 +64,11 @@ TEST(value_note, commits_with_0_value) auto result = circuit_note.commitment; result.assert_equal(expected); - waffle::TurboProver prover = composer.create_prover(); + Prover prover = composer.create_prover(); - EXPECT_FALSE(composer.failed); + EXPECT_FALSE(composer.failed()); printf("composer gates = %zu\n", composer.get_num_gates()); - waffle::TurboVerifier verifier = composer.create_verifier(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); @@ -96,11 +96,11 @@ TEST(value_note, commit_with_oversized_asset_id_fails) auto result = circuit_note.commitment; result.assert_equal(expected); - waffle::TurboProver prover = composer.create_prover(); + Prover prover = composer.create_prover(); - EXPECT_TRUE(composer.failed); + EXPECT_TRUE(composer.failed()); printf("composer gates = %zu\n", composer.get_num_gates()); - waffle::TurboVerifier verifier = composer.create_verifier(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); diff --git a/cpp/src/aztec/rollup/proofs/notes/circuit/value/witness_data.hpp b/cpp/src/aztec/rollup/proofs/notes/circuit/value/witness_data.hpp index c10357a939..6bde7100dc 100644 --- a/cpp/src/aztec/rollup/proofs/notes/circuit/value/witness_data.hpp +++ b/cpp/src/aztec/rollup/proofs/notes/circuit/value/witness_data.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include "../../native/value/value_note.hpp" #include "../../constants.hpp" @@ -9,7 +9,7 @@ namespace notes { namespace circuit { namespace value { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; struct witness_data { point_ct owner; diff --git a/cpp/src/aztec/rollup/proofs/rollup/rollup_circuit.cpp b/cpp/src/aztec/rollup/proofs/rollup/rollup_circuit.cpp index 234ce37424..2133b3371c 100644 --- a/cpp/src/aztec/rollup/proofs/rollup/rollup_circuit.cpp +++ b/cpp/src/aztec/rollup/proofs/rollup/rollup_circuit.cpp @@ -16,7 +16,7 @@ namespace rollup { namespace proofs { namespace rollup { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; using namespace plonk::stdlib::recursion; using namespace plonk::stdlib::merkle_tree; using namespace notes; @@ -332,12 +332,11 @@ recursion_output rollup_circuit(Composer& composer, recursive_verification_key->validate_key_is_in_set(verification_keys); // Verify the inner proof. - recursion_output = - verify_proof>(&composer, - recursive_verification_key, - recursive_manifest, - waffle::plonk_proof{ rollup.txs[i] }, - recursion_output); + recursion_output = verify_proof(&composer, + recursive_verification_key, + recursive_manifest, + waffle::plonk_proof{ rollup.txs[i] }, + recursion_output); auto is_real = num_txs > uint32_ct(&composer, i); auto& public_inputs = recursion_output.public_inputs; diff --git a/cpp/src/aztec/rollup/proofs/rollup/rollup_circuit.hpp b/cpp/src/aztec/rollup/proofs/rollup/rollup_circuit.hpp index ad76544108..da3af76bfb 100644 --- a/cpp/src/aztec/rollup/proofs/rollup/rollup_circuit.hpp +++ b/cpp/src/aztec/rollup/proofs/rollup/rollup_circuit.hpp @@ -2,13 +2,13 @@ #include "rollup_tx.hpp" #include #include -#include +#include namespace rollup { namespace proofs { namespace rollup { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; using namespace plonk::stdlib::recursion; field_ct check_nullifiers_inserted(Composer& composer, diff --git a/cpp/src/aztec/rollup/proofs/rollup/rollup_circuit.test.cpp b/cpp/src/aztec/rollup/proofs/rollup/rollup_circuit.test.cpp index afebec4b52..d0a8718ca4 100644 --- a/cpp/src/aztec/rollup/proofs/rollup/rollup_circuit.test.cpp +++ b/cpp/src/aztec/rollup/proofs/rollup/rollup_circuit.test.cpp @@ -59,7 +59,7 @@ class rollup_tests : public ::testing::Test { .second_output_in_use = true }, .aux_data = 0 }; - // MIKE started here + auto defi_proof1 = context.create_defi_proof({ 0, 1 }, { 100, 50 }, { 40, 110 }, bid); return create_rollup_tx(context.world_state, 1, { defi_proof1 }, { bid }); diff --git a/cpp/src/aztec/rollup/proofs/rollup/rollup_circuit_full.test.cpp b/cpp/src/aztec/rollup/proofs/rollup/rollup_circuit_full.test.cpp index 2f1568af13..8640cd0258 100644 --- a/cpp/src/aztec/rollup/proofs/rollup/rollup_circuit_full.test.cpp +++ b/cpp/src/aztec/rollup/proofs/rollup/rollup_circuit_full.test.cpp @@ -14,6 +14,13 @@ using namespace notes::native::value; using namespace plonk::stdlib::merkle_tree; namespace { +#ifdef CI +constexpr bool CIRCUIT_CHANGE_EXPECTED = false; +#else +// During development, if the circuit vk hash/gate count is expected to change, set the following to true. +constexpr bool CIRCUIT_CHANGE_EXPECTED = false; +#endif + std::shared_ptr srs; join_split::circuit_data js_cd; account::circuit_data account_cd; @@ -83,27 +90,24 @@ HEAVY_TEST_F(rollup_full_tests, test_1_proof_in_1_rollup_full_proof_and_detect_c EXPECT_EQ(inner_data.public_value, tx_data.public_value); EXPECT_EQ(inner_data.public_owner, tx_data.public_owner); EXPECT_EQ(inner_data.asset_id, tx_data.asset_id); + // The below part detects the changes in the rollup circuit + constexpr uint32_t CIRCUIT_GATE_COUNT = 1153136; + constexpr uint32_t GATES_NEXT_POWER_OF_TWO = 2097152; + const uint256_t VK_HASH("b6481781e449ba7c4a3bff935cc08421ab9b88527d0a70fa454dd9288dba8c46"); + auto number_of_gates_rollup = rollup_circuit_data.num_gates; auto vk_hash_rollup = rollup_circuit_data.verification_key->sha256_hash(); - // If the below assertions fail, consider changing the variable is_circuit_change_expected to 1 in - // rollup/constants.hpp and see if atleast the next power of two limit is not exceeded. Please change the constant - // values accordingly and set is_circuit_change_expected to 0 in rollup/constants.hpp before merging. - if (!(circuit_gate_count::is_circuit_change_expected)) { - EXPECT_EQ(number_of_gates_rollup, circuit_gate_count::ROLLUP) - << "The gate count for the rollup circuit is changed."; - EXPECT_EQ(from_buffer(vk_hash_rollup), circuit_vk_hash::ROLLUP) + + if (!CIRCUIT_CHANGE_EXPECTED) { + EXPECT_EQ(number_of_gates_rollup, CIRCUIT_GATE_COUNT) << "The gate count for the rollup circuit is changed."; + EXPECT_EQ(from_buffer(vk_hash_rollup), VK_HASH) << "The verification key hash for the rollup circuit is changed."; - // For the next power of two limit, we need to consider that we reserve four gates for adding - // randomness/zero-knowledge - EXPECT_LE(number_of_gates_rollup, - circuit_gate_next_power_of_two::ROLLUP - waffle::ComposerBase::NUM_RESERVED_GATES) - << "You have exceeded the next power of two limit for the rollup circuit."; - } else { - EXPECT_LE(number_of_gates_rollup, - circuit_gate_next_power_of_two::ROLLUP - waffle::ComposerBase::NUM_RESERVED_GATES) - << "You have exceeded the next power of two limit for the rollup circuit."; } + // For the next power of two limit, we need to consider that we reserve four gates for adding + // randomness/zero-knowledge + EXPECT_LE(number_of_gates_rollup, GATES_NEXT_POWER_OF_TWO - waffle::ComposerBase::NUM_RESERVED_GATES) + << "You have exceeded the next power of two limit for the rollup circuit."; } HEAVY_TEST_F(rollup_full_tests, test_1_proof_in_2_rollup_full_proof) diff --git a/cpp/src/aztec/rollup/proofs/rollup/rollup_proof_data.hpp b/cpp/src/aztec/rollup/proofs/rollup/rollup_proof_data.hpp index b746221bf3..aee7bd7042 100644 --- a/cpp/src/aztec/rollup/proofs/rollup/rollup_proof_data.hpp +++ b/cpp/src/aztec/rollup/proofs/rollup/rollup_proof_data.hpp @@ -1,12 +1,12 @@ #pragma once -#include +#include #include "../../constants.hpp" namespace rollup { namespace proofs { namespace rollup { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; namespace RollupProofFields { enum { diff --git a/cpp/src/aztec/rollup/proofs/rollup/verify.cpp b/cpp/src/aztec/rollup/proofs/rollup/verify.cpp index 68fa30ef7b..a0da94e70a 100644 --- a/cpp/src/aztec/rollup/proofs/rollup/verify.cpp +++ b/cpp/src/aztec/rollup/proofs/rollup/verify.cpp @@ -5,7 +5,7 @@ namespace proofs { namespace rollup { using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; namespace { verify_result build_circuit(Composer& composer, rollup_tx& tx, circuit_data const& cd) diff --git a/cpp/src/aztec/rollup/proofs/rollup/verify.hpp b/cpp/src/aztec/rollup/proofs/rollup/verify.hpp index 6225b42234..8b92bcab73 100644 --- a/cpp/src/aztec/rollup/proofs/rollup/verify.hpp +++ b/cpp/src/aztec/rollup/proofs/rollup/verify.hpp @@ -6,7 +6,7 @@ namespace rollup { namespace proofs { namespace rollup { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; verify_result verify_logic(rollup_tx& tx, circuit_data const& cd); diff --git a/cpp/src/aztec/rollup/proofs/root_rollup/root_rollup_circuit.cpp b/cpp/src/aztec/rollup/proofs/root_rollup/root_rollup_circuit.cpp index eb26f81d6f..7388f708c2 100644 --- a/cpp/src/aztec/rollup/proofs/root_rollup/root_rollup_circuit.cpp +++ b/cpp/src/aztec/rollup/proofs/root_rollup/root_rollup_circuit.cpp @@ -16,7 +16,7 @@ namespace rollup { namespace proofs { namespace root_rollup { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; using namespace plonk::stdlib::recursion; using namespace plonk::stdlib::merkle_tree; using namespace notes; @@ -279,12 +279,11 @@ circuit_result_data root_rollup_circuit(Composer& composer, for (uint32_t i = 0; i < max_num_inner_proofs; ++i) { auto is_real = num_inner_proofs > i; - recursion_output = - verify_proof>(&composer, - recursive_verification_key, - recursive_manifest, - waffle::plonk_proof{ tx.rollups[i] }, - recursion_output); + recursion_output = verify_proof(&composer, + recursive_verification_key, + recursive_manifest, + waffle::plonk_proof{ tx.rollups[i] }, + recursion_output); auto& public_inputs = recursion_output.public_inputs; diff --git a/cpp/src/aztec/rollup/proofs/root_rollup/root_rollup_circuit.hpp b/cpp/src/aztec/rollup/proofs/root_rollup/root_rollup_circuit.hpp index 798a939253..953b1cf57b 100644 --- a/cpp/src/aztec/rollup/proofs/root_rollup/root_rollup_circuit.hpp +++ b/cpp/src/aztec/rollup/proofs/root_rollup/root_rollup_circuit.hpp @@ -2,13 +2,13 @@ #include "./root_rollup_tx.hpp" #include #include -#include +#include namespace rollup { namespace proofs { namespace root_rollup { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; using namespace plonk::stdlib::recursion; struct circuit_result_data { diff --git a/cpp/src/aztec/rollup/proofs/root_rollup/root_rollup_full.test.cpp b/cpp/src/aztec/rollup/proofs/root_rollup/root_rollup_full.test.cpp index 5a17998e62..b6e4d7b8fa 100644 --- a/cpp/src/aztec/rollup/proofs/root_rollup/root_rollup_full.test.cpp +++ b/cpp/src/aztec/rollup/proofs/root_rollup/root_rollup_full.test.cpp @@ -17,10 +17,14 @@ using namespace plonk::stdlib::merkle_tree; namespace { #ifdef CI +constexpr bool CIRCUIT_CHANGE_EXPECTED = false; bool persist = false; #else +// During development, if the circuit vk hash/gate count is expected to change, set the following to true. +constexpr bool CIRCUIT_CHANGE_EXPECTED = false; bool persist = false; #endif + std::shared_ptr srs; numeric::random::Engine* rand_engine = &numeric::random::get_debug_engine(true); fixtures::user_context user = fixtures::create_user_context(rand_engine); @@ -138,27 +142,25 @@ HEAVY_TEST_F(root_rollup_full_tests, test_root_rollup_3x2_and_detect_circuit_cha EXPECT_EQ(inner_data.public_value, fr(0)); EXPECT_EQ(inner_data.public_owner, fr(0)); EXPECT_EQ(inner_data.asset_id, fr(0)); + // The below assertions detect changes in the root rollup circuit + constexpr uint32_t CIRCUIT_GATE_COUNT = 5424685; + constexpr uint32_t GATES_NEXT_POWER_OF_TWO = 8388608; + const uint256_t VK_HASH("6f6d58bfe23a31ea15dcc612c6a96d89bf211a192f52386673a0af1ef0fd3745"); + size_t number_of_gates_root_rollup = result.number_of_gates; auto vk_hash_root_rollup = result.verification_key->sha256_hash(); - // If the below assertions fail, consider changing the variable is_circuit_change_expected to 1 in - // rollup/constants.hpp and see if atleast the next power of two limit is not exceeded. Please change the constant - // values accordingly and set is_circuit_change_expected to 0 in rollup/constants.hpp before merging. - if (!(circuit_gate_count::is_circuit_change_expected)) { - EXPECT_EQ(number_of_gates_root_rollup, circuit_gate_count::ROOT_ROLLUP) + + if (!CIRCUIT_CHANGE_EXPECTED) { + EXPECT_EQ(number_of_gates_root_rollup, CIRCUIT_GATE_COUNT) << "The gate count for the root rollup circuit is changed."; - EXPECT_EQ(from_buffer(vk_hash_root_rollup), circuit_vk_hash::ROOT_ROLLUP) + EXPECT_EQ(from_buffer(vk_hash_root_rollup), VK_HASH) << "The verification key hash for the root rollup circuit is changed."; - // For the next power of two limit, we need to consider that we reserve four gates for adding - // randomness/zero-knowledge - EXPECT_LE(number_of_gates_root_rollup, - circuit_gate_next_power_of_two::ROOT_ROLLUP - waffle::ComposerBase::NUM_RESERVED_GATES) - << "You have exceeded the next power of two limit for the root rollup circuit."; - } else { - EXPECT_LE(number_of_gates_root_rollup, - circuit_gate_next_power_of_two::ROOT_ROLLUP - waffle::ComposerBase::NUM_RESERVED_GATES) - << "You have exceeded the next power of two limit for the root rollup circuit."; } + // For the next power of two limit, we need to consider that we reserve four gates for adding + // randomness/zero-knowledge + EXPECT_LE(number_of_gates_root_rollup, GATES_NEXT_POWER_OF_TWO - waffle::ComposerBase::NUM_RESERVED_GATES) + << "You have exceeded the next power of two limit for the root rollup circuit."; } HEAVY_TEST_F(root_rollup_full_tests, test_root_rollup_2x3) @@ -212,7 +214,7 @@ HEAVY_TEST_F(root_rollup_full_tests, test_bad_js_proof_fails) Composer inner_composer = Composer(tx_rollup_cd.proving_key, tx_rollup_cd.verification_key, tx_rollup_cd.num_gates); rollup::pad_rollup_tx(inner_rollup_tx, tx_rollup_cd.num_txs, tx_rollup_cd.join_split_circuit_data.padding_proof); rollup::rollup_circuit(inner_composer, inner_rollup_tx, tx_rollup_cd.verification_keys, tx_rollup_cd.num_txs); - ASSERT_FALSE(inner_composer.failed); + ASSERT_FALSE(inner_composer.failed()); auto inner_prover = inner_composer.create_unrolled_prover(); auto inner_proof = inner_prover.construct_proof(); auto inner_verifier = inner_composer.create_unrolled_verifier(); @@ -233,7 +235,7 @@ HEAVY_TEST_F(root_rollup_full_tests, test_bad_js_proof_fails) root_rollup_cd.inner_rollup_circuit_data.rollup_size, root_rollup_cd.rollup_size, root_rollup_cd.inner_rollup_circuit_data.verification_key); - ASSERT_FALSE(root_composer.failed); + ASSERT_FALSE(root_composer.failed()); auto root_prover = root_composer.create_prover(); auto root_proof = root_prover.construct_proof(); auto root_verifier = root_composer.create_verifier(); diff --git a/cpp/src/aztec/rollup/proofs/root_rollup/root_rollup_proof_data.hpp b/cpp/src/aztec/rollup/proofs/root_rollup/root_rollup_proof_data.hpp index 6a52a6967e..c3c76eee92 100644 --- a/cpp/src/aztec/rollup/proofs/root_rollup/root_rollup_proof_data.hpp +++ b/cpp/src/aztec/rollup/proofs/root_rollup/root_rollup_proof_data.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include "../rollup/rollup_proof_data.hpp" #include "../../constants.hpp" @@ -7,7 +7,7 @@ namespace rollup { namespace proofs { namespace root_rollup { -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; struct root_rollup_proof_data { fr input_hash; diff --git a/cpp/src/aztec/rollup/proofs/root_rollup/verify.cpp b/cpp/src/aztec/rollup/proofs/root_rollup/verify.cpp index bea25b2870..0f37391e2f 100644 --- a/cpp/src/aztec/rollup/proofs/root_rollup/verify.cpp +++ b/cpp/src/aztec/rollup/proofs/root_rollup/verify.cpp @@ -7,7 +7,7 @@ namespace proofs { namespace root_rollup { using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; namespace { verify_result build_circuit(Composer& composer, root_rollup_tx& tx, circuit_data const& circuit_data) diff --git a/cpp/src/aztec/rollup/proofs/root_rollup/verify.hpp b/cpp/src/aztec/rollup/proofs/root_rollup/verify.hpp index 3223e84ed7..e14b70c6b2 100644 --- a/cpp/src/aztec/rollup/proofs/root_rollup/verify.hpp +++ b/cpp/src/aztec/rollup/proofs/root_rollup/verify.hpp @@ -8,7 +8,7 @@ namespace proofs { namespace root_rollup { using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; struct verify_result : ::rollup::proofs::verify_result { std::vector broadcast_data; diff --git a/cpp/src/aztec/rollup/proofs/root_verifier/root_verifier.test.cpp b/cpp/src/aztec/rollup/proofs/root_verifier/root_verifier.test.cpp index d6d96512eb..6c0b53e517 100644 --- a/cpp/src/aztec/rollup/proofs/root_verifier/root_verifier.test.cpp +++ b/cpp/src/aztec/rollup/proofs/root_verifier/root_verifier.test.cpp @@ -80,11 +80,12 @@ class root_verifier_tests : public ::testing::Test { srs, { root_rollup_cd.verification_key }, FIXTURE_PATH, - false, - false, - true, - false, - true); + false, // compute + false, // save + true, // load + false, // pk + true // vk + ); // create 1x2 key to use later root_rollup_cd_bad = root_rollup::get_circuit_data(2U, tx_rollup_cd, srs, FIXTURE_PATH, false, false, true, false, true); diff --git a/cpp/src/aztec/rollup/proofs/root_verifier/root_verifier_circuit.cpp b/cpp/src/aztec/rollup/proofs/root_verifier/root_verifier_circuit.cpp index e13495b0ba..949eca6ca1 100644 --- a/cpp/src/aztec/rollup/proofs/root_verifier/root_verifier_circuit.cpp +++ b/cpp/src/aztec/rollup/proofs/root_verifier/root_verifier_circuit.cpp @@ -15,8 +15,7 @@ recursion_output root_verifier_circuit( { recursion_output recursion_output; if (!valid_vks.size()) { - composer.failed = true; - composer.err = "Cannot build root verifier circuit with empty list of keys."; + composer.failure("Cannot build root verifier circuit with empty list of keys."); return recursion_output; } diff --git a/cpp/src/aztec/rollup/proofs/root_verifier/root_verifier_circuit.hpp b/cpp/src/aztec/rollup/proofs/root_verifier/root_verifier_circuit.hpp index 386c9e49eb..98c739a542 100644 --- a/cpp/src/aztec/rollup/proofs/root_verifier/root_verifier_circuit.hpp +++ b/cpp/src/aztec/rollup/proofs/root_verifier/root_verifier_circuit.hpp @@ -2,6 +2,7 @@ #include "./root_verifier_tx.hpp" #include #include +#include namespace rollup { namespace proofs { @@ -9,13 +10,24 @@ namespace root_verifier { using namespace plonk; -using InnerComposer = waffle::TurboComposer; +/** + * The InnerComposer is the composer used by the inner circuits, i.e. the rollup and root rollup circuits. + * Note that the InnerComposer can only be set to Turbo or Ultra. Although it can also work with Standard composer + * theoretically, the size of the rollup and root rollup circuits with standard composer would be humongous. + * As such, we don't need the InnerComposer to be standard in any case for efficiency reasons. + */ +typedef std:: + conditional_t + InnerComposer; using OuterComposer = waffle::StandardComposer; typedef stdlib::bn254 outer_curve; typedef stdlib::recursion::verification_key verification_key_pt; -typedef stdlib::recursion::recursive_turbo_verifier_settings recursive_settings; +typedef std::conditional_t, + stdlib::recursion::recursive_turbo_verifier_settings> + recursive_settings; struct circuit_outputs { stdlib::recursion::recursion_output recursion_output; diff --git a/cpp/src/aztec/rollup/proofs/root_verifier/root_verifier_full.test.cpp b/cpp/src/aztec/rollup/proofs/root_verifier/root_verifier_full.test.cpp index 603c9e45c6..dd414fdc6b 100644 --- a/cpp/src/aztec/rollup/proofs/root_verifier/root_verifier_full.test.cpp +++ b/cpp/src/aztec/rollup/proofs/root_verifier/root_verifier_full.test.cpp @@ -23,6 +23,13 @@ using namespace notes::native; using namespace plonk::stdlib::merkle_tree; namespace { +#ifdef CI +constexpr bool CIRCUIT_CHANGE_EXPECTED = false; +#else +// During development, if the circuit vk hash/gate count is expected to change, set the following to true. +constexpr bool CIRCUIT_CHANGE_EXPECTED = false; +#endif + std::shared_ptr srs; numeric::random::Engine* rand_engine = &numeric::random::get_debug_engine(true); fixtures::user_context user = fixtures::create_user_context(rand_engine); @@ -89,27 +96,26 @@ HEAVY_TEST_F(root_verifier_full_tests, good_data_passes_and_detect_circuit_chang auto tx = create_root_verifier_tx(); auto result = verify(tx, root_verifier_cd, root_rollup_cd); ASSERT_TRUE(result.verified); + // The below part detects changes in the root verifier circuit + constexpr uint32_t CIRCUIT_GATE_COUNT = 7158521; + constexpr uint32_t GATES_NEXT_POWER_OF_TWO = 8388608; + const uint256_t VK_HASH("8adecb7bd1be689ce8adb46192a9356ad42cb2310b08e55b9cb14708dd2eb85c"); + size_t number_of_gates_root_verifier = result.number_of_gates; auto vk_hash_root_verifier = result.verification_key->sha256_hash(); - // If the below assertions fail, consider changing the variable is_circuit_change_expected to 1 in - // rollup/constants.hpp and see if atleast the next power of two limit is not exceeded. Please change the constant - // values accordingly and set is_circuit_change_expected to 0 in rollup/constants.hpp before merging. - if (!(circuit_gate_count::is_circuit_change_expected)) { - EXPECT_EQ(number_of_gates_root_verifier, circuit_gate_count::ROOT_VERIFIER) + + if (!CIRCUIT_CHANGE_EXPECTED) { + EXPECT_EQ(number_of_gates_root_verifier, CIRCUIT_GATE_COUNT) << "The gate count for the root verifier circuit is changed."; - EXPECT_EQ(from_buffer(vk_hash_root_verifier), circuit_vk_hash::ROOT_VERIFIER) + EXPECT_EQ(from_buffer(vk_hash_root_verifier), VK_HASH) << "The verification key hash for the root verifier circuit is changed."; - // For the next power of two limit, we need to consider that we reserve four gates for adding - // randomness/zero-knowledge - EXPECT_LE(number_of_gates_root_verifier, - circuit_gate_next_power_of_two::ROOT_VERIFIER - waffle::ComposerBase::NUM_RESERVED_GATES) - << "You have exceeded the next power of two limit for the root verifier circuit."; - } else { - EXPECT_LE(number_of_gates_root_verifier, - circuit_gate_next_power_of_two::ROOT_VERIFIER - waffle::ComposerBase::NUM_RESERVED_GATES) - << "You have exceeded the next power of two limit for the root verifier circuit."; } + + // For the next power of two limit, we need to consider that we reserve four gates for adding + // randomness/zero-knowledge + EXPECT_LE(number_of_gates_root_verifier, GATES_NEXT_POWER_OF_TWO - waffle::ComposerBase::NUM_RESERVED_GATES) + << "You have exceeded the next power of two limit for the root verifier circuit."; } HEAVY_TEST_F(root_verifier_full_tests, bad_byte_failure) diff --git a/cpp/src/aztec/rollup/proofs/standard_example/c_bind.cpp b/cpp/src/aztec/rollup/proofs/standard_example/c_bind.cpp index d12ba2d167..bbb03c96f3 100644 --- a/cpp/src/aztec/rollup/proofs/standard_example/c_bind.cpp +++ b/cpp/src/aztec/rollup/proofs/standard_example/c_bind.cpp @@ -2,11 +2,10 @@ #include "standard_example.hpp" #include #include -#include +#include #include using namespace barretenberg; -using namespace plonk::stdlib::types::standard; #define WASM_EXPORT __attribute__((visibility("default"))) @@ -28,12 +27,12 @@ WASM_EXPORT void standard_example__init_verification_key(void* pippenger_ptr, ui WASM_EXPORT void* standard_example__new_prover() { auto prover = rollup::proofs::standard_example::new_prover(); - return new Prover(std::move(prover)); + return new waffle::Prover(std::move(prover)); } WASM_EXPORT void standard_example__delete_prover(void* prover) { - delete reinterpret_cast(prover); + delete reinterpret_cast(prover); } WASM_EXPORT bool standard_example__verify_proof(uint8_t* proof, uint32_t length) diff --git a/cpp/src/aztec/rollup/proofs/standard_example/c_bind.test.cpp b/cpp/src/aztec/rollup/proofs/standard_example/c_bind.test.cpp index 1e8064217c..089d74d563 100644 --- a/cpp/src/aztec/rollup/proofs/standard_example/c_bind.test.cpp +++ b/cpp/src/aztec/rollup/proofs/standard_example/c_bind.test.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include using namespace barretenberg; diff --git a/cpp/src/aztec/rollup/proofs/standard_example/standard_example.cpp b/cpp/src/aztec/rollup/proofs/standard_example/standard_example.cpp index 17ddae39af..dd897f5647 100644 --- a/cpp/src/aztec/rollup/proofs/standard_example/standard_example.cpp +++ b/cpp/src/aztec/rollup/proofs/standard_example/standard_example.cpp @@ -1,7 +1,8 @@ #include "standard_example.hpp" #include -#include #include +#include +#include namespace rollup { namespace proofs { @@ -9,6 +10,15 @@ namespace standard_example { using namespace plonk; +// Defining standard-plonk-specific types for standard example. +using Composer = waffle::StandardComposer; +using Prover = waffle::Prover; +using Verifier = waffle::Verifier; +using bool_ct = stdlib::bool_t; +using uint32_ct = stdlib::uint32; +using witness_ct = stdlib::witness_t; +using public_witness_ct = stdlib::public_witness_t; + static std::shared_ptr proving_key; static std::shared_ptr verification_key; @@ -34,8 +44,7 @@ void init_verification_key(std::unique_ptr&& crs } // Patch the 'nothing' reference string fed to init_proving_key. proving_key->reference_string = crs_factory->get_prover_crs(proving_key->n); - verification_key = - waffle::standard_composer::compute_verification_key(proving_key, crs_factory->get_verifier_crs()); + verification_key = Composer::compute_verification_key_base(proving_key, crs_factory->get_verifier_crs()); } Prover new_prover() diff --git a/cpp/src/aztec/rollup/proofs/standard_example/standard_example.hpp b/cpp/src/aztec/rollup/proofs/standard_example/standard_example.hpp index 612598eaf1..d38f32869e 100644 --- a/cpp/src/aztec/rollup/proofs/standard_example/standard_example.hpp +++ b/cpp/src/aztec/rollup/proofs/standard_example/standard_example.hpp @@ -1,20 +1,21 @@ #pragma once -#include -#include +#include +#include namespace rollup { namespace proofs { namespace standard_example { -using namespace plonk::stdlib::types::standard; +using Composer = waffle::StandardComposer; +using Prover = waffle::Prover; void init_proving_key(std::unique_ptr&& crs_factory); void init_verification_key(std::unique_ptr&& crs_factory); -void build_circuit(plonk::stdlib::types::standard::Composer& composer); +void build_circuit(Composer& composer); -plonk::stdlib::types::standard::Prover new_prover(); +Prover new_prover(); bool verify_proof(waffle::plonk_proof const& proof); diff --git a/cpp/src/aztec/rollup/proofs/standard_example/standard_example.test.cpp b/cpp/src/aztec/rollup/proofs/standard_example/standard_example.test.cpp index 719330a932..42c87cf092 100644 --- a/cpp/src/aztec/rollup/proofs/standard_example/standard_example.test.cpp +++ b/cpp/src/aztec/rollup/proofs/standard_example/standard_example.test.cpp @@ -4,15 +4,14 @@ #include using namespace barretenberg; -using namespace plonk::stdlib::types::standard; using namespace rollup::proofs::standard_example; TEST(standard_example_tests, test_standard_example) { - Composer composer = Composer("../srs_db/ignition"); + waffle::StandardComposer composer = waffle::StandardComposer("../srs_db/ignition"); build_circuit(composer); - Prover prover = composer.create_prover(); + waffle::Prover prover = composer.create_prover(); waffle::plonk_proof proof = prover.construct_proof(); std::cout << "gates: " << composer.get_num_gates() << std::endl; diff --git a/cpp/src/aztec/rollup/proofs/verify.hpp b/cpp/src/aztec/rollup/proofs/verify.hpp index ab2a4fa6d0..ec71093b98 100644 --- a/cpp/src/aztec/rollup/proofs/verify.hpp +++ b/cpp/src/aztec/rollup/proofs/verify.hpp @@ -46,9 +46,9 @@ auto verify_logic_internal(Composer& composer, Tx& tx, CircuitData const& cd, ch auto result = build_circuit(composer, tx, cd); info(name, ": Circuit built in ", timer.toString(), "s"); - if (composer.failed) { - info(name, ": Circuit logic failed: " + composer.err); - result.err = composer.err; + if (composer.failed()) { + info(name, ": Circuit logic failed: " + composer.err()); + result.err = composer.err(); return result; } @@ -85,9 +85,21 @@ auto verify_internal( if (!cd.mock) { if (unrolled) { - auto prover = composer.create_unrolled_prover(); - auto proof = prover.construct_proof(); - result.proof_data = proof.proof_data; + if constexpr (std::is_same::value) { + if (std::string(name) == "root rollup") { + auto prover = composer.create_unrolled_ultra_to_standard_prover(); + auto proof = prover.construct_proof(); + result.proof_data = proof.proof_data; + } else { + auto prover = composer.create_unrolled_prover(); + auto proof = prover.construct_proof(); + result.proof_data = proof.proof_data; + } + } else { + auto prover = composer.create_unrolled_prover(); + auto proof = prover.construct_proof(); + result.proof_data = proof.proof_data; + } } else { auto prover = composer.create_prover(); auto proof = prover.construct_proof(); @@ -97,9 +109,21 @@ auto verify_internal( Composer mock_proof_composer = Composer(cd.proving_key, cd.verification_key, cd.num_gates); ::rollup::proofs::mock::mock_circuit(mock_proof_composer, composer.get_public_inputs()); if (unrolled) { - auto prover = mock_proof_composer.create_unrolled_prover(); - auto proof = prover.construct_proof(); - result.proof_data = proof.proof_data; + if constexpr (std::is_same::value) { + if (std::string(name) == "root rollup") { + auto prover = mock_proof_composer.create_unrolled_ultra_to_standard_prover(); + auto proof = prover.construct_proof(); + result.proof_data = proof.proof_data; + } else { + auto prover = mock_proof_composer.create_unrolled_prover(); + auto proof = prover.construct_proof(); + result.proof_data = proof.proof_data; + } + } else { + auto prover = mock_proof_composer.create_unrolled_prover(); + auto proof = prover.construct_proof(); + result.proof_data = proof.proof_data; + } } else { auto prover = mock_proof_composer.create_prover(); auto proof = prover.construct_proof(); @@ -110,8 +134,18 @@ auto verify_internal( info(name, ": Proof created in ", proof_timer.toString(), "s"); info(name, ": Total time taken: ", timer.toString(), "s"); if (unrolled) { - auto verifier = composer.create_unrolled_verifier(); - result.verified = verifier.verify_proof({ result.proof_data }); + if constexpr (std::is_same::value) { + if (std::string(name) == "root rollup") { + auto verifier = composer.create_unrolled_ultra_to_standard_verifier(); + result.verified = verifier.verify_proof({ result.proof_data }); + } else { + auto verifier = composer.create_unrolled_verifier(); + result.verified = verifier.verify_proof({ result.proof_data }); + } + } else { + auto verifier = composer.create_unrolled_verifier(); + result.verified = verifier.verify_proof({ result.proof_data }); + } } else { auto verifier = composer.create_verifier(); result.verified = verifier.verify_proof({ result.proof_data }); diff --git a/cpp/src/aztec/rollup/rollup_cli/main.cpp b/cpp/src/aztec/rollup/rollup_cli/main.cpp index b5d79ba69a..4872272a66 100644 --- a/cpp/src/aztec/rollup/rollup_cli/main.cpp +++ b/cpp/src/aztec/rollup/rollup_cli/main.cpp @@ -17,7 +17,6 @@ #include #include -#include #include #include diff --git a/cpp/src/aztec/rollup/tx_factory/main.cpp b/cpp/src/aztec/rollup/tx_factory/main.cpp index 127f05af7a..385d752f71 100644 --- a/cpp/src/aztec/rollup/tx_factory/main.cpp +++ b/cpp/src/aztec/rollup/tx_factory/main.cpp @@ -12,7 +12,7 @@ using namespace ::rollup::proofs; using namespace ::rollup::fixtures; using namespace plonk::stdlib::merkle_tree; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; namespace tx_rollup = ::rollup::proofs::rollup; using WorldState = ::rollup::world_state::WorldState; diff --git a/cpp/src/aztec/srs/io.cpp b/cpp/src/aztec/srs/io.cpp index 2e22085a19..a84b5d24b7 100644 --- a/cpp/src/aztec/srs/io.cpp +++ b/cpp/src/aztec/srs/io.cpp @@ -8,16 +8,6 @@ namespace barretenberg { namespace io { -struct Manifest { - uint32_t transcript_number; - uint32_t total_transcripts; - uint32_t total_g1_points; - uint32_t total_g2_points; - uint32_t num_g1_points; - uint32_t num_g2_points; - uint32_t start_from; -}; - constexpr size_t BLAKE2B_CHECKSUM_LENGTH = 64; size_t get_transcript_size(const Manifest& manifest) @@ -141,20 +131,31 @@ std::string get_transcript_path(std::string const& dir, size_t num) return dir + "/transcript" + (num < 10 ? "0" : "") + std::to_string(num) + ".dat"; }; +std::string get_lagrange_transcript_path(std::string const& dir, size_t degree) +{ + auto log2_n = static_cast(numeric::get_msb(degree)); + auto new_degree = (1 << log2_n); + return dir + "/transcript_" + std::to_string(new_degree) + ".dat"; +}; + bool is_file_exist(std::string const& fileName) { std::ifstream infile(fileName); return infile.good(); } -void read_transcript_g1(g1::affine_element* monomials, size_t degree, std::string const& dir) +void read_transcript_g1(g1::affine_element* monomials, size_t degree, std::string const& dir, bool is_lagrange) { - // read g1 elements at second array position - first point is the basic generator - monomials[0] = g1::affine_one; - size_t num = 0; - size_t num_read = 1; - std::string path = get_transcript_path(dir, num); + size_t num_read = 0; + std::string path = get_lagrange_transcript_path(dir, degree); + if (!is_lagrange) { + // read g1 elements at second array position - first point is the basic generato + // This is true for monomial base srs. We start from index 0 for lagrange base transcript. + monomials[0] = g1::affine_one; + num_read = 1; + path = get_transcript_path(dir, num); + } while (is_file_exist(path) && num_read < degree) { Manifest manifest; @@ -176,12 +177,20 @@ void read_transcript_g1(g1::affine_element* monomials, size_t degree, std::strin path = get_transcript_path(dir, ++num); } - if (num_read < degree) { - throw_or_abort(format("Only read ", num_read, " points but require ", degree, ". Is your srs large enough?")); + const bool monomial_srs_condition = (num_read < degree) & (!is_lagrange); + if (monomial_srs_condition) { + throw_or_abort(format("Only read ", + num_read, + " points but require ", + degree, + ". Is your srs large enough? Either run bootstrap.sh to download the transcript.dat " + "files to `srs_db/ignition/`, or you might need to download extra transcript.dat files " + "by editing `srs_db/download_ignition.sh` (but be careful, as this suggests you've " + "just changed a circuit to exceed a new 'power of two' boundary).")); } } -void read_transcript_g2(g2::affine_element& g2_x, std::string const& dir) +void read_transcript_g2(g2::affine_element& g2_x, std::string const& dir, bool is_lagrange) { const size_t g2_size = sizeof(fq2) * 2; @@ -199,7 +208,7 @@ void read_transcript_g2(g2::affine_element& g2_x, std::string const& dir) } // Get transcript starting at g0.dat - path = get_transcript_path(dir, 0); + path = is_lagrange ? get_lagrange_transcript_path(dir, 2) : get_transcript_path(dir, 0); Manifest manifest; read_manifest(path, manifest); @@ -215,11 +224,127 @@ void read_transcript_g2(g2::affine_element& g2_x, std::string const& dir) byteswap(&g2_x, size); } -void read_transcript(g1::affine_element* monomials, g2::affine_element& g2_x, size_t degree, std::string const& path) +void read_transcript( + g1::affine_element* monomials, g2::affine_element& g2_x, size_t degree, std::string const& path, bool is_lagrange) +{ + read_transcript_g1(monomials, degree, path, is_lagrange); + read_transcript_g2(g2_x, path, is_lagrange); +} + +void write_buffer_to_file(std::string const& filename, char const* buffer, size_t buffer_size) +{ + std::ofstream file; + file.open(filename); + file.write(&buffer[0], (int)(buffer_size)); + file.close(); +} + +void write_g1_elements_to_buffer(g1::affine_element const* elements, char* buffer, size_t num_elements) { - read_transcript_g1(monomials, degree, path); - read_transcript_g2(g2_x, path); + uint64_t temp_x[4]; + uint64_t temp_y[4]; + fq temp_x_g1; + fq temp_y_g1; + + if (is_little_endian()) { + for (size_t i = 0; i < num_elements; ++i) { + size_t byte_position_1 = sizeof(fq) * i * 2; + size_t byte_position_2 = sizeof(fq) * (i * 2 + 1); + + temp_x_g1 = elements[i].x.from_montgomery_form(); + temp_y_g1 = elements[i].y.from_montgomery_form(); + + temp_x[0] = __builtin_bswap64(temp_x_g1.data[0]); + temp_x[1] = __builtin_bswap64(temp_x_g1.data[1]); + temp_x[2] = __builtin_bswap64(temp_x_g1.data[2]); + temp_x[3] = __builtin_bswap64(temp_x_g1.data[3]); + temp_y[0] = __builtin_bswap64(temp_y_g1.data[0]); + temp_y[1] = __builtin_bswap64(temp_y_g1.data[1]); + temp_y[2] = __builtin_bswap64(temp_y_g1.data[2]); + temp_y[3] = __builtin_bswap64(temp_y_g1.data[3]); + + memcpy((void*)(buffer + byte_position_1), (void*)temp_x, sizeof(fq)); + memcpy((void*)(buffer + byte_position_2), (void*)temp_y, sizeof(fq)); + } + } +} + +void write_g2_elements_to_buffer(g2::affine_element const* elements, char* buffer, size_t num_elements) +{ + uint64_t temp_x[8]; + uint64_t temp_y[8]; + fq temp_x_g2_1; + fq temp_x_g2_2; + fq temp_y_g2_1; + fq temp_y_g2_2; + + if (is_little_endian()) { + for (size_t i = 0; i < num_elements; ++i) { + size_t byte_position_1 = sizeof(fq) * (4 * i); + size_t byte_position_2 = sizeof(fq) * (4 * i + 2); + + temp_x_g2_1 = elements[i].x.c0.from_montgomery_form(); + temp_x_g2_2 = elements[i].x.c1.from_montgomery_form(); + temp_y_g2_1 = elements[i].y.c0.from_montgomery_form(); + temp_y_g2_2 = elements[i].y.c1.from_montgomery_form(); + + temp_x[0] = __builtin_bswap64(temp_x_g2_1.data[0]); + temp_x[1] = __builtin_bswap64(temp_x_g2_1.data[1]); + temp_x[2] = __builtin_bswap64(temp_x_g2_1.data[2]); + temp_x[3] = __builtin_bswap64(temp_x_g2_1.data[3]); + temp_x[4] = __builtin_bswap64(temp_x_g2_2.data[0]); + temp_x[5] = __builtin_bswap64(temp_x_g2_2.data[1]); + temp_x[6] = __builtin_bswap64(temp_x_g2_2.data[2]); + temp_x[7] = __builtin_bswap64(temp_x_g2_2.data[3]); + + temp_y[0] = __builtin_bswap64(temp_y_g2_1.data[0]); + temp_y[1] = __builtin_bswap64(temp_y_g2_1.data[1]); + temp_y[2] = __builtin_bswap64(temp_y_g2_1.data[2]); + temp_y[3] = __builtin_bswap64(temp_y_g2_1.data[3]); + temp_y[4] = __builtin_bswap64(temp_y_g2_2.data[0]); + temp_y[5] = __builtin_bswap64(temp_y_g2_2.data[1]); + temp_y[6] = __builtin_bswap64(temp_y_g2_2.data[2]); + temp_y[7] = __builtin_bswap64(temp_y_g2_2.data[3]); + + memcpy((void*)(buffer + byte_position_1), (void*)temp_x, 2 * sizeof(fq)); + memcpy((void*)(buffer + byte_position_2), (void*)temp_y, 2 * sizeof(fq)); + } + } +} + +void write_transcript(g1::affine_element const* g1_x, + g2::affine_element const* g2_x, + Manifest const& manifest, + std::string const& dir, + bool is_lagrange) +{ + const size_t num_g1_x = manifest.num_g1_points; + const size_t num_g2_x = manifest.num_g2_points; + const size_t manifest_size = sizeof(Manifest); + const size_t g1_buffer_size = sizeof(fq) * 2 * num_g1_x; + const size_t g2_buffer_size = sizeof(fq) * 4 * num_g2_x; + const size_t transcript_size = manifest_size + g1_buffer_size + g2_buffer_size; + std::string path = is_lagrange ? get_lagrange_transcript_path(dir, num_g1_x) : get_transcript_path(dir, 0); + // const size_t transcript_size = manifest_size + g1_buffer_size + g2_buffer_size + + // checksum::BLAKE2B_CHECKSUM_LENGTH; + std::vector buffer(transcript_size); + + Manifest net_manifest; + net_manifest.transcript_number = htonl(manifest.transcript_number); + net_manifest.total_transcripts = htonl(manifest.total_transcripts); + net_manifest.total_g1_points = htonl(manifest.total_g1_points); + net_manifest.total_g2_points = htonl(manifest.total_g2_points); + net_manifest.num_g1_points = htonl(manifest.num_g1_points); + net_manifest.num_g2_points = htonl(manifest.num_g2_points); + net_manifest.start_from = htonl(manifest.start_from); + + std::copy(&net_manifest, &net_manifest + 1, (Manifest*)&buffer[0]); + + write_g1_elements_to_buffer(g1_x, &buffer[manifest_size], num_g1_x); + write_g2_elements_to_buffer(g2_x, &buffer[manifest_size + g1_buffer_size], num_g2_x); + // add_checksum_to_buffer(&buffer[0], manifest_size + g1_buffer_size + g2_buffer_size); + write_buffer_to_file(path, &buffer[0], transcript_size); } } // namespace io -} // namespace barretenberg +} // namespace barretenberg \ No newline at end of file diff --git a/cpp/src/aztec/srs/io.hpp b/cpp/src/aztec/srs/io.hpp index 24006bff5b..d3879f38ec 100644 --- a/cpp/src/aztec/srs/io.hpp +++ b/cpp/src/aztec/srs/io.hpp @@ -7,11 +7,25 @@ namespace barretenberg { namespace io { -void read_transcript_g1(g1::affine_element* monomials, size_t degree, std::string const& dir); +struct Manifest { + uint32_t transcript_number; + uint32_t total_transcripts; + uint32_t total_g1_points; + uint32_t total_g2_points; + uint32_t num_g1_points; + uint32_t num_g2_points; + uint32_t start_from; +}; -void read_transcript_g2(g2::affine_element& g2_x, std::string const& dir); +void read_transcript_g1(g1::affine_element* monomials, size_t degree, std::string const& dir, bool is_lagrange = false); -void read_transcript(g1::affine_element* monomials, g2::affine_element& g2_x, size_t degree, std::string const& path); +void read_transcript_g2(g2::affine_element& g2_x, std::string const& dir, bool is_lagrange = false); + +void read_transcript(g1::affine_element* monomials, + g2::affine_element& g2_x, + size_t degree, + std::string const& path, + bool is_lagrange = false); void read_g1_elements_from_buffer(g1::affine_element* elements, char const* buffer, size_t buffer_size); void byteswap(g1::affine_element* elements, size_t buffer_size); @@ -19,5 +33,17 @@ void byteswap(g1::affine_element* elements, size_t buffer_size); void read_g2_elements_from_buffer(g2::affine_element* elements, char const* buffer, size_t buffer_size); void byteswap(g2::affine_element* elements, size_t buffer_size); +void write_buffer_to_file(std::string const& filename, char const* buffer, size_t buffer_size); + +void write_g1_elements_to_buffer(g1::affine_element const* elements, char* buffer, size_t num_elements); + +void write_g2_elements_to_buffer(g2::affine_element const* elements, char* buffer, size_t num_elements); + +void write_transcript(g1::affine_element const* g1_x, + g2::affine_element const* g2_x, + Manifest const& manifest, + std::string const& dir, + bool is_lagrange = false); + } // namespace io } // namespace barretenberg diff --git a/cpp/src/aztec/srs/lagrange_base_transformation/lagrange_base.test.cpp b/cpp/src/aztec/srs/lagrange_base_transformation/lagrange_base.test.cpp index d63a59564b..28d471186e 100644 --- a/cpp/src/aztec/srs/lagrange_base_transformation/lagrange_base.test.cpp +++ b/cpp/src/aztec/srs/lagrange_base_transformation/lagrange_base.test.cpp @@ -1,14 +1,17 @@ -#include +#include + +#include +#include +#include +#include +#include +#include +#include -#include "../../ecc/curves/bn254/g1.hpp" -#include "../../ecc/curves/bn254/g2.hpp" -#include "../../ecc/curves/bn254/fq12.hpp" -#include "../../ecc/curves/bn254/pairing.hpp" -#include "../../ecc/curves/bn254/scalar_multiplication/scalar_multiplication.hpp" #include "lagrange_base.hpp" -#include "../../polynomials/polynomial.hpp" -#include "../../plonk/reference_string/file_reference_string.hpp" -#include "srs/io.hpp" +#include "../io.hpp" + +#include using namespace barretenberg; @@ -59,7 +62,7 @@ TEST(lagrange_base, verify_lagrange_base_transformation) expected = expected.normalize(); result = result.normalize(); - EXPECT_EQ(result == expected, true); + EXPECT_EQ(result, expected); } TEST(lagrange_base, verify_lagrange_base_transformation_on_rand_poly) @@ -108,30 +111,31 @@ TEST(lagrange_base, verify_lagrange_base_transformation_on_rand_poly) expected = expected.normalize(); result = result.normalize(); - EXPECT_EQ(result == expected, true); + EXPECT_EQ(result, expected); } TEST(lagrange_base, verify_lagrange_base_import_srs) { - constexpr size_t degree = 1 << 2; + constexpr size_t degree = 1 << 4; // step 1: create monomial base srs // step 2: create lagrange base srs // step 3: create polynomial // step 4: commit to poly over both reference strings // step 5: very correctness - auto reference_string = std::make_shared(degree, "../srs_db/ignition"); - std::vector monomial_srs(degree * 2); - std::vector lagrange_base_srs(degree * 2); - lagrange_base::transform_srs(reference_string->get_monomials(), &lagrange_base_srs[0], degree); + std::array monomial_srs; + barretenberg::io::read_transcript_g1(&monomial_srs[0], degree, "../srs_db/ignition"); + + std::array lagrange_base_srs; + lagrange_base::transform_srs(&monomial_srs[0], &lagrange_base_srs[0], degree); barretenberg::evaluation_domain domain(degree); domain.compute_lookup_table(); + barretenberg::polynomial test_polynomial(degree); for (size_t i = 0; i < degree; ++i) { test_polynomial[i] = fr::random_element(); - monomial_srs[i] = reference_string->get_monomials()[i]; } barretenberg::polynomial lagrange_base_polynomial(test_polynomial); @@ -147,5 +151,59 @@ TEST(lagrange_base, verify_lagrange_base_import_srs) expected = expected.normalize(); result = result.normalize(); - EXPECT_EQ(result == expected, true); + EXPECT_EQ(result, expected); +} + +void test_lagrange_transcripts_helper(const size_t degree, + const std::string monomial_path, + const std::string lagrange_path) +{ + auto lagrange_reference_string = scalar_multiplication::Pippenger(lagrange_path, degree, true); + auto monomial_reference_string = scalar_multiplication::Pippenger(monomial_path, degree, false); + + barretenberg::evaluation_domain domain(degree); + domain.compute_lookup_table(); + + barretenberg::polynomial test_polynomial(degree); + for (size_t i = 0; i < degree; ++i) { + test_polynomial[i] = fr::random_element(); + } + barretenberg::polynomial lagrange_base_polynomial(test_polynomial); + lagrange_base_polynomial.fft(domain); + + g1::affine_element expected = monomial_reference_string.pippenger_unsafe(&test_polynomial[0], 0, degree); + g1::affine_element result = lagrange_reference_string.pippenger_unsafe(&lagrange_base_polynomial[0], 0, degree); + + EXPECT_EQ(result, expected); + + g2::affine_element lagrange_g2x; + g2::affine_element monomial_g2x; + barretenberg::io::read_transcript_g2(lagrange_g2x, lagrange_path, true); + barretenberg::io::read_transcript_g2(monomial_g2x, monomial_path, false); + + EXPECT_EQ(lagrange_g2x, monomial_g2x); +} + +/** + * The following tests ensure the correctness of the Lagrange transcripts downloaded using + * `download_ignition_lagrange.sh`. These need to be run only once after you download the transcripts (IFF you want to + * verify their correctness). Also, the `num_files` is set to 16 which checks transcripts upto size 2^16. You can change + * it to 24 if you wish to check all the transcripts (Warning: it'll a lot of time for size > 2^{20}). + */ +HEAVY_TEST(lagrange_base, test_local_lagrange_transcripts) +{ + // Setup monomial srs + // TODO: Not sure if this is a test that should be run every time + // We can check a single one to see + const size_t num_files = 4; + + for (size_t i = 0; i < num_files; i++) { + const size_t degree = static_cast(1 << (i + 1)); + auto begin = std::chrono::steady_clock::now(); + test_lagrange_transcripts_helper(degree, "../srs_db/ignition", "../srs_db/lagrange"); + auto end = std::chrono::steady_clock::now(); + + std::cout << "Verified Lagrange transcript of size " << degree << " in " + << std::chrono::duration_cast(end - begin).count() << " ms" << std::endl; + } } \ No newline at end of file diff --git a/cpp/src/aztec/plonk/reference_string/file_reference_string.cpp b/cpp/src/aztec/srs/reference_string/file_reference_string.cpp similarity index 62% rename from cpp/src/aztec/plonk/reference_string/file_reference_string.cpp rename to cpp/src/aztec/srs/reference_string/file_reference_string.cpp index 769f33bb8a..265c553a51 100644 --- a/cpp/src/aztec/plonk/reference_string/file_reference_string.cpp +++ b/cpp/src/aztec/srs/reference_string/file_reference_string.cpp @@ -1,18 +1,16 @@ #include "file_reference_string.hpp" -#include -#include +#include "../io.hpp" -#ifndef NO_MULTITHREADING -#include -#endif +#include namespace waffle { -VerifierFileReferenceString::VerifierFileReferenceString(std::string const& path) +VerifierFileReferenceString::VerifierFileReferenceString(std::string const& path, bool is_lagrange) + : precomputed_g2_lines( + (barretenberg::pairing::miller_lines*)(aligned_alloc(64, sizeof(barretenberg::pairing::miller_lines) * 2))) { - precomputed_g2_lines = - (barretenberg::pairing::miller_lines*)(aligned_alloc(64, sizeof(barretenberg::pairing::miller_lines) * 2)); - barretenberg::io::read_transcript_g2(g2_x, path); + + barretenberg::io::read_transcript_g2(g2_x, path, is_lagrange); barretenberg::pairing::precompute_miller_lines(barretenberg::g2::one, precomputed_g2_lines[0]); barretenberg::pairing::precompute_miller_lines(g2_x, precomputed_g2_lines[1]); } diff --git a/cpp/src/aztec/plonk/reference_string/file_reference_string.hpp b/cpp/src/aztec/srs/reference_string/file_reference_string.hpp similarity index 65% rename from cpp/src/aztec/plonk/reference_string/file_reference_string.hpp rename to cpp/src/aztec/srs/reference_string/file_reference_string.hpp index 080535d556..5a6b9776a7 100644 --- a/cpp/src/aztec/plonk/reference_string/file_reference_string.hpp +++ b/cpp/src/aztec/srs/reference_string/file_reference_string.hpp @@ -3,16 +3,17 @@ */ #pragma once #include "reference_string.hpp" -#include + #include #include #include -namespace barretenberg { -namespace pairing { +#include +#include + +namespace barretenberg::pairing { struct miller_lines; -} -} // namespace barretenberg +} // namespace barretenberg::pairing namespace waffle { @@ -20,12 +21,12 @@ using namespace barretenberg; class VerifierFileReferenceString : public VerifierReferenceString { public: - VerifierFileReferenceString(std::string const& path); + VerifierFileReferenceString(std::string const& path, bool is_lagrange = false); ~VerifierFileReferenceString(); - g2::affine_element get_g2x() const { return g2_x; } + g2::affine_element get_g2x() const override { return g2_x; } - pairing::miller_lines const* get_precomputed_g2_lines() const { return precomputed_g2_lines; } + pairing::miller_lines const* get_precomputed_g2_lines() const override { return precomputed_g2_lines; } private: g2::affine_element g2_x; @@ -34,14 +35,14 @@ class VerifierFileReferenceString : public VerifierReferenceString { class FileReferenceString : public ProverReferenceString { public: - FileReferenceString(const size_t num_points, std::string const& path) + FileReferenceString(const size_t num_points, std::string const& path, bool is_lagrange = false) : n(num_points) - , pippenger_(path, num_points) + , pippenger_(path, num_points, is_lagrange) {} - g1::affine_element* get_monomials() { return pippenger_.get_point_table(); } + g1::affine_element* get_monomials() override { return pippenger_.get_point_table(); } - size_t get_size() { return n; } + size_t get_size() const override { return n; } private: size_t n; @@ -50,50 +51,54 @@ class FileReferenceString : public ProverReferenceString { class FileReferenceStringFactory : public ReferenceStringFactory { public: - FileReferenceStringFactory(std::string const& path) - : path_(path) + FileReferenceStringFactory(std::string path, bool is_lagrange = false) + : path_(std::move(path)) + , is_lagrange_(is_lagrange) {} FileReferenceStringFactory(FileReferenceStringFactory&& other) = default; - std::shared_ptr get_prover_crs(size_t degree) + std::shared_ptr get_prover_crs(size_t degree) override { - return std::make_shared(degree, path_); + return std::make_shared(degree, path_, is_lagrange_); } - std::shared_ptr get_verifier_crs() + std::shared_ptr get_verifier_crs() override { - return std::make_shared(path_); + return std::make_shared(path_, is_lagrange_); } private: std::string path_; + bool is_lagrange_; }; class DynamicFileReferenceStringFactory : public ReferenceStringFactory { public: - DynamicFileReferenceStringFactory(std::string const& path, size_t initial_degree = 0) - : path_(path) + DynamicFileReferenceStringFactory(std::string path, size_t initial_degree = 0, bool is_lagrange = false) + : path_(std::move(path)) , degree_(initial_degree) - , verifier_crs_(std::make_shared(path_)) + , is_lagrange_(is_lagrange) + , verifier_crs_(std::make_shared(path_, is_lagrange)) {} DynamicFileReferenceStringFactory(DynamicFileReferenceStringFactory&& other) = default; - std::shared_ptr get_prover_crs(size_t degree) + std::shared_ptr get_prover_crs(size_t degree) override { if (degree > degree_) { - prover_crs_ = std::make_shared(degree, path_); + prover_crs_ = std::make_shared(degree, path_, is_lagrange_); degree_ = degree; } return prover_crs_; } - std::shared_ptr get_verifier_crs() { return verifier_crs_; } + std::shared_ptr get_verifier_crs() override { return verifier_crs_; } private: std::string path_; size_t degree_; + bool is_lagrange_; std::shared_ptr prover_crs_; std::shared_ptr verifier_crs_; }; diff --git a/cpp/src/aztec/plonk/reference_string/mem_reference_string.cpp b/cpp/src/aztec/srs/reference_string/mem_reference_string.cpp similarity index 70% rename from cpp/src/aztec/plonk/reference_string/mem_reference_string.cpp rename to cpp/src/aztec/srs/reference_string/mem_reference_string.cpp index d70c937c1a..c18967e9c5 100644 --- a/cpp/src/aztec/plonk/reference_string/mem_reference_string.cpp +++ b/cpp/src/aztec/srs/reference_string/mem_reference_string.cpp @@ -1,21 +1,18 @@ #include "mem_reference_string.hpp" +#include "../io.hpp" + #include #include -#include -#include -#ifndef NO_MULTITHREADING -#include -#endif +#include namespace waffle { -VerifierMemReferenceString::VerifierMemReferenceString(uint8_t const* buffer) +VerifierMemReferenceString::VerifierMemReferenceString(uint8_t const* g2x) + : precomputed_g2_lines( + (barretenberg::pairing::miller_lines*)(aligned_alloc(64, sizeof(barretenberg::pairing::miller_lines) * 2))) { - barretenberg::io::read_g2_elements_from_buffer(&g2_x, (char*)buffer, 128); - - precomputed_g2_lines = - (barretenberg::pairing::miller_lines*)(aligned_alloc(64, sizeof(barretenberg::pairing::miller_lines) * 2)); + barretenberg::io::read_g2_elements_from_buffer(&g2_x, (char*)g2x, 128); barretenberg::pairing::precompute_miller_lines(barretenberg::g2::one, precomputed_g2_lines[0]); barretenberg::pairing::precompute_miller_lines(g2_x, precomputed_g2_lines[1]); diff --git a/cpp/src/aztec/plonk/reference_string/mem_reference_string.hpp b/cpp/src/aztec/srs/reference_string/mem_reference_string.hpp similarity index 72% rename from cpp/src/aztec/plonk/reference_string/mem_reference_string.hpp rename to cpp/src/aztec/srs/reference_string/mem_reference_string.hpp index d72a57b277..e6d672649d 100644 --- a/cpp/src/aztec/plonk/reference_string/mem_reference_string.hpp +++ b/cpp/src/aztec/srs/reference_string/mem_reference_string.hpp @@ -2,14 +2,14 @@ * Create reference strings given a buffer containing network formatted g1 or g2 points. */ #pragma once + #include "reference_string.hpp" + #include -namespace barretenberg { -namespace pairing { +namespace barretenberg::pairing { struct miller_lines; -} -} // namespace barretenberg +} // namespace barretenberg::pairing namespace waffle { @@ -18,11 +18,11 @@ using namespace barretenberg; class VerifierMemReferenceString : public VerifierReferenceString { public: VerifierMemReferenceString(uint8_t const* g2x); - ~VerifierMemReferenceString(); + ~VerifierMemReferenceString() override; - g2::affine_element get_g2x() const { return g2_x; } + g2::affine_element get_g2x() const override { return g2_x; } - pairing::miller_lines const* get_precomputed_g2_lines() const { return precomputed_g2_lines; } + pairing::miller_lines const* get_precomputed_g2_lines() const override { return precomputed_g2_lines; } private: g2::affine_element g2_x; diff --git a/cpp/src/aztec/plonk/reference_string/mem_reference_string.test.cpp b/cpp/src/aztec/srs/reference_string/mem_reference_string.test.cpp similarity index 99% rename from cpp/src/aztec/plonk/reference_string/mem_reference_string.test.cpp rename to cpp/src/aztec/srs/reference_string/mem_reference_string.test.cpp index ba3f358117..642d3b2066 100644 --- a/cpp/src/aztec/plonk/reference_string/mem_reference_string.test.cpp +++ b/cpp/src/aztec/srs/reference_string/mem_reference_string.test.cpp @@ -1,9 +1,12 @@ #include "file_reference_string.hpp" #include "mem_reference_string.hpp" + #include -#include + #include +#include + TEST(reference_string, mem_file_consistency) { std::ifstream transcript; diff --git a/cpp/src/aztec/plonk/reference_string/pippenger_reference_string.hpp b/cpp/src/aztec/srs/reference_string/pippenger_reference_string.hpp similarity index 79% rename from cpp/src/aztec/plonk/reference_string/pippenger_reference_string.hpp rename to cpp/src/aztec/srs/reference_string/pippenger_reference_string.hpp index ba8b7e3d5a..a0f71fc3c3 100644 --- a/cpp/src/aztec/plonk/reference_string/pippenger_reference_string.hpp +++ b/cpp/src/aztec/srs/reference_string/pippenger_reference_string.hpp @@ -5,11 +5,9 @@ #pragma once #include "mem_reference_string.hpp" -namespace barretenberg { -namespace pairing { +namespace barretenberg::pairing { struct miller_lines; -} -} // namespace barretenberg +} // namespace barretenberg::pairing namespace waffle { @@ -21,8 +19,8 @@ class PippengerReferenceString : public ProverReferenceString { : pippenger_(pippenger) {} - size_t get_size() { return pippenger_->get_num_points(); } - g1::affine_element* get_monomials() { return pippenger_->get_point_table(); } + size_t get_size() const override { return pippenger_->get_num_points(); } + g1::affine_element* get_monomials() override { return pippenger_->get_point_table(); } private: scalar_multiplication::Pippenger* pippenger_; @@ -37,13 +35,13 @@ class PippengerReferenceStringFactory : public ReferenceStringFactory { PippengerReferenceStringFactory(PippengerReferenceStringFactory&& other) = default; - std::shared_ptr get_prover_crs(size_t degree) + std::shared_ptr get_prover_crs(size_t degree) override { ASSERT(degree <= pippenger_->get_num_points()); return std::make_shared(pippenger_); } - std::shared_ptr get_verifier_crs() + std::shared_ptr get_verifier_crs() override { return std::make_shared(g2x_); } diff --git a/cpp/src/aztec/plonk/reference_string/reference_string.hpp b/cpp/src/aztec/srs/reference_string/reference_string.hpp similarity index 75% rename from cpp/src/aztec/plonk/reference_string/reference_string.hpp rename to cpp/src/aztec/srs/reference_string/reference_string.hpp index 5f1e160d94..6508d12b25 100644 --- a/cpp/src/aztec/plonk/reference_string/reference_string.hpp +++ b/cpp/src/aztec/srs/reference_string/reference_string.hpp @@ -1,20 +1,21 @@ #pragma once + #include -#include #include #include -namespace barretenberg { -namespace pairing { +#include + +namespace barretenberg::pairing { struct miller_lines; -} -} // namespace barretenberg +} // namespace barretenberg::pairing namespace waffle { class VerifierReferenceString { public: - virtual ~VerifierReferenceString(){}; + virtual ~VerifierReferenceString() = default; + ; virtual barretenberg::g2::affine_element get_g2x() const = 0; @@ -23,17 +24,18 @@ class VerifierReferenceString { class ProverReferenceString { public: - virtual ~ProverReferenceString(){}; + virtual ~ProverReferenceString() = default; + ; virtual barretenberg::g1::affine_element* get_monomials() = 0; - virtual size_t get_size() = 0; + virtual size_t get_size() const = 0; }; class ReferenceStringFactory { public: ReferenceStringFactory() = default; ReferenceStringFactory(ReferenceStringFactory&& other) = default; - virtual ~ReferenceStringFactory() {} + virtual ~ReferenceStringFactory() = default; virtual std::shared_ptr get_prover_crs(size_t) { return nullptr; } virtual std::shared_ptr get_verifier_crs() { return nullptr; } }; diff --git a/cpp/src/aztec/stdlib/encryption/aes128/aes128.cpp b/cpp/src/aztec/stdlib/encryption/aes128/aes128.cpp index 8ff50498d7..76b996b9a2 100644 --- a/cpp/src/aztec/stdlib/encryption/aes128/aes128.cpp +++ b/cpp/src/aztec/stdlib/encryption/aes128/aes128.cpp @@ -1,6 +1,6 @@ #include "./aes128.hpp" -#include +#include #include #include @@ -16,37 +16,37 @@ namespace stdlib { namespace aes128 { constexpr uint32_t AES128_BASE = 9; -typedef plonk::stdlib::field_t field_t; -typedef plonk::stdlib::witness_t witness_t; +typedef plonk::stdlib::field_t field_t; +typedef plonk::stdlib::witness_t witness_t; typedef std::pair byte_pair; -field_t normalize_sparse_form(waffle::PlookupComposer*, field_t& byte) +field_t normalize_sparse_form(waffle::UltraComposer*, field_t& byte) { - auto result = plookup::read_from_1_to_2_table(waffle::AES_NORMALIZE, byte); + auto result = plookup_read::read_from_1_to_2_table(AES_NORMALIZE, byte); return result; } -byte_pair apply_aes_sbox_map(waffle::PlookupComposer*, field_t& input) +byte_pair apply_aes_sbox_map(waffle::UltraComposer*, field_t& input) { - return plookup::read_pair_from_table(waffle::AES_SBOX, input); + return plookup_read::read_pair_from_table(AES_SBOX, input); } -std::array convert_into_sparse_bytes(waffle::PlookupComposer*, const field_t& block_data) +std::array convert_into_sparse_bytes(waffle::UltraComposer*, const field_t& block_data) { // `block_data` must be a 128 bit variable std::array sparse_bytes; - auto sequence = plookup::read_sequence_from_table(waffle::AES_INPUT, block_data); + auto lookup = plookup_read::get_lookup_accumulators(AES_INPUT, block_data); for (size_t i = 0; i < 16; ++i) { - sparse_bytes[15 - i] = sequence[1][i]; + sparse_bytes[15 - i] = lookup[ColumnIdx::C2][i]; } return sparse_bytes; } -field_t convert_from_sparse_bytes(waffle::PlookupComposer* ctx, field_t* sparse_bytes) +field_t convert_from_sparse_bytes(waffle::UltraComposer* ctx, field_t* sparse_bytes) { std::array bytes; @@ -60,16 +60,16 @@ field_t convert_from_sparse_bytes(waffle::PlookupComposer* ctx, field_t* sparse_ field_t result = witness_t(ctx, fr(accumulator)); - const auto indices = plookup::read_sequence_from_table(waffle::AES_INPUT, result); + const auto lookup = plookup_read::get_lookup_accumulators(AES_INPUT, result); for (size_t i = 0; i < 16; ++i) { - sparse_bytes[15 - i].assert_equal(indices[1][i]); + sparse_bytes[15 - i].assert_equal(lookup[ColumnIdx::C2][i]); } return result; } -std::array expand_key(waffle::PlookupComposer* ctx, const field_t& key) +std::array expand_key(waffle::UltraComposer* ctx, const field_t& key) { constexpr uint8_t round_constants[11] = { 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; std::array sparse_round_constants{ @@ -210,7 +210,7 @@ void mix_columns_and_add_round_key(byte_pair* state_pairs, field_t* round_key, u mix_column_and_add_round_key(state_pairs + 12, round_key + 12, round); } -void sub_bytes(waffle::PlookupComposer* ctx, byte_pair* state_pairs) +void sub_bytes(waffle::UltraComposer* ctx, byte_pair* state_pairs) { for (size_t i = 0; i < 16; ++i) { state_pairs[i] = apply_aes_sbox_map(ctx, state_pairs[i].first); @@ -233,7 +233,7 @@ void xor_with_iv(byte_pair* state, field_t* iv) } } -void aes128_cipher(waffle::PlookupComposer* ctx, byte_pair* state, field_t* sparse_round_key) +void aes128_cipher(waffle::UltraComposer* ctx, byte_pair* state, field_t* sparse_round_key) { add_round_key(state, sparse_round_key, 0); for (size_t i = 0; i < 16; ++i) { @@ -256,7 +256,7 @@ void aes128_cipher(waffle::PlookupComposer* ctx, byte_pair* state, field_t* spar std::vector encrypt_buffer_cbc(const std::vector& input, const field_t& iv, const field_t& key) { - waffle::PlookupComposer* ctx = key.get_context(); + waffle::UltraComposer* ctx = key.get_context(); auto round_key = expand_key(ctx, key); diff --git a/cpp/src/aztec/stdlib/encryption/aes128/aes128.hpp b/cpp/src/aztec/stdlib/encryption/aes128/aes128.hpp index 27ab5fe54d..9ba7b1c08e 100644 --- a/cpp/src/aztec/stdlib/encryption/aes128/aes128.hpp +++ b/cpp/src/aztec/stdlib/encryption/aes128/aes128.hpp @@ -13,10 +13,9 @@ namespace stdlib { namespace aes128 { -std::vector> encrypt_buffer_cbc( - const std::vector>& input, - const field_t& iv, - const field_t& key); +std::vector> encrypt_buffer_cbc(const std::vector>& input, + const field_t& iv, + const field_t& key); } // namespace aes128 } // namespace stdlib diff --git a/cpp/src/aztec/stdlib/encryption/aes128/aes128.test.cpp b/cpp/src/aztec/stdlib/encryption/aes128/aes128.test.cpp index bb0c82dfb4..e2af73ca8b 100644 --- a/cpp/src/aztec/stdlib/encryption/aes128/aes128.test.cpp +++ b/cpp/src/aztec/stdlib/encryption/aes128/aes128.test.cpp @@ -1,6 +1,6 @@ #include "aes128.hpp" -#include +#include #include @@ -11,8 +11,8 @@ using namespace plonk; TEST(stdlib_aes128, encrypt_64_bytes) { - typedef plonk::stdlib::field_t field_pt; - typedef plonk::stdlib::witness_t witness_pt; + typedef plonk::stdlib::field_t field_pt; + typedef plonk::stdlib::witness_t witness_pt; uint8_t key[16]{ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c }; uint8_t out[64]{ 0x76, 0x49, 0xab, 0xac, 0x81, 0x19, 0xb2, 0x46, 0xce, 0xe9, 0x8e, 0x9b, 0x12, 0xe9, 0x19, 0x7d, @@ -34,7 +34,7 @@ TEST(stdlib_aes128, encrypt_64_bytes) return converted; }; - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); std::vector in_field{ witness_pt(&composer, fr(convert_bytes(in))), @@ -58,8 +58,8 @@ TEST(stdlib_aes128, encrypt_64_bytes) std::cout << "composer gates = " << composer.get_num_gates() << std::endl; - waffle::PlookupProver prover = composer.create_prover(); - waffle::PlookupVerifier verifier = composer.create_verifier(); + waffle::UltraProver prover = composer.create_prover(); + waffle::UltraVerifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool proof_result = verifier.verify_proof(proof); EXPECT_EQ(proof_result, true); diff --git a/cpp/src/aztec/stdlib/encryption/ecdsa/ecdsa.hpp b/cpp/src/aztec/stdlib/encryption/ecdsa/ecdsa.hpp index 0213e36a94..bdd67f46b4 100644 --- a/cpp/src/aztec/stdlib/encryption/ecdsa/ecdsa.hpp +++ b/cpp/src/aztec/stdlib/encryption/ecdsa/ecdsa.hpp @@ -12,8 +12,7 @@ template struct signature { stdlib::byte_array s; }; -// (ノಠ益ಠ)ノ彡┻━┻ -template +template bool_t verify_signature(const stdlib::byte_array& message, const G1& public_key, const signature& sig); diff --git a/cpp/src/aztec/stdlib/encryption/ecdsa/ecdsa.test.cpp b/cpp/src/aztec/stdlib/encryption/ecdsa/ecdsa.test.cpp index bc28d0d84f..a84d954e77 100644 --- a/cpp/src/aztec/stdlib/encryption/ecdsa/ecdsa.test.cpp +++ b/cpp/src/aztec/stdlib/encryption/ecdsa/ecdsa.test.cpp @@ -1,101 +1,56 @@ #include "../../primitives/bigfield/bigfield.hpp" #include "../../primitives/biggroup/biggroup.hpp" +#include "../../primitives/curves/secp256k1.hpp" #include "ecdsa.hpp" #include - #include -#include using namespace barretenberg; using namespace plonk; -namespace plonk { -namespace stdlib { -namespace secp256r { -typedef typename plonk::stdlib::bigfield fq; -typedef typename plonk::stdlib::bigfield fr; -typedef typename plonk::stdlib::element g1; - -} // namespace secp256r -} // namespace stdlib -} // namespace plonk -typedef stdlib::bool_t bool_t; -typedef stdlib::field_t field_t; -typedef stdlib::witness_t witness_t; -typedef stdlib::public_witness_t public_witness_t; - -stdlib::secp256r::g1 convert_inputs(waffle::TurboComposer* ctx, const secp256r1::g1::affine_element& input) -{ - uint256_t x_u256(input.x); - uint256_t y_u256(input.y); - - stdlib::secp256r::fq x(witness_t(ctx, barretenberg::fr(x_u256.slice(0, stdlib::secp256r::fq::NUM_LIMB_BITS * 2))), - witness_t(ctx, - barretenberg::fr(x_u256.slice(stdlib::secp256r::fq::NUM_LIMB_BITS * 2, - stdlib::secp256r::fq::NUM_LIMB_BITS * 4)))); - stdlib::secp256r::fq y(witness_t(ctx, barretenberg::fr(y_u256.slice(0, stdlib::secp256r::fq::NUM_LIMB_BITS * 2))), - witness_t(ctx, - barretenberg::fr(y_u256.slice(stdlib::secp256r::fq::NUM_LIMB_BITS * 2, - stdlib::secp256r::fq::NUM_LIMB_BITS * 4)))); - - return stdlib::secp256r::g1(x, y); -} - -stdlib::secp256r::fr convert_inputs(waffle::TurboComposer* ctx, const barretenberg::fr& scalar) -{ - uint256_t scalar_u256(scalar); - - stdlib::secp256r::fr x( - witness_t(ctx, barretenberg::fr(scalar_u256.slice(0, stdlib::secp256r::fq::NUM_LIMB_BITS * 2))), - witness_t(ctx, - barretenberg::fr(scalar_u256.slice(stdlib::secp256r::fq::NUM_LIMB_BITS * 2, - stdlib::secp256r::fq::NUM_LIMB_BITS * 4)))); - - return x; -} +namespace test_stdlib_ecdsa { +using Composer = waffle::UltraComposer; +using curve = stdlib::secp256k1; -/** TODO (#LARGE_MODULUS_AFFINE_POINT_COMPRESSION): Rewrite this test after designing point compression for p>2^255 HEAVY_TEST(stdlib_ecdsa, verify_signature) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); // whaaablaghaaglerijgeriij std::string message_string = "Instructions unclear, ask again later."; - crypto::ecdsa::key_pair account; - account.private_key = secp256r1::fr::random_element(); - account.public_key = secp256r1::g1::one * account.private_key; + crypto::ecdsa::key_pair account; + account.private_key = curve::fr::random_element(); + account.public_key = curve::g1::one * account.private_key; crypto::ecdsa::signature signature = - crypto::ecdsa::construct_signature(message_string, - account); + crypto::ecdsa::construct_signature(message_string, account); - bool first_result = crypto::ecdsa::verify_signature( + bool first_result = crypto::ecdsa::verify_signature( message_string, account.public_key, signature); EXPECT_EQ(first_result, true); - stdlib::secp256r::g1 public_key = convert_inputs(&composer, account.public_key); + curve::g1_bigfr_ct public_key = curve::g1_bigfr_ct::from_witness(&composer, account.public_key); std::vector rr(signature.r.begin(), signature.r.end()); std::vector ss(signature.s.begin(), signature.s.end()); - stdlib::ecdsa::signature sig{ stdlib::byte_array(&composer, rr), - stdlib::byte_array(&composer, ss) }; + stdlib::ecdsa::signature sig{ curve::byte_array_ct(&composer, rr), curve::byte_array_ct(&composer, ss) }; - stdlib::byte_array message(&composer, message_string); + curve::byte_array_ct message(&composer, message_string); - stdlib::bool_t signature_result = stdlib::ecdsa:: - verify_signature( + curve::bool_ct signature_result = + stdlib::ecdsa::verify_signature( message, public_key, sig); EXPECT_EQ(signature_result.get_value(), true); - std::cout << "composer gates = " << composer.get_num_gates() << std::endl; - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); + std::cerr << "composer gates = " << composer.get_num_gates() << std::endl; + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); bool proof_result = verifier.verify_proof(proof); EXPECT_EQ(proof_result, true); } -**/ +} // namespace test_stdlib_ecdsa \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/encryption/ecdsa/ecdsa_impl.hpp b/cpp/src/aztec/stdlib/encryption/ecdsa/ecdsa_impl.hpp index 462e75f831..78056bb8e6 100644 --- a/cpp/src/aztec/stdlib/encryption/ecdsa/ecdsa_impl.hpp +++ b/cpp/src/aztec/stdlib/encryption/ecdsa/ecdsa_impl.hpp @@ -6,7 +6,7 @@ namespace plonk { namespace stdlib { namespace ecdsa { -template +template bool_t verify_signature(const stdlib::byte_array& message, const G1& public_key, const signature& sig) @@ -20,6 +20,9 @@ bool_t verify_signature(const stdlib::byte_array& message, z.assert_is_in_field(); Fr r(sig.r); + // force r to be < secp256k1 group modulus, so we can compare with `result_mod_r` below + r.assert_is_in_field(); + Fr s(sig.s); // r and s should not be zero @@ -29,17 +32,31 @@ bool_t verify_signature(const stdlib::byte_array& message, Fr u1 = z / s; Fr u2 = r / s; - G1 result = G1::batch_mul({ G1::one(ctx), public_key }, { u1, u2 }); - result.x.assert_is_in_field(); + G1 result; + if constexpr (Composer::type == waffle::ComposerType::PLOOKUP) { + ASSERT(Curve::type == waffle::CurveType::SECP256K1); + public_key.validate_on_curve(); + result = G1::secp256k1_ecdsa_mul(public_key, u1, u2); + } else { + result = G1::batch_mul({ G1::one(ctx), public_key }, { u1, u2 }); + } + result.x.self_reduce(); + + // transfer Fq value x to an Fr element and reduce mod r + Fr result_mod_r(ctx, 0); + result_mod_r.binary_basis_limbs[0].element = result.x.binary_basis_limbs[0].element; + result_mod_r.binary_basis_limbs[1].element = result.x.binary_basis_limbs[1].element; + result_mod_r.binary_basis_limbs[2].element = result.x.binary_basis_limbs[2].element; + result_mod_r.binary_basis_limbs[3].element = result.x.binary_basis_limbs[3].element; + result_mod_r.binary_basis_limbs[0].maximum_value = result.x.binary_basis_limbs[0].maximum_value; + result_mod_r.binary_basis_limbs[1].maximum_value = result.x.binary_basis_limbs[1].maximum_value; + result_mod_r.binary_basis_limbs[2].maximum_value = result.x.binary_basis_limbs[2].maximum_value; + result_mod_r.binary_basis_limbs[3].maximum_value = result.x.binary_basis_limbs[3].maximum_value; - field_t result_x_lo = - result.x.binary_basis_limbs[1].element * Fq::shift_1 + result.x.binary_basis_limbs[0].element; - field_t result_x_hi = - result.x.binary_basis_limbs[3].element * Fq::shift_1 + result.x.binary_basis_limbs[2].element; + result_mod_r.prime_basis_limb = result.x.prime_basis_limb; - Fr result_mod_r(result_x_lo, result_x_hi); result_mod_r.assert_is_in_field(); - r.assert_is_in_field(); + result_mod_r.binary_basis_limbs[0].element.assert_equal(r.binary_basis_limbs[0].element); result_mod_r.binary_basis_limbs[1].element.assert_equal(r.binary_basis_limbs[1].element); result_mod_r.binary_basis_limbs[2].element.assert_equal(r.binary_basis_limbs[2].element); diff --git a/cpp/src/aztec/stdlib/encryption/schnorr/schnorr.cpp b/cpp/src/aztec/stdlib/encryption/schnorr/schnorr.cpp index de0f4992c1..f0858a2d0d 100644 --- a/cpp/src/aztec/stdlib/encryption/schnorr/schnorr.cpp +++ b/cpp/src/aztec/stdlib/encryption/schnorr/schnorr.cpp @@ -159,7 +159,7 @@ point variable_base_mul(const point& pub_key, const point& current_accu field_t two(pub_key.x.context, 2); - // Various elliptic curve point additions that follow assume we that the two points are distinct and not mutually + // Various elliptic curve point additions that follow assume that the two points are distinct and not mutually // inverse. collision_offset is chosen to prevent a malicious prover from exploiting this assumption. grumpkin::g1::affine_element collision_offset = crypto::pedersen::get_generator_data(DEFAULT_GEN_1).generator; grumpkin::g1::affine_element collision_end = collision_offset * grumpkin::fr(uint256_t(1) << 129); @@ -302,6 +302,9 @@ void verify_signature(const byte_array& message, const point& pub_key, con template wnaf_record convert_field_into_wnaf( waffle::TurboComposer* context, const field_t& limb); +template wnaf_record convert_field_into_wnaf( + waffle::UltraComposer* context, const field_t& limb); + template point variable_base_mul(const point& pub_key, const field_t& low_bits, const field_t& high_bits); @@ -314,11 +317,14 @@ template point variable_base_mul( template void verify_signature(const byte_array&, const point&, const signature_bits&); +template void verify_signature(const byte_array&, + const point&, + const signature_bits&); template signature_bits convert_signature( waffle::TurboComposer*, const crypto::schnorr::signature&); -template signature_bits convert_signature( - waffle::PlookupComposer*, const crypto::schnorr::signature&); +template signature_bits convert_signature( + waffle::UltraComposer*, const crypto::schnorr::signature&); } // namespace schnorr } // namespace stdlib -} // namespace plonk \ No newline at end of file +} // namespace plonk diff --git a/cpp/src/aztec/stdlib/encryption/schnorr/schnorr.hpp b/cpp/src/aztec/stdlib/encryption/schnorr/schnorr.hpp index 55cb9b69d4..2477f2cc95 100644 --- a/cpp/src/aztec/stdlib/encryption/schnorr/schnorr.hpp +++ b/cpp/src/aztec/stdlib/encryption/schnorr/schnorr.hpp @@ -47,17 +47,20 @@ extern template point variable_base_mul(const point convert_field_into_wnaf( waffle::TurboComposer* context, const field_t& limb); +extern template wnaf_record convert_field_into_wnaf( + waffle::UltraComposer* context, const field_t& limb); + extern template void verify_signature(const byte_array&, const point&, const signature_bits&); -extern template void verify_signature(const byte_array&, - const point&, - const signature_bits&); +extern template void verify_signature(const byte_array&, + const point&, + const signature_bits&); extern template signature_bits convert_signature( waffle::TurboComposer*, const crypto::schnorr::signature&); -extern template signature_bits convert_signature( - waffle::PlookupComposer*, const crypto::schnorr::signature&); +extern template signature_bits convert_signature( + waffle::UltraComposer*, const crypto::schnorr::signature&); } // namespace schnorr } // namespace stdlib diff --git a/cpp/src/aztec/stdlib/encryption/schnorr/schnorr.test.cpp b/cpp/src/aztec/stdlib/encryption/schnorr/schnorr.test.cpp index 1d2d641597..7dda11d0be 100644 --- a/cpp/src/aztec/stdlib/encryption/schnorr/schnorr.test.cpp +++ b/cpp/src/aztec/stdlib/encryption/schnorr/schnorr.test.cpp @@ -2,15 +2,49 @@ #include #include #include -#include +#include namespace test_stdlib_schnorr { using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; using namespace plonk::stdlib::schnorr; -typedef wnaf_record wnaf_record_ct; +auto run_scalar_mul_test = [](grumpkin::fr scalar_mont, bool expect_verify) { + Composer composer = Composer(); + + grumpkin::fr scalar = scalar_mont.from_montgomery_form(); + + uint256_t scalar_low{ scalar.data[0], scalar.data[1], 0ULL, 0ULL }; + uint256_t scalar_high{ scalar.data[2], scalar.data[3], 0ULL, 0ULL }; + + field_ct input_lo = witness_ct(&composer, scalar_low); + field_ct input_hi = witness_ct(&composer, scalar_high); + + grumpkin::g1::element expected = grumpkin::g1::one * scalar_mont; + expected = expected.normalize(); + point_ct point_input{ witness_ct(&composer, grumpkin::g1::affine_one.x), + witness_ct(&composer, grumpkin::g1::affine_one.y) }; + + point_ct output = variable_base_mul(point_input, input_lo, input_hi); + + if (expect_verify) { + EXPECT_EQ(output.x.get_value(), expected.x); + EXPECT_EQ(output.y.get_value(), expected.y); + }; + + Prover prover = composer.create_prover(); + + printf("composer gates = %zu\n", composer.get_num_gates()); + Verifier verifier = composer.create_verifier(); + + waffle::plonk_proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, expect_verify); +}; + +typedef wnaf_record wnaf_record_ct; /** * @brief Helper function to compare wnaf_records, useful since == on bool_ct's returns a bool_ct. @@ -59,34 +93,30 @@ TEST(stdlib_schnorr, convert_field_into_wnaf_special) }; // manually build the expected wnaf records - - // these values are corrrectly reproduced - // // 1 is given by ((false, ..., false), false) + // 1 is given by ((false, ..., false), false) auto record_1 = all_false; - // // 0 is given by ((false, ..., false), true) + // 0 is given by ((false, ..., false), true) auto record_0 = all_false; record_0.skew = true; - // // 2^128 - 1 = 2^128 - 2^127 + (2^127 - 1) - 0 is given by((false, true, ..., true), false) + // 2^128 - 1 = 2^128 - 2^127 + (2^127 - 1) - 0 is given by((false, true, ..., true), false) auto record_128_minus_1 = all_true; record_128_minus_1.bits[0] = false; record_128_minus_1.skew = false; - // these values are not correctly reproduced - // // 2^128 + 1 = 2^128 + (2^127 - (2^127 - 1)) - 0 is given by((true, true, false, ..., false), false) + // 2^128 + 1 = 2^128 + (2^127 - (2^127 - 1)) - 0 is given by((true, false, false, ..., false), false) auto record_128_plus_1 = all_false; record_128_plus_1.bits[0] = true; - record_128_plus_1.bits[1] = true; - // // 2^128 = 2^128 + (2^127 - (2^127 - 1)) - 1 is given by((true, true, false, ..., false), true) + // 2^128 = 2^128 + (2^127 - (2^127 - 1)) - 1 is given by((true, false, false, ..., false), true) auto record_128 = all_false; record_128.bits[0] = true; - record_128.bits[1] = true; record_128.skew = true; // // 2^129-1 = 2^128 + 2^127 + ... + 1 - 0 should be given by ((true, true, ..., true), false). - // Note: fixed_wnaf<129, 1, 1>, used inside of convert_field_into_wnaf, incorrectly computes the the coefficient of + // Note: fixed_wnaf<129, 1, 1>, used inside of convert_field_into_wnaf, incorrectly computes the the coefficient + // of // 2^127 in the wnaf representation of to be -1. auto record_max = all_true; record_max.skew = false; @@ -95,25 +125,13 @@ TEST(stdlib_schnorr, convert_field_into_wnaf_special) { record_1, record_0, record_128_minus_1, record_128_plus_1, record_128, record_max }); // integers less than 2^128 are converted correctly - for (size_t i = 0; i != 3; ++i) { + for (size_t i = 0; i != num_special_values; ++i) { field_ct elt = special_field_elts[i]; wnaf_record_ct record = convert_field_into_wnaf(&composer, elt); wnaf_record_ct expected_record = expected_wnaf_records[i]; bool records_equal = compare_records(record, expected_record); ASSERT_TRUE(records_equal); - ASSERT_FALSE(composer.failed); - } - - // integers >= 2^128 are not converted correctly. - for (size_t i = 3; i != num_special_values; ++i) { - field_ct elt = special_field_elts[i]; - wnaf_record_ct record = convert_field_into_wnaf(&composer, elt); - wnaf_record_ct expected_record = expected_wnaf_records[i]; - bool records_equal = compare_records(record, expected_record); - ASSERT_FALSE(records_equal); - ASSERT_TRUE(composer.failed); - // reset composer state for the next test. - composer.failed = false; + ASSERT_FALSE(composer.failed()); } } @@ -149,50 +167,22 @@ TEST(stdlib_schnorr, convert_field_into_wnaf) * @brief Test variable_base_mul(const point& pub_key, * const field_t& low_bits, * const field_t& high_bits) - * by taking a random field Fr element s, commputing the corresponding Grumpkin G1 element both natively + * by taking a random field Fr element s, computing the corresponding Grumpkin G1 element both natively * and using the function in question (splitting s into 128-bit halves), then comparing the results. */ TEST(stdlib_schnorr, test_scalar_mul_low_high) { - auto run_test = [](grumpkin::fr scalar_mont, bool expect_verify) { - Composer composer = Composer(); - - grumpkin::fr scalar = scalar_mont.from_montgomery_form(); - - uint256_t scalar_low{ scalar.data[0], scalar.data[1], 0ULL, 0ULL }; - uint256_t scalar_high{ scalar.data[2], scalar.data[3], 0ULL, 0ULL }; - - field_ct input_lo = witness_ct(&composer, scalar_low); - field_ct input_hi = witness_ct(&composer, scalar_high); - - grumpkin::g1::element expected = grumpkin::g1::one * scalar_mont; - expected = expected.normalize(); - point_ct point_input{ witness_ct(&composer, grumpkin::g1::affine_one.x), - witness_ct(&composer, grumpkin::g1::affine_one.y) }; - - point_ct output = variable_base_mul(point_input, input_lo, input_hi); - - if (expect_verify) { - EXPECT_EQ(output.x.get_value(), expected.x); - EXPECT_EQ(output.y.get_value(), expected.y); - }; - - Prover prover = composer.create_prover(); - - printf("composer gates = %zu\n", composer.get_num_gates()); - Verifier verifier = composer.create_verifier(); - - waffle::plonk_proof proof = prover.construct_proof(); - - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, expect_verify); - }; + run_scalar_mul_test(grumpkin::fr::random_element(), true); + run_scalar_mul_test(grumpkin::fr(static_cast(1) << 128), false); +} - run_test(grumpkin::fr::random_element(), true); - // // removing and ASSERT in variable_base_mul, these tests show that - // // verification fails when input element fits in a single limb - // run_test(0, false); - // run_test(grumpkin::fr(static_cast(1) << 128), false); +/** + * @brief Death test for cases exluded by `ASSERT` in variable_base_mul.KH + */ +TEST(stdlib_schnorrDeathTest, test_scalar_mul_low_high) +{ + // without the assert causing the death here, the test passes (i.e., verification fails). + ASSERT_DEATH(run_scalar_mul_test(0, false), ""); } /** diff --git a/cpp/src/aztec/stdlib/hash/CMakeLists.txt b/cpp/src/aztec/stdlib/hash/CMakeLists.txt index c3009adfee..d8b6078d10 100644 --- a/cpp/src/aztec/stdlib/hash/CMakeLists.txt +++ b/cpp/src/aztec/stdlib/hash/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(blake2s) +add_subdirectory(blake3s) add_subdirectory(pedersen) add_subdirectory(sha256) \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/hash/blake2s/blake2s.cpp b/cpp/src/aztec/stdlib/hash/blake2s/blake2s.cpp index c527630b8f..007ba1f277 100644 --- a/cpp/src/aztec/stdlib/hash/blake2s/blake2s.cpp +++ b/cpp/src/aztec/stdlib/hash/blake2s/blake2s.cpp @@ -1,7 +1,9 @@ #include "blake2s.hpp" +#include "blake2s_plookup.hpp" +#include "blake_util.hpp" #include #include -#include +#include #include namespace plonk { @@ -15,14 +17,6 @@ constexpr uint32_t initial_H[8] = { 0x6b08e647, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, }; -constexpr uint8_t blake2s_sigma[10][16] = { - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, - { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 }, { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 }, - { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 }, { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 }, - { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 }, { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 }, - { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 }, { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 }, -}; - enum blake2s_constant { BLAKE2S_BLOCKBYTES = 64, BLAKE2S_OUTBYTES = 32, @@ -48,30 +42,6 @@ template void blake2s_increment_counter(blake2s_state void blake2s_compress(blake2s_state& S, byte_array const& in) { uint32 m[16]; @@ -94,16 +64,27 @@ template void blake2s_compress(blake2s_state& S, b v[14] = S.f[0] ^ blake2s_IV[6]; v[15] = S.f[1] ^ blake2s_IV[7]; - ROUND(0); - ROUND(1); - ROUND(2); - ROUND(3); - ROUND(4); - ROUND(5); - ROUND(6); - ROUND(7); - ROUND(8); - ROUND(9); + blake_util::round_fn(v, m, 0); + blake_util::round_fn(v, m, 1); + blake_util::round_fn(v, m, 2); + blake_util::round_fn(v, m, 3); + blake_util::round_fn(v, m, 4); + blake_util::round_fn(v, m, 5); + blake_util::round_fn(v, m, 6); + blake_util::round_fn(v, m, 7); + blake_util::round_fn(v, m, 8); + blake_util::round_fn(v, m, 9); + + // ROUND(0); + // ROUND(1); + // ROUND(2); + // ROUND(3); + // ROUND(4); + // ROUND(5); + // ROUND(6); + // ROUND(7); + // ROUND(8); + // ROUND(9); for (size_t i = 0; i < 8; ++i) { S.h[i] = S.h[i] ^ v[i] ^ v[i + 8]; @@ -138,6 +119,10 @@ template void blake2s(blake2s_state& S, byte_array template byte_array blake2s(const byte_array& input) { + if constexpr (Composer::type == waffle::ComposerType::PLOOKUP) { + return blake2s_plookup::blake2s(input); + } + blake2s_state S; for (size_t i = 0; i < 8; i++) { @@ -156,7 +141,7 @@ template byte_array blake2s(const byte_array blake2s(const byte_array& input); template byte_array blake2s(const byte_array& input); -template byte_array blake2s(const byte_array& input); +template byte_array blake2s(const byte_array& input); } // namespace stdlib -} // namespace plonk \ No newline at end of file +} // namespace plonk diff --git a/cpp/src/aztec/stdlib/hash/blake2s/blake2s.hpp b/cpp/src/aztec/stdlib/hash/blake2s/blake2s.hpp index 7d064fcfc7..dd15cae4dc 100644 --- a/cpp/src/aztec/stdlib/hash/blake2s/blake2s.hpp +++ b/cpp/src/aztec/stdlib/hash/blake2s/blake2s.hpp @@ -4,7 +4,7 @@ namespace waffle { class StandardComposer; class TurboComposer; -class PlookupComposer; +class UltraComposer; } // namespace waffle namespace plonk { @@ -14,7 +14,7 @@ template byte_array blake2s(const byte_array blake2s(const byte_array& input); extern template byte_array blake2s(const byte_array& input); -extern template byte_array blake2s(const byte_array& input); +extern template byte_array blake2s(const byte_array& input); } // namespace stdlib } // namespace plonk diff --git a/cpp/src/aztec/stdlib/hash/blake2s/blake2s.test.cpp b/cpp/src/aztec/stdlib/hash/blake2s/blake2s.test.cpp index 4a26de9dd5..fc9b68aaca 100644 --- a/cpp/src/aztec/stdlib/hash/blake2s/blake2s.test.cpp +++ b/cpp/src/aztec/stdlib/hash/blake2s/blake2s.test.cpp @@ -1,26 +1,17 @@ #include "blake2s.hpp" +#include "blake2s_plookup.hpp" #include #include -#include +#include using namespace barretenberg; using namespace plonk; -typedef waffle::TurboComposer Composer; +using namespace plonk::stdlib::types; typedef stdlib::byte_array byte_array; +typedef stdlib::byte_array byte_array_plookup; typedef stdlib::public_witness_t public_witness_t; - -namespace std { -inline std::ostream& operator<<(std::ostream& os, std::vector const& t) -{ - os << "[ "; - for (auto e : t) { - os << std::setfill('0') << std::hex << std::setw(2) << (int)e << " "; - } - os << "]"; - return os; -} -} // namespace std +typedef stdlib::public_witness_t public_witness_t_plookup; TEST(stdlib_blake2s, test_single_block) { @@ -28,8 +19,8 @@ TEST(stdlib_blake2s, test_single_block) std::string input = "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01"; std::vector input_v(input.begin(), input.end()); - byte_array input_arr(&composer, input_v); - byte_array output = stdlib::blake2s(input_arr); + byte_array_ct input_arr(&composer, input_v); + byte_array_ct output = stdlib::blake2s(input_arr); std::vector expected = blake2::blake2s(input_v); @@ -46,14 +37,38 @@ TEST(stdlib_blake2s, test_single_block) EXPECT_EQ(proof_result, true); } +TEST(stdlib_blake2s, test_single_block_plookup) +{ + waffle::UltraComposer composer = waffle::UltraComposer(); + std::string input = "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01"; + std::vector input_v(input.begin(), input.end()); + + byte_array_plookup input_arr(&composer, input_v); + byte_array_plookup output = stdlib::blake2s(input_arr); + + std::vector expected = blake2::blake2s(input_v); + + EXPECT_EQ(output.get_value(), expected); + + auto prover = composer.create_prover(); + std::cout << "prover gates = " << prover.n << std::endl; + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); + + auto proof = prover.construct_proof(); + + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); +} + TEST(stdlib_blake2s, test_double_block) { Composer composer = Composer(); std::string input = "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789"; std::vector input_v(input.begin(), input.end()); - byte_array input_arr(&composer, input_v); - byte_array output = stdlib::blake2s(input_arr); + byte_array_ct input_arr(&composer, input_v); + byte_array_ct output = stdlib::blake2s(input_arr); std::vector expected = blake2::blake2s(input_v); @@ -69,3 +84,27 @@ TEST(stdlib_blake2s, test_double_block) bool proof_result = verifier.verify_proof(proof); EXPECT_EQ(proof_result, true); } + +TEST(stdlib_blake2s, test_double_block_plookup) +{ + waffle::UltraComposer composer = waffle::UltraComposer(); + std::string input = "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789"; + std::vector input_v(input.begin(), input.end()); + + byte_array_plookup input_arr(&composer, input_v); + byte_array_plookup output = stdlib::blake2s(input_arr); + + std::vector expected = blake2::blake2s(input_v); + + EXPECT_EQ(output.get_value(), expected); + + auto prover = composer.create_prover(); + std::cout << "prover gates = " << prover.n << std::endl; + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); + + auto proof = prover.construct_proof(); + + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); +} diff --git a/cpp/src/aztec/stdlib/hash/blake2s/blake2s_plookup.cpp b/cpp/src/aztec/stdlib/hash/blake2s/blake2s_plookup.cpp new file mode 100644 index 0000000000..488c9107cb --- /dev/null +++ b/cpp/src/aztec/stdlib/hash/blake2s/blake2s_plookup.cpp @@ -0,0 +1,174 @@ +#include "blake2s_plookup.hpp" +#include "blake_util.hpp" + +#include +#include +#include +#include +#include +#include +#include + +/** + * Optimizations: + * + * 1. use lookup tables for basic XOR operations + * 2. replace use of uint32 with basic field_t type + * + **/ +namespace plonk { +namespace stdlib { + +namespace blake2s_plookup { + +using plookup::ColumnIdx; + +constexpr uint32_t blake2s_IV[8] = { 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL, + 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL }; + +constexpr uint32_t initial_H[8] = { + 0x6b08e647, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, +}; + +enum blake2s_constant { + BLAKE2S_BLOCKBYTES = 64, + BLAKE2S_OUTBYTES = 32, + BLAKE2S_KEYBYTES = 32, + BLAKE2S_SALTBYTES = 8, + BLAKE2S_PERSONALBYTES = 8 +}; + +/** + * The blake2s_state consists of the following components: + * h: A 64-byte chain value denoted decomposed as (h_0, h_1, ..., h_7), each h_i is a 32-bit number. + * It form the first two rows on the internal state matrix v of the compression function G. + * + * t: It is a counter (t_0 lsb and t_1 msb) used in the the initialization of the internal state v. + * + * f: f_0 and f_1 are finalization flags used in the initialization of the internal state v. + * / 0xfff...ff if the block processed is the last + * f_0 = | + * \ 0x000...00 otherwise + * / 0xfff...ff if the last node is processed in merkle-tree hashing + * f_1 = | + * \ 0x000...00 otherwise + * + * Further, the internal state 4x4 matrix used by the compression function is denoted by v. + * The input data is stored in the 16-word message m. + */ +template struct blake2s_state { + field_t h[8]; + field_t t[2]; + field_t f[2]; +}; + +template void blake2s_increment_counter(blake2s_state& S, const uint32_t inc) +{ + typedef field_t field_pt; + field_pt inc_scalar = field_pt(uint256_t(inc)); + S.t[0] = S.t[0] + inc_scalar; + // TODO: Secure!? Think so as inc is known at "compile" time as it's derived from the msg length. + const bool to_inc = uint32_t(uint256_t(S.t[0].get_value())) < inc; + S.t[1] = S.t[1] + (to_inc ? field_pt(1) : field_pt(0)); +} + +template void blake2s_compress(blake2s_state& S, byte_array const& in) +{ + typedef field_t field_pt; + field_pt m[16]; + field_pt v[16]; + + for (size_t i = 0; i < 16; ++i) { + m[i] = field_pt(in.slice(i * 4, 4).reverse()); + } + + for (size_t i = 0; i < 8; ++i) { + v[i] = S.h[i]; + } + + v[8] = field_pt(uint256_t(blake2s_IV[0])); + v[9] = field_pt(uint256_t(blake2s_IV[1])); + v[10] = field_pt(uint256_t(blake2s_IV[2])); + v[11] = field_pt(uint256_t(blake2s_IV[3])); + + // Use the lookup tables to perform XORs + const auto lookup_1 = + plookup_read::get_lookup_accumulators(BLAKE_XOR, S.t[0], field_pt(uint256_t(blake2s_IV[4])), true); + v[12] = lookup_1[ColumnIdx::C3][0]; + const auto lookup_2 = + plookup_read::get_lookup_accumulators(BLAKE_XOR, S.t[1], field_pt(uint256_t(blake2s_IV[5])), true); + v[13] = lookup_2[ColumnIdx::C3][0]; + const auto lookup_3 = + plookup_read::get_lookup_accumulators(BLAKE_XOR, S.f[0], field_pt(uint256_t(blake2s_IV[6])), true); + v[14] = lookup_3[ColumnIdx::C3][0]; + const auto lookup_4 = + plookup_read::get_lookup_accumulators(BLAKE_XOR, S.f[1], field_pt(uint256_t(blake2s_IV[7])), true); + v[15] = lookup_4[ColumnIdx::C3][0]; + + blake_util::round_fn_lookup(v, m, 0); + blake_util::round_fn_lookup(v, m, 1); + blake_util::round_fn_lookup(v, m, 2); + blake_util::round_fn_lookup(v, m, 3); + blake_util::round_fn_lookup(v, m, 4); + blake_util::round_fn_lookup(v, m, 5); + blake_util::round_fn_lookup(v, m, 6); + blake_util::round_fn_lookup(v, m, 7); + blake_util::round_fn_lookup(v, m, 8); + blake_util::round_fn_lookup(v, m, 9); + + // At this point in the algorithm, the elements (v0, v1, v2, v3) and (v8, v9, v10, v11) in the state matrix 'v' can + // be 'overflowed' i.e. contain values > 2^{32}. However we do NOT need to normalize them to be < 2^{32}, the + // following `read_sequence_from_table` calls correctly constrain the output to be 32-bits + for (size_t i = 0; i < 8; ++i) { + const auto lookup_a = plookup_read::get_lookup_accumulators(BLAKE_XOR, S.h[i], v[i], true); + const auto lookup_b = + plookup_read::get_lookup_accumulators(BLAKE_XOR, lookup_a[ColumnIdx::C3][0], v[i + 8], true); + S.h[i] = lookup_b[ColumnIdx::C3][0]; + } +} + +template void blake2s(blake2s_state& S, byte_array const& in) +{ + size_t offset = 0; + size_t size = in.size(); + + while (size > BLAKE2S_BLOCKBYTES) { + blake2s_increment_counter(S, BLAKE2S_BLOCKBYTES); + blake2s_compress(S, in.slice(offset, BLAKE2S_BLOCKBYTES)); + offset += BLAKE2S_BLOCKBYTES; + size -= BLAKE2S_BLOCKBYTES; + } + + // Set last block. + S.f[0] = field_t(uint256_t((uint32_t)-1)); + + byte_array final(in.get_context()); + final.write(in.slice(offset)).write(byte_array(in.get_context(), BLAKE2S_BLOCKBYTES - size)); + blake2s_increment_counter(S, (uint32_t)size); + blake2s_compress(S, final); +} + +template byte_array blake2s(const byte_array& input) +{ + blake2s_state S; + + for (size_t i = 0; i < 8; i++) { + S.h[i] = field_t(uint256_t(initial_H[i])); + } + + blake2s(S, input); + + byte_array result(input.get_context()); + for (auto h : S.h) { + byte_array v(h, 4); + result.write(v.reverse()); + } + return result; +} + +template byte_array blake2s(const byte_array& input); + +} // namespace blake2s_plookup + +} // namespace stdlib +} // namespace plonk diff --git a/cpp/src/aztec/stdlib/hash/blake2s/blake2s_plookup.hpp b/cpp/src/aztec/stdlib/hash/blake2s/blake2s_plookup.hpp new file mode 100644 index 0000000000..b52ade2c56 --- /dev/null +++ b/cpp/src/aztec/stdlib/hash/blake2s/blake2s_plookup.hpp @@ -0,0 +1,24 @@ +#pragma once +#include +#include +#include + +#include + +#include "../../primitives/field/field.hpp" +#include "../../primitives/composers/composers_fwd.hpp" +#include "../../primitives/packed_byte_array/packed_byte_array.hpp" + +namespace plonk { +namespace stdlib { + +namespace blake2s_plookup { + +template byte_array blake2s(const byte_array& input); + +extern template byte_array blake2s(const byte_array& input); + +} // namespace blake2s_plookup + +} // namespace stdlib +} // namespace plonk diff --git a/cpp/src/aztec/stdlib/hash/blake2s/blake_util.hpp b/cpp/src/aztec/stdlib/hash/blake2s/blake_util.hpp new file mode 100644 index 0000000000..7dcda992c7 --- /dev/null +++ b/cpp/src/aztec/stdlib/hash/blake2s/blake_util.hpp @@ -0,0 +1,258 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace plonk { +namespace stdlib { + +namespace blake_util { + +// constants +enum blake_constant { BLAKE3_STATE_SIZE = 16 }; + +constexpr uint8_t MSG_SCHEDULE_BLAKE3[7][16] = { + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, { 2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8 }, + { 3, 4, 10, 12, 13, 2, 7, 14, 6, 5, 9, 0, 11, 15, 8, 1 }, { 10, 7, 12, 9, 14, 3, 13, 15, 4, 0, 11, 2, 5, 8, 1, 6 }, + { 12, 13, 9, 11, 15, 10, 14, 8, 7, 2, 5, 3, 0, 1, 6, 4 }, { 9, 14, 11, 5, 8, 12, 15, 1, 13, 3, 0, 10, 2, 6, 4, 7 }, + { 11, 15, 5, 0, 1, 9, 8, 6, 14, 10, 2, 12, 3, 4, 7, 13 }, +}; + +constexpr uint8_t MSG_SCHEDULE_BLAKE2[10][16] = { + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, + { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 }, { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 }, + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 }, { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 }, + { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 }, { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 }, + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 }, { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 }, +}; + +/** + * Addition with normalisation (to ensure the addition is in the scalar field.) + * Given two field_t elements a and b, this function computes ((a + b) % 2^{32}). + * Additionally, it checks if the overflow of the addition is a maximum of 3 bits. + * This is to ascertain that the additions of two 32-bit scalars in blake2s and blake3s do not exceed 35 bits. + */ +template field_t add_normalize(const field_t& a, const field_t& b) +{ + typedef field_t field_pt; + typedef witness_t witness_pt; + + Composer* ctx = a.get_context() ? a.get_context() : b.get_context(); + + uint256_t sum = a.get_value() + b.get_value(); + + uint256_t normalized_sum = static_cast(sum.data[0]); + + if (a.witness_index == IS_CONSTANT && b.witness_index == IS_CONSTANT) { + return field_pt(ctx, normalized_sum); + } + + field_pt overflow = witness_pt(ctx, fr((sum - normalized_sum) >> 32)); + + // The overflow here could be of 2 bits because we allow that much overflow in the Blake rounds. + overflow.create_range_constraint(3); + + // a + b - (overflow * 2^{32}) + field_pt result = a.add_two(b, overflow * field_pt(ctx, -fr((uint64_t)(1ULL << 32ULL)))); + + return result; +} + +/** + * + * Function `G' in the Blake2s and Blake3s algorithm which is the core + * mixing step with additions, xors and right-rotates. This function is + * used in both TurboPlonk (without lookup tables). + * + * Inputs: - A pointer to a 16-word `state`, + * - indices a, b, c, d, + * - addition messages x and y + * + **/ +template +void g(uint32 state[BLAKE3_STATE_SIZE], + size_t a, + size_t b, + size_t c, + size_t d, + uint32 x, + uint32 y) +{ + state[a] = state[a] + state[b] + x; + state[d] = (state[d] ^ state[a]).ror(16); + state[c] = state[c] + state[d]; + state[b] = (state[b] ^ state[c]).ror(12); + state[a] = state[a] + state[b] + y; + state[d] = (state[d] ^ state[a]).ror(8); + state[c] = state[c] + state[d]; + state[b] = (state[b] ^ state[c]).ror(7); +} + +/** + * + * Function `G' in the Blake2s and Blake3s algorithm which is the core + * mixing step with additions, xors and right-rotates. This function is + * used in UltraPlonk version (with lookup tables). + * + * Inputs: - A pointer to a 16-word `state`, + * - indices a, b, c, d, + * - addition messages x and y + * - boolean `last_update` to make sure addition is normalised only in + * last update of the state + * + * Gate costs per call to function G in lookup case: + * + * Read sequence from table = 6 gates per read => 6 * 4 = 24 + * Addition gates = 4 gates + * Range gates = 2 gates + * Addition gate for correct output of XOR rotate 12 = 1 gate + * Normalizing scaling factors = 2 gates + * + * Subtotal = 33 gates + * Outside rounds, each of Blake2s and Blake3s needs 20 and 24 lookup reads respectively. + * + * +-----------+--------------+-----------------------+---------------------------+--------------+ + * | | calls to G | gate count for rounds | gate count outside rounds | total | + * |-----------|--------------|-----------------------|---------------------------|--------------| + * | Blake2s | 80 | 80 * 33 | 20 * 6 | 2760 | + * | Blake3s | 56 | 56 * 33 | 24 * 6 | 1992 | + * +-----------+--------------+-----------------------+---------------------------+--------------+ + * + * P.S. This doesn't include some more addition gates required after the rounds. + * This cost would be negligible as compared to the above gate counts. + * + * + * TODO: Idea for getting rid of extra addition and multiplication gates by tweaking gate structure. + * To be implemented later. + * + * q_plookup = 1 | d0 | a0 | d'0 | -- | + * q_plookup = 1 | d1 | a1 | d'1 | d2 | <--- set q_arith = 1 and validate d2 - d'5 * scale_factor = 0 + * q_plookup = 1 | d2 | a2 | d'2 | d'5 | + * q_plookup = 1 | d3 | a3 | d'3 | -- | + * q_plookup = 1 | d4 | a4 | d'4 | -- | + * q_plookup = 1 | d5 | a5 | d'5 | c | <---- set q_arith = 1 and validate d'5 * scale_factor + c - c2 = + * 0. | | c2 | <---- this row is start of another lookup table (b ^ c) + * + * + **/ +template +void g_lookup(field_t state[BLAKE3_STATE_SIZE], + size_t a, + size_t b, + size_t c, + size_t d, + field_t x, + field_t y, + const bool last_update = false) +{ + typedef field_t field_pt; + + // For simplicity, state[a] is written as `a' in comments. + // a = a + b + x + state[a] = state[a].add_two(state[b], x); + + // d = (d ^ a).ror(16) + const auto lookup_1 = plookup_read::get_lookup_accumulators(BLAKE_XOR_ROTATE_16, state[d], state[a], true); + field_pt scaling_factor_1 = (1 << (32 - 16)); + state[d] = lookup_1[ColumnIdx::C3][0] * scaling_factor_1; + + // c = c + d + state[c] = state[c] + state[d]; + + // b = (b ^ c).ror(12) + const auto lookup_2 = plookup_read::get_lookup_accumulators(BLAKE_XOR, state[b], state[c], true); + field_pt lookup_output = lookup_2[ColumnIdx::C3][2]; + field_pt t2_term = field_pt(1 << 12) * lookup_2[ColumnIdx::C3][2]; + lookup_output += (lookup_2[ColumnIdx::C3][0] - t2_term) * field_pt(1 << 20); + state[b] = lookup_output; + + // a = a + b + y + if (!last_update) { + state[a] = state[a].add_two(state[b], y); + } else { + state[a] = add_normalize(state[a], state[b] + y); + } + + // d = (d ^ a).ror(8) + const auto lookup_3 = plookup_read::get_lookup_accumulators(BLAKE_XOR_ROTATE_8, state[d], state[a], true); + field_pt scaling_factor_3 = (1 << (32 - 8)); + state[d] = lookup_3[ColumnIdx::C3][0] * scaling_factor_3; + + // c = c + d + if (!last_update) { + state[c] = state[c] + state[d]; + } else { + state[c] = add_normalize(state[c], state[d]); + } + + // b = (b ^ c).ror(7) + const auto lookup_4 = plookup_read::get_lookup_accumulators(BLAKE_XOR_ROTATE_7, state[b], state[c], true); + field_pt scaling_factor_4 = (1 << (32 - 7)); + state[b] = lookup_4[ColumnIdx::C3][0] * scaling_factor_4; +} + +/* + * This is the round function used in Blake2s and Blake3s for TurboPlonk. + * Inputs: - 16-word state + * - 16-word msg + * - round numbe + * - which_blake to choose Blake2 or Blake3 (false -> Blake2) + */ +template +void round_fn(uint32 state[BLAKE3_STATE_SIZE], + uint32 msg[BLAKE3_STATE_SIZE], + size_t round, + const bool which_blake = false) +{ + // Select the message schedule based on the round. + const uint8_t* schedule = which_blake ? MSG_SCHEDULE_BLAKE3[round] : MSG_SCHEDULE_BLAKE2[round]; + + // Mix the columns. + g(state, 0, 4, 8, 12, msg[schedule[0]], msg[schedule[1]]); + g(state, 1, 5, 9, 13, msg[schedule[2]], msg[schedule[3]]); + g(state, 2, 6, 10, 14, msg[schedule[4]], msg[schedule[5]]); + g(state, 3, 7, 11, 15, msg[schedule[6]], msg[schedule[7]]); + + // Mix the rows. + g(state, 0, 5, 10, 15, msg[schedule[8]], msg[schedule[9]]); + g(state, 1, 6, 11, 12, msg[schedule[10]], msg[schedule[11]]); + g(state, 2, 7, 8, 13, msg[schedule[12]], msg[schedule[13]]); + g(state, 3, 4, 9, 14, msg[schedule[14]], msg[schedule[15]]); +} + +/* + * This is the round function used in Blake2s and Blake3s for UltraPlonk. + * Inputs: - 16-word state + * - 16-word msg + * - round numbe + * - which_blake to choose Blake2 or Blake3 (false -> Blake2) + */ +template +void round_fn_lookup(field_t state[BLAKE3_STATE_SIZE], + field_t msg[BLAKE3_STATE_SIZE], + size_t round, + const bool which_blake = false) +{ + // Select the message schedule based on the round. + const uint8_t* schedule = which_blake ? MSG_SCHEDULE_BLAKE3[round] : MSG_SCHEDULE_BLAKE2[round]; + + // Mix the columns. + g_lookup(state, 0, 4, 8, 12, msg[schedule[0]], msg[schedule[1]]); + g_lookup(state, 1, 5, 9, 13, msg[schedule[2]], msg[schedule[3]]); + g_lookup(state, 2, 6, 10, 14, msg[schedule[4]], msg[schedule[5]]); + g_lookup(state, 3, 7, 11, 15, msg[schedule[6]], msg[schedule[7]]); + + // Mix the rows. + g_lookup(state, 0, 5, 10, 15, msg[schedule[8]], msg[schedule[9]], true); + g_lookup(state, 1, 6, 11, 12, msg[schedule[10]], msg[schedule[11]], true); + g_lookup(state, 2, 7, 8, 13, msg[schedule[12]], msg[schedule[13]], true); + g_lookup(state, 3, 4, 9, 14, msg[schedule[14]], msg[schedule[15]], true); +} + +} // namespace blake_util + +} // namespace stdlib +} // namespace plonk diff --git a/cpp/src/aztec/stdlib/hash/blake3s/CMakeLists.txt b/cpp/src/aztec/stdlib/hash/blake3s/CMakeLists.txt new file mode 100644 index 0000000000..913f098512 --- /dev/null +++ b/cpp/src/aztec/stdlib/hash/blake3s/CMakeLists.txt @@ -0,0 +1 @@ +barretenberg_module(stdlib_blake3s stdlib_primitives crypto_blake3s) \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/hash/blake3s/blake3s.cpp b/cpp/src/aztec/stdlib/hash/blake3s/blake3s.cpp new file mode 100644 index 0000000000..59b2944e3b --- /dev/null +++ b/cpp/src/aztec/stdlib/hash/blake3s/blake3s.cpp @@ -0,0 +1,267 @@ +#include "blake3s.hpp" +#include "blake3s_plookup.hpp" +#include +#include +#include +#include "../blake2s/blake_util.hpp" + +namespace plonk { +namespace stdlib { + +namespace blake3_internal { + +/* + * Constants and more. + */ +#define BLAKE3_VERSION_STRING "0.3.7" + +// internal flags +enum blake3_flags { + CHUNK_START = 1 << 0, + CHUNK_END = 1 << 1, + PARENT = 1 << 2, + ROOT = 1 << 3, + KEYED_HASH = 1 << 4, + DERIVE_KEY_CONTEXT = 1 << 5, + DERIVE_KEY_MATERIAL = 1 << 6, +}; + +// constants +enum blake3s_constant { + BLAKE3_KEY_LEN = 32, + BLAKE3_OUT_LEN = 32, + BLAKE3_BLOCK_LEN = 64, + BLAKE3_CHUNK_LEN = 1024, + BLAKE3_MAX_DEPTH = 54, + BLAKE3_STATE_SIZE = 16 +}; + +static const uint32_t IV[8] = { 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL, + 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL }; + +template struct blake3_hasher { + uint32 key[8]; + uint32 cv[8]; + byte_array buf; + uint8_t buf_len; + uint8_t blocks_compressed; + uint8_t flags; + Composer* context; +}; + +/* + * Core Blake3s functions. These are similar to that of Blake2s except for a few + * constant parameters and fewer rounds. + * + */ +template +void compress_pre(uint32 state[BLAKE3_STATE_SIZE], + const uint32 cv[8], + const byte_array& block, + uint8_t block_len, + uint8_t flags) +{ + uint32 block_words[BLAKE3_STATE_SIZE]; + for (size_t i = 0; i < BLAKE3_STATE_SIZE; ++i) { + block_words[i] = uint32(block.slice(i * 4, 4).reverse()); + } + + state[0] = cv[0]; + state[1] = cv[1]; + state[2] = cv[2]; + state[3] = cv[3]; + state[4] = cv[4]; + state[5] = cv[5]; + state[6] = cv[6]; + state[7] = cv[7]; + state[8] = IV[0]; + state[9] = IV[1]; + state[10] = IV[2]; + state[11] = IV[3]; + state[12] = uint32(block.get_context(), 0); + state[13] = uint32(block.get_context(), 0); + state[14] = uint32(block.get_context(), block_len); + state[15] = uint32(block.get_context(), flags); + + blake_util::round_fn(state, &block_words[0], 0, true); + blake_util::round_fn(state, &block_words[0], 1, true); + blake_util::round_fn(state, &block_words[0], 2, true); + blake_util::round_fn(state, &block_words[0], 3, true); + blake_util::round_fn(state, &block_words[0], 4, true); + blake_util::round_fn(state, &block_words[0], 5, true); + blake_util::round_fn(state, &block_words[0], 6, true); +} + +template +void blake3_compress_in_place(uint32 cv[8], + const byte_array& block, + uint8_t block_len, + uint8_t flags) +{ + uint32 state[BLAKE3_STATE_SIZE]; + compress_pre(state, cv, block, block_len, flags); + cv[0] = state[0] ^ state[8]; + cv[1] = state[1] ^ state[9]; + cv[2] = state[2] ^ state[10]; + cv[3] = state[3] ^ state[11]; + cv[4] = state[4] ^ state[12]; + cv[5] = state[5] ^ state[13]; + cv[6] = state[6] ^ state[14]; + cv[7] = state[7] ^ state[15]; +} + +template +void blake3_compress_xof(const uint32 cv[8], + const byte_array& block, + uint8_t block_len, + uint8_t flags, + byte_array& out) +{ + uint32 state[BLAKE3_STATE_SIZE]; + compress_pre(state, cv, block, block_len, flags); + + out.write_at(static_cast>(state[0] ^ state[8]).reverse(), 0 * 4); + out.write_at(static_cast>(state[1] ^ state[9]).reverse(), 1 * 4); + out.write_at(static_cast>(state[2] ^ state[10]).reverse(), 2 * 4); + out.write_at(static_cast>(state[3] ^ state[11]).reverse(), 3 * 4); + out.write_at(static_cast>(state[4] ^ state[12]).reverse(), 4 * 4); + out.write_at(static_cast>(state[5] ^ state[13]).reverse(), 5 * 4); + out.write_at(static_cast>(state[6] ^ state[14]).reverse(), 6 * 4); + out.write_at(static_cast>(state[7] ^ state[15]).reverse(), 7 * 4); + + out.write_at(static_cast>(state[8] ^ cv[0]).reverse(), 8 * 4); + out.write_at(static_cast>(state[9] ^ cv[1]).reverse(), 9 * 4); + out.write_at(static_cast>(state[10] ^ cv[2]).reverse(), 10 * 4); + out.write_at(static_cast>(state[11] ^ cv[3]).reverse(), 11 * 4); + out.write_at(static_cast>(state[12] ^ cv[4]).reverse(), 12 * 4); + out.write_at(static_cast>(state[13] ^ cv[5]).reverse(), 13 * 4); + out.write_at(static_cast>(state[14] ^ cv[6]).reverse(), 14 * 4); + out.write_at(static_cast>(state[15] ^ cv[7]).reverse(), 15 * 4); +} + +/* + * Blake3s helper functions. + * + */ +template uint8_t maybe_start_flag(const blake3_hasher* self) +{ + if (self->blocks_compressed == 0) { + return CHUNK_START; + } else { + return 0; + } +} + +template struct output_t { + uint32 input_cv[8]; + byte_array block; + uint8_t block_len; + uint8_t flags; +}; + +template +output_t make_output(const uint32 input_cv[8], + const byte_array& block, + uint8_t block_len, + uint8_t flags) +{ + output_t ret; + for (size_t i = 0; i < (BLAKE3_OUT_LEN >> 2); ++i) { + ret.input_cv[i] = input_cv[i]; + } + ret.block = byte_array(block.get_context(), BLAKE3_BLOCK_LEN); + for (size_t i = 0; i < BLAKE3_BLOCK_LEN; i++) { + ret.block.set_byte(i, block[i]); + } + ret.block_len = block_len; + ret.flags = flags; + return ret; +} + +/* + * Blake3s wrapper functions. + * + */ +template void blake3_hasher_init(blake3_hasher* self) +{ + for (size_t i = 0; i < (BLAKE3_KEY_LEN >> 2); ++i) { + self->key[i] = IV[i]; + self->cv[i] = IV[i]; + } + self->buf = byte_array(self->context, BLAKE3_BLOCK_LEN); + for (size_t i = 0; i < BLAKE3_BLOCK_LEN; i++) { + self->buf.set_byte(i, field_t(self->context, 0)); + } + self->buf_len = 0; + self->blocks_compressed = 0; + self->flags = 0; +} + +template +void blake3_hasher_update(blake3_hasher* self, const byte_array& input, size_t input_len) +{ + if (input_len == 0) { + return; + } + + size_t start_counter = 0; + while (input_len > BLAKE3_BLOCK_LEN) { + blake3_compress_in_place(self->cv, + input.slice(start_counter, BLAKE3_BLOCK_LEN), + BLAKE3_BLOCK_LEN, + self->flags | maybe_start_flag(self)); + self->blocks_compressed = static_cast(self->blocks_compressed + 1); + start_counter += BLAKE3_BLOCK_LEN; + input_len -= BLAKE3_BLOCK_LEN; + } + + size_t take = BLAKE3_BLOCK_LEN - ((size_t)self->buf_len); + if (take > input_len) { + take = input_len; + } + for (size_t i = 0; i < take; i++) { + self->buf.set_byte(self->buf_len + i, input[i + start_counter]); + } + + self->buf_len = static_cast(self->buf_len + (uint8_t)take); + input_len -= take; +} + +template void blake3_hasher_finalize(const blake3_hasher* self, byte_array& out) +{ + uint8_t block_flags = self->flags | maybe_start_flag(self) | CHUNK_END; + output_t output = make_output(self->cv, self->buf, self->buf_len, block_flags); + + byte_array wide_buf(out.get_context(), BLAKE3_BLOCK_LEN); + blake3_compress_xof(output.input_cv, output.block, output.block_len, output.flags | ROOT, wide_buf); + for (size_t i = 0; i < BLAKE3_OUT_LEN; i++) { + out.set_byte(i, wide_buf[i]); + } + return; +} + +} // namespace blake3_internal + +using namespace blake3_internal; + +template byte_array blake3s(const byte_array& input) +{ + if constexpr (Composer::type == waffle::ComposerType::PLOOKUP) { + return blake3s_plookup::blake3s(input); + } + + blake3_hasher hasher = {}; + hasher.context = input.get_context(); + blake3_hasher_init(&hasher); + blake3_hasher_update(&hasher, input, input.size()); + byte_array result(input.get_context(), BLAKE3_OUT_LEN); + blake3_hasher_finalize(&hasher, result); + return result; +} + +template byte_array blake3s(const byte_array& input); +template byte_array blake3s(const byte_array& input); +template byte_array blake3s(const byte_array& input); + +} // namespace stdlib +} // namespace plonk diff --git a/cpp/src/aztec/stdlib/hash/blake3s/blake3s.hpp b/cpp/src/aztec/stdlib/hash/blake3s/blake3s.hpp new file mode 100644 index 0000000000..517fed08a9 --- /dev/null +++ b/cpp/src/aztec/stdlib/hash/blake3s/blake3s.hpp @@ -0,0 +1,19 @@ +#pragma once +#include + +namespace waffle { +class TurboComposer; +class UltraComposer; +} // namespace waffle + +namespace plonk { +namespace stdlib { + +template byte_array blake3s(const byte_array& input); + +extern template byte_array blake3s(const byte_array& input); +extern template byte_array blake3s(const byte_array& input); +extern template byte_array blake3s(const byte_array& input); + +} // namespace stdlib +} // namespace plonk diff --git a/cpp/src/aztec/stdlib/hash/blake3s/blake3s.test.cpp b/cpp/src/aztec/stdlib/hash/blake3s/blake3s.test.cpp new file mode 100644 index 0000000000..29d2001aa4 --- /dev/null +++ b/cpp/src/aztec/stdlib/hash/blake3s/blake3s.test.cpp @@ -0,0 +1,123 @@ +#include "blake3s.hpp" +#include "blake3s_plookup.hpp" +#include +#include +#include +#include + +using namespace barretenberg; +using namespace plonk; + +typedef waffle::TurboComposer Composer; +typedef stdlib::byte_array byte_array; +typedef stdlib::byte_array byte_array_plookup; +typedef stdlib::public_witness_t public_witness_t; +typedef stdlib::public_witness_t public_witness_t_plookup; + +namespace std { +inline std::ostream& operator<<(std::ostream& os, std::vector const& t) +{ + os << "[ "; + for (auto e : t) { + os << std::setfill('0') << std::hex << std::setw(2) << (int)e << " "; + } + os << "]"; + return os; +} +} // namespace std + +TEST(stdlib_blake3s, test_single_block) +{ + Composer composer = Composer(); + std::string input = "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01"; + std::vector input_v(input.begin(), input.end()); + + byte_array input_arr(&composer, input_v); + byte_array output = stdlib::blake3s(input_arr); + + std::vector expected = blake3::blake3s(input_v); + + EXPECT_EQ(output.get_value(), expected); + + auto prover = composer.create_prover(); + + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); + + auto proof = prover.construct_proof(); + + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); +} + +TEST(stdlib_blake3s, test_single_block_plookup) +{ + waffle::UltraComposer composer = waffle::UltraComposer(); + std::string input = "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01"; + std::vector input_v(input.begin(), input.end()); + + byte_array_plookup input_arr(&composer, input_v); + byte_array_plookup output = stdlib::blake3s(input_arr); + + std::vector expected = blake3::blake3s(input_v); + + EXPECT_EQ(output.get_value(), expected); + + auto prover = composer.create_prover(); + std::cout << "prover gates = " << prover.n << std::endl; + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); + + auto proof = prover.construct_proof(); + + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); +} + +TEST(stdlib_blake3s, test_double_block) +{ + Composer composer = Composer(); + std::string input = "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789"; + std::vector input_v(input.begin(), input.end()); + + byte_array input_arr(&composer, input_v); + byte_array output = stdlib::blake3s(input_arr); + + std::vector expected = blake3::blake3s(input_v); + + EXPECT_EQ(output.get_value(), expected); + + auto prover = composer.create_prover(); + + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); + + auto proof = prover.construct_proof(); + + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); +} + +TEST(stdlib_blake3s, test_double_block_plookup) +{ + waffle::UltraComposer composer = waffle::UltraComposer(); + std::string input = "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789"; + std::vector input_v(input.begin(), input.end()); + + byte_array_plookup input_arr(&composer, input_v); + byte_array_plookup output = stdlib::blake3s(input_arr); + + std::vector expected = blake3::blake3s(input_v); + + EXPECT_EQ(output.get_value(), expected); + + auto prover = composer.create_prover(); + std::cout << "prover gates = " << prover.n << std::endl; + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); + + auto proof = prover.construct_proof(); + + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); +} diff --git a/cpp/src/aztec/stdlib/hash/blake3s/blake3s_plookup.cpp b/cpp/src/aztec/stdlib/hash/blake3s/blake3s_plookup.cpp new file mode 100644 index 0000000000..b33c3372e5 --- /dev/null +++ b/cpp/src/aztec/stdlib/hash/blake3s/blake3s_plookup.cpp @@ -0,0 +1,267 @@ +#include "blake3s_plookup.hpp" +#include "../blake2s/blake_util.hpp" + +#include +#include +#include +#include +#include +#include + +namespace plonk { +namespace stdlib { + +namespace blake3s_plookup { + +/* + * Constants and more. + */ +#define BLAKE3_VERSION_STRING "0.3.7" + +// internal flags +enum blake3_flags { + CHUNK_START = 1 << 0, + CHUNK_END = 1 << 1, + PARENT = 1 << 2, + ROOT = 1 << 3, + KEYED_HASH = 1 << 4, + DERIVE_KEY_CONTEXT = 1 << 5, + DERIVE_KEY_MATERIAL = 1 << 6, +}; + +// constants +enum blake3s_constant { + BLAKE3_KEY_LEN = 32, + BLAKE3_OUT_LEN = 32, + BLAKE3_BLOCK_LEN = 64, + BLAKE3_CHUNK_LEN = 1024, + BLAKE3_MAX_DEPTH = 54, + BLAKE3_STATE_SIZE = 16 +}; + +constexpr uint32_t IV[8] = { 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL, + 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL }; + +template struct blake3_hasher { + field_t key[8]; + field_t cv[8]; + byte_array buf; + uint8_t buf_len; + uint8_t blocks_compressed; + uint8_t flags; + Composer* context; +}; + +/* + * Core Blake3s functions. These are similar to that of Blake2s except for a few + * constant parameters and fewer rounds. + * + */ +template +void compress_pre(field_t state[BLAKE3_STATE_SIZE], + const field_t cv[8], + const byte_array& block, + uint8_t block_len, + uint8_t flags) +{ + typedef field_t field_pt; + field_pt block_words[BLAKE3_STATE_SIZE]; + for (size_t i = 0; i < BLAKE3_STATE_SIZE; ++i) { + block_words[i] = field_pt(block.slice(i * 4, 4).reverse()); + } + + state[0] = cv[0]; + state[1] = cv[1]; + state[2] = cv[2]; + state[3] = cv[3]; + state[4] = cv[4]; + state[5] = cv[5]; + state[6] = cv[6]; + state[7] = cv[7]; + state[8] = field_pt(block.get_context(), uint256_t(IV[0])); + state[9] = field_pt(block.get_context(), uint256_t(IV[1])); + state[10] = field_pt(block.get_context(), uint256_t(IV[2])); + state[11] = field_pt(block.get_context(), uint256_t(IV[3])); + state[12] = field_pt(block.get_context(), 0); + state[13] = field_pt(block.get_context(), 0); + state[14] = field_pt(block.get_context(), uint256_t(block_len)); + state[15] = field_pt(block.get_context(), uint256_t(flags)); + + blake_util::round_fn_lookup(state, &block_words[0], 0, true); + blake_util::round_fn_lookup(state, &block_words[0], 1, true); + blake_util::round_fn_lookup(state, &block_words[0], 2, true); + blake_util::round_fn_lookup(state, &block_words[0], 3, true); + blake_util::round_fn_lookup(state, &block_words[0], 4, true); + blake_util::round_fn_lookup(state, &block_words[0], 5, true); + blake_util::round_fn_lookup(state, &block_words[0], 6, true); +} + +template +void blake3_compress_in_place(field_t cv[8], + const byte_array& block, + uint8_t block_len, + uint8_t flags) +{ + typedef field_t field_pt; + field_pt state[BLAKE3_STATE_SIZE]; + compress_pre(state, cv, block, block_len, flags); + + /** + * At this point in the algorithm, a malicious prover could tweak the add_normalise function in `blake_util.hpp` to + * create unexpected overflow in the state matrix. At the end of the `compress_pre()` function, there might be + * overflows in the elements of the first and third rows of the state matrix. But this wouldn't be a problem because + * in the below loop, while reading from the lookup table, we ensure that the overflow is ignored and the result is + * contrained to 32 bits. + */ + for (size_t i = 0; i < (BLAKE3_STATE_SIZE >> 1); i++) { + const auto lookup = plookup_read::get_lookup_accumulators(BLAKE_XOR, state[i], state[i + 8], true); + cv[i] = lookup[ColumnIdx::C3][0]; + } +} + +template +void blake3_compress_xof(const field_t cv[8], + const byte_array& block, + uint8_t block_len, + uint8_t flags, + byte_array& out) +{ + typedef field_t field_pt; + field_pt state[BLAKE3_STATE_SIZE]; + + compress_pre(state, cv, block, block_len, flags); + + /** + * The same note as in the above `blake3_compress_in_place()` function. Here too, reading from the lookup table + * ensures that correct 32-bit inputs are used. + */ + for (size_t i = 0; i < (BLAKE3_STATE_SIZE >> 1); i++) { + const auto lookup_1 = plookup_read::get_lookup_accumulators(BLAKE_XOR, state[i], state[i + 8], true); + byte_array out_bytes_1(lookup_1[ColumnIdx::C3][0], 4); + out.write_at(out_bytes_1.reverse(), i * 4); + + const auto lookup_2 = plookup_read::get_lookup_accumulators(BLAKE_XOR, state[i + 8], cv[i], true); + byte_array out_bytes_2(lookup_2[ColumnIdx::C3][0], 4); + out.write_at(out_bytes_2.reverse(), (i + 8) * 4); + } +} + +/* + * Blake3s helper functions. + * + */ +template uint8_t maybe_start_flag(const blake3_hasher* self) +{ + if (self->blocks_compressed == 0) { + return CHUNK_START; + } else { + return 0; + } +} + +template struct output_t { + field_t input_cv[8]; + byte_array block; + uint8_t block_len; + uint8_t flags; +}; + +template +output_t make_output(const field_t input_cv[8], + const byte_array& block, + uint8_t block_len, + uint8_t flags) +{ + output_t ret; + for (size_t i = 0; i < (BLAKE3_OUT_LEN >> 2); ++i) { + ret.input_cv[i] = input_cv[i]; + } + ret.block = byte_array(block.get_context(), BLAKE3_BLOCK_LEN); + for (size_t i = 0; i < BLAKE3_BLOCK_LEN; i++) { + ret.block.set_byte(i, block[i]); + } + ret.block_len = block_len; + ret.flags = flags; + return ret; +} + +/* + * Blake3s wrapper functions. + * + */ +template void blake3_hasher_init(blake3_hasher* self) +{ + typedef field_t field_pt; + for (size_t i = 0; i < (BLAKE3_KEY_LEN >> 2); ++i) { + self->key[i] = field_pt(uint256_t(IV[i])); + self->cv[i] = field_pt(uint256_t(IV[i])); + } + self->buf = byte_array(self->context, BLAKE3_BLOCK_LEN); + for (size_t i = 0; i < BLAKE3_BLOCK_LEN; i++) { + self->buf.set_byte(i, field_pt(self->context, 0)); + } + self->buf_len = 0; + self->blocks_compressed = 0; + self->flags = 0; +} + +template +void blake3_hasher_update(blake3_hasher* self, const byte_array& input, size_t input_len) +{ + if (input_len == 0) { + return; + } + + size_t start_counter = 0; + while (input_len > BLAKE3_BLOCK_LEN) { + blake3_compress_in_place(self->cv, + input.slice(start_counter, BLAKE3_BLOCK_LEN), + BLAKE3_BLOCK_LEN, + self->flags | maybe_start_flag(self)); + self->blocks_compressed = static_cast(self->blocks_compressed + 1); + start_counter += BLAKE3_BLOCK_LEN; + input_len -= BLAKE3_BLOCK_LEN; + } + + size_t take = BLAKE3_BLOCK_LEN - ((size_t)self->buf_len); + if (take > input_len) { + take = input_len; + } + for (size_t i = 0; i < take; i++) { + self->buf.set_byte(self->buf_len + i, input[i + start_counter]); + } + + self->buf_len = static_cast(self->buf_len + (uint8_t)take); + input_len -= take; +} + +template void blake3_hasher_finalize(const blake3_hasher* self, byte_array& out) +{ + uint8_t block_flags = self->flags | maybe_start_flag(self) | CHUNK_END; + output_t output = make_output(self->cv, self->buf, self->buf_len, block_flags); + + byte_array wide_buf(out.get_context(), BLAKE3_BLOCK_LEN); + blake3_compress_xof(output.input_cv, output.block, output.block_len, output.flags | ROOT, wide_buf); + for (size_t i = 0; i < BLAKE3_OUT_LEN; i++) { + out.set_byte(i, wide_buf[i]); + } + return; +} + +template byte_array blake3s(const byte_array& input) +{ + blake3_hasher hasher = {}; + hasher.context = input.get_context(); + blake3_hasher_init(&hasher); + blake3_hasher_update(&hasher, input, input.size()); + byte_array result(input.get_context(), BLAKE3_OUT_LEN); + blake3_hasher_finalize(&hasher, result); + return result; +} + +template byte_array blake3s(const byte_array& input); + +} // namespace blake3s_plookup + +} // namespace stdlib +} // namespace plonk diff --git a/cpp/src/aztec/stdlib/hash/blake3s/blake3s_plookup.hpp b/cpp/src/aztec/stdlib/hash/blake3s/blake3s_plookup.hpp new file mode 100644 index 0000000000..777698c4ea --- /dev/null +++ b/cpp/src/aztec/stdlib/hash/blake3s/blake3s_plookup.hpp @@ -0,0 +1,24 @@ +#pragma once +#include +#include +#include + +#include + +#include "../../primitives/field/field.hpp" +#include "../../primitives/composers/composers_fwd.hpp" +#include "../../primitives/packed_byte_array/packed_byte_array.hpp" + +namespace plonk { +namespace stdlib { + +namespace blake3s_plookup { + +template byte_array blake3s(const byte_array& input); + +extern template byte_array blake3s(const byte_array& input); + +} // namespace blake3s_plookup + +} // namespace stdlib +} // namespace plonk diff --git a/cpp/src/aztec/stdlib/hash/pedersen/pedersen.bench.cpp b/cpp/src/aztec/stdlib/hash/pedersen/pedersen.bench.cpp index 27285dc082..d73444f244 100644 --- a/cpp/src/aztec/stdlib/hash/pedersen/pedersen.bench.cpp +++ b/cpp/src/aztec/stdlib/hash/pedersen/pedersen.bench.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #define BARRETENBERG_SRS_PATH "../srs_db/ignition" diff --git a/cpp/src/aztec/stdlib/hash/pedersen/pedersen.cpp b/cpp/src/aztec/stdlib/hash/pedersen/pedersen.cpp index b1dc7a7fa4..e35309fcc9 100644 --- a/cpp/src/aztec/stdlib/hash/pedersen/pedersen.cpp +++ b/cpp/src/aztec/stdlib/hash/pedersen/pedersen.cpp @@ -222,8 +222,40 @@ point pedersen::hash_single(const field_t& in, if (i > 0) { ctx->create_fixed_group_add_gate(round_quad); } else { + if constexpr (C::type == waffle::PLOOKUP && + C::merkle_hash_type == waffle::MerkleHashType::FIXED_BASE_PEDERSEN) { + /* In TurboComposer, the selector q_5 is used to show that w_1 and w_2 are properly initialized to the + * coordinates of P_s = (-s + 4^n)[g]. In UltraPlonK, we have removed q_5 for overall efficiency (it + * would only be used here in this gate), but this presents us a cost in the present circuit: we must + * use an additional gate to perform part of the initialization. Since q_5 is only involved in the + * x-coordinate initialization (in the notation of the widget, Constraint 5), we only perform that part + * of the initialization with additional gates, letting Constraints 4 and 6 be handled in the Ultra + * version of the widget as in the Turbo verison. + * x-coordinate initialization constraint (Pi = origin_points[i] for i = 0,1): + * c * (P0.x - x_0) + (P0.x - P1.x) * (1 - a_0)) + * = -c * x_0 + c * P0.x - (P0.x - P1.x) * a_0 + (P0.x - P1.x) + * In present terms, x_0 = round_quad.a, c = round_quad.c, P0.x = init_quad.q_x_1, + * a_0 = round_quad.d, P0.x - P1.x = init_quad.q_x_2, + * so we want to impose the constraint: + * 0 = -round_quad.a * round_quad.c + * + init_quad.q_x_1 * round_quad.c + * - init_quad.q_x_2 * round_quad.d + * + init_quad.q_x_2 + * */ + waffle::mul_quad x_init_quad{ .a = round_quad.a, + .b = round_quad.c, + .c = 0, + .d = round_quad.d, + .mul_scaling = -1, + .a_scaling = 0, + .b_scaling = init_quad.q_x_1, + .c_scaling = 0, + .d_scaling = -init_quad.q_x_2, + .const_scaling = init_quad.q_x_2 }; + ctx->create_big_mul_gate(x_init_quad); + } ctx->create_fixed_group_add_gate_with_init(round_quad, init_quad); - } + }; accumulator_witnesses.push_back(round_quad.d); } @@ -248,7 +280,7 @@ point pedersen::hash_single(const field_t& in, result.y = field_t(ctx); result.y.witness_index = add_quad.b; - field_t::from_witness_index(ctx, add_quad.d).assert_equal(in, "pedersen d != in"); + field_t::from_witness_index(ctx, add_quad.d).assert_equal(in, "pedersen: d != in"); if (validate_input_is_in_field) { validate_wnaf_is_in_field(ctx, accumulator_witnesses); @@ -260,8 +292,8 @@ point pedersen::hash_single(const field_t& in, * Check the wnaf sum is smaller than the circuit modulus * * When we compute a scalar mul e.g. x * [1], we decompose `x` into an accumulating sum of 2-bit non-adjacent form - *values. In `hash_single`, we validate that the sum of the 2-bit NAFs (`w`) equals x. But we only check that `w == x - *mod r` where r is the circuit modulus. + * values. In `hash_single`, we validate that the sum of the 2-bit NAFs (`w`) equals x. But we only check that `w == x + * mod r` where r is the circuit modulus. * * If we require the pedersen hash to be injective, we must ensure that `w < r`. * Typically this is required for all instances where `w` represents a field element. @@ -319,7 +351,7 @@ template void pedersen::validate_wnaf_is_in_field(C* ctx, const * * We can extract w.hi from accumulator[64], but we need to remove the contribution from the wnaf skew * We can extract w.lo by subtracting w.hi * 2^{126} from the final accumulator (the final accumulator will be - *equal to `w`) + * equal to `w`) * * 2. Compute y.lo = (r.lo - w.lo) + 2^{126} (the 2^126 constant ensures this is positive) * r.lo is the least significant 126 bits of r @@ -340,7 +372,7 @@ template void pedersen::validate_wnaf_is_in_field(C* ctx, const * 6. Range constrain y.hi to be a 128-bit integer * * We slice the low limb to be 126 bits so that both our range checks can be over 128-bit integers (if the range is - *a multiple of 8 we save 1 gate per range check) + * a multiple of 8 we save 1 gate per range check) * * The following table describes the range of values the above terms can take, if w < r * @@ -396,7 +428,7 @@ template void pedersen::validate_wnaf_is_in_field(C* ctx, const * * Therefore the 2^{-254} term in accumulator[0] will translate to a value of `1` when `input` is computed * This corresponds to `input` being an even number (without a skew term, wnaf represenatations can only express odd - *numbers) + * numbers) * * We need to factor out this skew term from w.hi as it is part of w.lo * @@ -427,11 +459,29 @@ template void pedersen::validate_wnaf_is_in_field(C* ctx, const field_t y_lo = (-reconstructed_input).add_two(high_limb_with_skew * shift + (r_lo + shift), is_even); - // Validate y.lo is a 128-bit integer - const auto y_lo_accumulators = ctx->decompose_into_base4_accumulators(y_lo.normalize().witness_index, 128); - - // Extract y.overlap, the 2 most significant bits of y.lo - field_t y_overlap = field_t::from_witness_index(ctx, y_lo_accumulators[0]) - 1; + field_t y_overlap; + if constexpr (C::type == waffle::ComposerType::PLOOKUP) { + // carve out the 2 high bits from y_lo and instantiate as y_overlap + const uint256_t y_lo_value = y_lo.get_value(); + const uint256_t y_overlap_value = y_lo_value >> 126; + y_overlap = witness_t(ctx, y_overlap_value); + + // Validate y.lo is a 128-bit integer + field_t y_remainder = y_lo - (y_overlap * field_t(uint256_t(1ULL) << 126)); + y_overlap.create_range_constraint(2, + "pedersen: range constraint on y_overlap fails in validate_wnaf_is_in_field"); + y_remainder.create_range_constraint( + 126, "pedersen: range constraint on y_remainder fails in validate_wnaf_is_in_field"); + y_overlap = y_overlap - 1; + } else { + // Validate y.lo is a 128-bit integer + const auto y_lo_accumulators = ctx->decompose_into_base4_accumulators( + y_lo.normalize().witness_index, + 128, + "pedersen: range constraint on y_lo fails in validate_wnaf_is_in_field"); + // Extract y.overlap, the 2 most significant bits of y.lo + y_overlap = field_t::from_witness_index(ctx, y_lo_accumulators[0]) - 1; + } /** * -126 @@ -442,7 +492,7 @@ template void pedersen::validate_wnaf_is_in_field(C* ctx, const field_t y_hi = (-is_even * fr(uint256_t(1) << 126).invert()).add_two(-high_limb_with_skew, y_overlap + (r_hi)); // Validate y.hi is a 128-bit integer - ctx->decompose_into_base4_accumulators(y_hi.normalize().witness_index, 128); + y_hi.create_range_constraint(128, "pedersen: range constraint on y_lo fails in validate_wnaf_is_in_field"); } template point pedersen::accumulate(const std::vector& to_accumulate) @@ -465,8 +515,9 @@ field_t pedersen::compress_unsafe(const field_t& in_left, const size_t hash_index, const bool validate_input_is_in_field) { - if constexpr (C::type == waffle::ComposerType::PLOOKUP) { - return pedersen_plookup::compress(in_left, in_right); + if constexpr (C::type == waffle::ComposerType::PLOOKUP && + C::merkle_hash_type == waffle::MerkleHashType::LOOKUP_PEDERSEN) { + return pedersen_plookup::compress({ in_left, in_right }); } std::vector accumulators; @@ -479,8 +530,9 @@ field_t pedersen::compress_unsafe(const field_t& in_left, template point pedersen::commit(const std::vector& inputs, const size_t hash_index) { - if constexpr (C::type == waffle::ComposerType::PLOOKUP) { - return pedersen_plookup::commit(inputs); + if constexpr (C::type == waffle::ComposerType::PLOOKUP && + C::merkle_hash_type == waffle::MerkleHashType::LOOKUP_PEDERSEN) { + return pedersen_plookup::commit(inputs, hash_index); } std::vector to_accumulate; @@ -493,22 +545,19 @@ template point pedersen::commit(const std::vector& i template field_t pedersen::compress(const std::vector& inputs, const size_t hash_index) { - if (C::type == waffle::ComposerType::PLOOKUP) { - // TODO handle hash index in plookup. This is a tricky problem but - // we can defer solving it until we migrate to UltraPlonk - return pedersen_plookup::compress(inputs); + if constexpr (C::type == waffle::ComposerType::PLOOKUP && + C::merkle_hash_type == waffle::MerkleHashType::LOOKUP_PEDERSEN) { + return pedersen_plookup::compress(inputs, hash_index); } + return commit(inputs, hash_index).x; } -// If the input values are all zero, we return the array length instead of `0` +// If the input values are all zero, we return the array length instead of `0\` // This is because we require the inputs to regular pedersen compression function are nonzero (we use this method to // hash the base layer of our merkle trees) template field_t pedersen::compress(const byte_array& input) { - if constexpr (C::type == waffle::ComposerType::PLOOKUP) { - return pedersen_plookup::compress(packed_byte_array(input)); - } const size_t num_bytes = input.size(); const size_t bytes_per_element = 31; size_t num_elements = (num_bytes % bytes_per_element != 0) + (num_bytes / bytes_per_element); @@ -537,7 +586,7 @@ template field_t pedersen::compress(const byte_array& input) template class pedersen; template class pedersen; -template class pedersen; +template class pedersen; } // namespace stdlib } // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/hash/pedersen/pedersen.hpp b/cpp/src/aztec/stdlib/hash/pedersen/pedersen.hpp index 3cfe96d833..fc8c71c263 100644 --- a/cpp/src/aztec/stdlib/hash/pedersen/pedersen.hpp +++ b/cpp/src/aztec/stdlib/hash/pedersen/pedersen.hpp @@ -52,7 +52,7 @@ template class pedersen { extern template class pedersen; extern template class pedersen; -extern template class pedersen; +extern template class pedersen; } // namespace stdlib } // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/hash/pedersen/pedersen.test.cpp b/cpp/src/aztec/stdlib/hash/pedersen/pedersen.test.cpp index e8e7366ec1..f0563293f0 100644 --- a/cpp/src/aztec/stdlib/hash/pedersen/pedersen.test.cpp +++ b/cpp/src/aztec/stdlib/hash/pedersen/pedersen.test.cpp @@ -1,6 +1,7 @@ #include "pedersen.hpp" #include "pedersen_plookup.hpp" #include +#include #include #include #include @@ -46,7 +47,7 @@ template class stdlib_pedersen : public testing::Test { uint64_t entry = wnafs[i]; grumpkin::fr prev = result + result; prev = prev + prev; - if ((entry & stdlib::WNAF_MASK) == 0) { + if ((entry & 0xffffff) == 0) { if (((entry >> 31UL) & 1UL) == 1UL) { result = prev - grumpkin::fr::one(); } else { @@ -166,13 +167,14 @@ template class stdlib_pedersen : public testing::Test { EXPECT_EQ(result, true); auto hash_output = pedersen_recover(left_in, right_in); - EXPECT_EQ(out.get_value(), hash_output.x); fr recovered_left = wnaf_recover(left_in); fr recovered_right = wnaf_recover(right_in); EXPECT_EQ(left_in, recovered_left); EXPECT_EQ(right_in, recovered_right); + EXPECT_EQ(out.get_value(), hash_output.x); + fr compress_native = crypto::pedersen::compress_native({ left.get_value(), right.get_value() }); EXPECT_EQ(out.get_value(), compress_native); } @@ -212,6 +214,7 @@ template class stdlib_pedersen : public testing::Test { auto hash_output_1_with_zero = pedersen_recover(zero_fr, one_fr); auto hash_output_1_with_r = pedersen_recover(r_fr, one_fr); auto hash_output_2 = pedersen_recover(r_minus_one_fr, r_minus_two_fr); + EXPECT_EQ(out_1_with_zero.get_value(), hash_output_1_with_zero.x); EXPECT_EQ(out_1_with_r.get_value(), hash_output_1_with_r.x); EXPECT_EQ(out_2.get_value(), hash_output_2.x); @@ -235,6 +238,7 @@ template class stdlib_pedersen : public testing::Test { fr compress_native_with_zero = crypto::pedersen::compress_native({ out_1_with_zero.get_value(), out_2.get_value() }); fr compress_native_with_r = crypto::pedersen::compress_native({ out_1_with_r.get_value(), out_2.get_value() }); + EXPECT_EQ(out_1_with_zero.get_value(), compress_native_1_with_zero); EXPECT_EQ(out_1_with_r.get_value(), compress_native_1_with_r); EXPECT_EQ(out_2.get_value(), compress_native_2); @@ -321,17 +325,17 @@ template class stdlib_pedersen : public testing::Test { input.push_back(engine.get_random_uint8()); } - auto expected = crypto::pedersen::compress_native(input); + fr expected = crypto::pedersen::compress_native(input); byte_array_ct circuit_input(&composer, input); auto result = pedersen::compress(circuit_input); EXPECT_EQ(result.get_value(), expected); - auto prover = composer.create_unrolled_prover(); + auto prover = composer.create_prover(); printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_unrolled_verifier(); + auto verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); @@ -399,16 +403,18 @@ template class stdlib_pedersen : public testing::Test { { Composer composer = Composer("../srs_db/ignition/"); - std::array inputs; - std::array, 8> witness_inputs; + std::vector inputs; + inputs.reserve(8); + std::vector> witness_inputs; for (size_t i = 0; i < 8; ++i) { - inputs[i] = barretenberg::fr::random_element(); - witness_inputs[i] = witness_ct(&composer, inputs[i]); + inputs.emplace_back(barretenberg::fr::random_element()); + witness_inputs.emplace_back(witness_ct(&composer, inputs[i])); } - barretenberg::fr expected = crypto::pedersen::compress_native(inputs); - auto result = pedersen::compress(witness_inputs); + constexpr size_t hash_idx = 10; + grumpkin::fq expected = crypto::pedersen::compress_native(inputs, hash_idx); + auto result = pedersen::compress(witness_inputs, hash_idx); EXPECT_EQ(result.get_value(), expected); } @@ -436,11 +442,7 @@ template class stdlib_pedersen : public testing::Test { } }; -typedef testing::Types - ComposerTypes; +typedef testing::Types ComposerTypes; TYPED_TEST_SUITE(stdlib_pedersen, ComposerTypes); @@ -484,102 +486,98 @@ TYPED_TEST(stdlib_pedersen, compress_constants) TestFixture::test_compress_constants(); }; -} // namespace test_stdlib_pedersen - -// PLOOKUP REMNANTS BELOW HERE - -// TEST(stdlib_pedersen, test_pedersen_plookup) -// { -// typedef stdlib::field_t field_pt; -// typedef stdlib::witness_t witness_pt; - -// waffle::PlookupComposer composer = waffle::PlookupComposer(); - -// fr left_in = fr::random_element(); -// fr right_in = fr::random_element(); +// Tests of Plookup-based Pedersen hash +namespace plookup_pedersen_tests { +typedef stdlib::field_t field_ct; +typedef stdlib::witness_t witness_ct; +TEST(stdlib_pedersen, test_pedersen_plookup) +{ + waffle::UltraComposer composer = waffle::UltraComposer(); -// field_pt left = witness_pt(&composer, left_in); -// field_pt right = witness_pt(&composer, right_in); + fr left_in = fr::random_element(); + fr right_in = fr::random_element(); -// field_pt result = stdlib::pedersen::compress(left, right); + field_ct left = witness_ct(&composer, left_in); + field_ct right = witness_ct(&composer, right_in); -// fr expected = crypto::pedersen::sidon::compress_native(left_in, right_in); + field_ct result = stdlib::pedersen_plookup::compress(left, right); -// EXPECT_EQ(result.get_value(), expected); + fr expected = crypto::pedersen::lookup::hash_pair(left_in, right_in); -// auto prover = composer.create_prover(); + EXPECT_EQ(result.get_value(), expected); -// printf("composer gates = %zu\n", composer.get_num_gates()); -// auto verifier = composer.create_verifier(); + auto prover = composer.create_prover(); -// waffle::plonk_proof proof = prover.construct_proof(); + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); -// bool proof_result = verifier.verify_proof(proof); -// EXPECT_EQ(proof_result, true); -// } + waffle::plonk_proof proof = prover.construct_proof(); -// TEST(stdlib_pedersen, test_compress_many_plookup) -// { -// typedef stdlib::field_t field_pt; -// typedef stdlib::witness_t witness_pt; + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); +} -// waffle::PlookupComposer composer = waffle::PlookupComposer(); +TEST(stdlib_pedersen, test_compress_many_plookup) +{ + waffle::UltraComposer composer = waffle::UltraComposer(); + + std::vector input_values{ + fr::random_element(), fr::random_element(), fr::random_element(), + fr::random_element(), fr::random_element(), fr::random_element(), + }; + std::vector inputs; + for (const auto& input : input_values) { + inputs.emplace_back(witness_ct(&composer, input)); + } -// std::vector input_values{ -// fr::random_element(), fr::random_element(), fr::random_element(), -// fr::random_element(), fr::random_element(), fr::random_element(), -// }; -// std::vector inputs; -// for (const auto& input : input_values) { -// inputs.emplace_back(witness_pt(&composer, input)); -// } + const size_t hash_idx = 20; -// field_pt result = stdlib::pedersen::compress(inputs); + field_ct result = stdlib::pedersen_plookup::compress(inputs, hash_idx); -// auto t0 = crypto::pedersen::sidon::compress_native(input_values[0], input_values[1]); -// auto t1 = crypto::pedersen::sidon::compress_native(input_values[2], input_values[3]); -// auto t2 = crypto::pedersen::sidon::compress_native(input_values[4], input_values[5]); -// auto t3 = crypto::pedersen::sidon::compress_native(0, 0); + auto expected = crypto::pedersen::lookup::compress_native(input_values, hash_idx); -// auto t4 = crypto::pedersen::sidon::compress_native(t0, t1); -// auto t5 = crypto::pedersen::sidon::compress_native(t2, t3); + EXPECT_EQ(result.get_value(), expected); -// auto expected = crypto::pedersen::sidon::compress_native(t4, t5); + auto prover = composer.create_prover(); -// EXPECT_EQ(result.get_value(), expected); + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); -// auto prover = composer.create_prover(); + waffle::plonk_proof proof = prover.construct_proof(); -// printf("composer gates = %zu\n", composer.get_num_gates()); -// auto verifier = composer.create_verifier(); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); +} -// waffle::plonk_proof proof = prover.construct_proof(); +TEST(stdlib_pedersen, test_merkle_damgard_compress_plookup) +{ + waffle::UltraComposer composer = waffle::UltraComposer(); + + std::vector input_values{ + fr::random_element(), fr::random_element(), fr::random_element(), + fr::random_element(), fr::random_element(), fr::random_element(), + }; + std::vector inputs; + for (const auto& input : input_values) { + inputs.emplace_back(witness_ct(&composer, input)); + } + field_ct iv = witness_ct(&composer, fr(10)); -// bool proof_result = verifier.verify_proof(proof); -// EXPECT_EQ(proof_result, true); -// } + field_ct result = stdlib::pedersen_plookup::merkle_damgard_compress(inputs, iv).x; -// TEST(stdlib_pedersen, test_sidon_compress_constants) -// { -// typedef stdlib::field_t field_pt; -// typedef stdlib::witness_t witness_pt; + auto expected = crypto::pedersen::lookup::merkle_damgard_compress(input_values, 10); -// waffle::PlookupComposer composer = waffle::PlookupComposer(); + EXPECT_EQ(result.get_value(), expected.normalize().x); -// std::vector inputs; -// std::vector> witness_inputs; + auto prover = composer.create_prover(); -// for (size_t i = 0; i < 8; ++i) { -// inputs.push_back(barretenberg::fr::random_element()); -// if (i % 2 == 1) { -// witness_inputs.push_back(witness_pt(&composer, inputs[i])); -// } else { -// witness_inputs.push_back(field_pt(&composer, inputs[i])); -// } -// } + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); -// barretenberg::fr expected = crypto::pedersen::sidon::compress_native(inputs); -// auto result = stdlib::pedersen::compress(witness_inputs); + waffle::plonk_proof proof = prover.construct_proof(); -// EXPECT_EQ(result.get_value(), expected); -// } \ No newline at end of file + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); +} +} // namespace plookup_pedersen_tests +} // namespace test_stdlib_pedersen diff --git a/cpp/src/aztec/stdlib/hash/pedersen/pedersen_plookup.cpp b/cpp/src/aztec/stdlib/hash/pedersen/pedersen_plookup.cpp index 672bc63994..a10e390c0b 100644 --- a/cpp/src/aztec/stdlib/hash/pedersen/pedersen_plookup.cpp +++ b/cpp/src/aztec/stdlib/hash/pedersen/pedersen_plookup.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "../../primitives/composers/composers.hpp" #include "../../primitives/plookup/plookup.hpp" @@ -19,17 +20,18 @@ template point pedersen_plookup::add_points(const point& p1, grumpkin::fq y_2_raw = p2.y.get_value(); grumpkin::fq endomorphism_coefficient = 1; grumpkin::fq sign_coefficient = 1; + grumpkin::fq beta = grumpkin::fq::cube_root_of_unity(); switch (add_type) { case ONE: { break; } case LAMBDA: { - endomorphism_coefficient = grumpkin::fq::beta(); + endomorphism_coefficient = beta; x_2_raw *= endomorphism_coefficient; break; } case ONE_PLUS_LAMBDA: { - endomorphism_coefficient = grumpkin::fq::beta().sqr(); + endomorphism_coefficient = beta.sqr(); sign_coefficient = -1; x_2_raw *= endomorphism_coefficient; y_2_raw = -y_2_raw; @@ -68,29 +70,57 @@ template point pedersen_plookup::hash_single(const field_t& s { if (scalar.is_constant()) { C* ctx = scalar.get_context(); - const auto hash_native = crypto::pedersen::sidon::compress_single(scalar.get_value(), parity).normalize(); + const auto hash_native = crypto::pedersen::lookup::hash_single(scalar.get_value(), parity).normalize(); return { field_t(ctx, hash_native.x), field_t(ctx, hash_native.y) }; } - std::array, 3> sequence; + // Slice the input scalar in lower 126 and higher 128 bits. + C* ctx = scalar.get_context(); + const field_t y_hi = witness_t(ctx, uint256_t(scalar.get_value()).slice(126, 256)); + const field_t y_lo = witness_t(ctx, uint256_t(scalar.get_value()).slice(0, 126)); + + ReadData lookup_hi, lookup_lo; if (parity) { - sequence = plookup::read_sequence_from_table(waffle::PlookupMultiTableId::PEDERSEN_RIGHT, scalar); + lookup_lo = plookup_read::get_lookup_accumulators(MultiTableId::PEDERSEN_RIGHT_LO, y_lo); + lookup_hi = plookup_read::get_lookup_accumulators(MultiTableId::PEDERSEN_RIGHT_HI, y_hi); } else { - sequence = plookup::read_sequence_from_table(waffle::PlookupMultiTableId::PEDERSEN_LEFT, scalar); + lookup_lo = plookup_read::get_lookup_accumulators(MultiTableId::PEDERSEN_LEFT_LO, y_lo); + lookup_hi = plookup_read::get_lookup_accumulators(MultiTableId::PEDERSEN_LEFT_HI, y_hi); } - const size_t num_lookups = sequence[0].size(); - - point p1{ sequence[1][num_lookups - 1], sequence[2][num_lookups - 1] }; + // Check if (r_hi - y_hi) is 128 bits and if (r_hi - y_hi) == 0, then + // (r_lo - y_lo) must be 126 bits. + constexpr uint256_t modulus = fr::modulus; + const field_t r_lo = witness_t(ctx, modulus.slice(0, 126)); + const field_t r_hi = witness_t(ctx, modulus.slice(126, 256)); + + const field_t term_hi = r_hi - y_hi; + const field_t term_lo = (r_lo - y_lo) * field_t(term_hi == field_t(0)); + term_hi.normalize().create_range_constraint(128); + term_lo.normalize().create_range_constraint(126); + + const size_t num_lookups_lo = lookup_lo[ColumnIdx::C1].size(); + const size_t num_lookups_hi = lookup_hi[ColumnIdx::C1].size(); + + point p1{ lookup_lo[ColumnIdx::C2][1], lookup_lo[ColumnIdx::C3][1] }; + point p2{ lookup_lo[ColumnIdx::C2][0], lookup_lo[ColumnIdx::C3][0] }; + point res = add_points(p1, p2, LAMBDA); + + for (size_t i = 2; i < num_lookups_lo; ++i) { + point p2 = { lookup_lo[ColumnIdx::C2][i], lookup_lo[ColumnIdx::C3][i] }; + AddType basic_type = (i % 2 == 0) ? LAMBDA : ONE; + point p3 = add_points(res, p2, basic_type); + res = p3; + } - for (size_t i = 0; i < num_lookups - 1; ++i) { - point p2 = { sequence[1][i], sequence[2][i] }; - AddType type = (i % 3 == 0) ? LAMBDA : (i % 3 == 1 ? ONE : ONE_PLUS_LAMBDA); - point p3 = add_points(p1, p2, type); - p1 = p3; + for (size_t i = 0; i < num_lookups_hi; ++i) { + point p2 = { lookup_hi[ColumnIdx::C2][i], lookup_hi[ColumnIdx::C3][i] }; + AddType basic_type = (i % 2 == 0) ? LAMBDA : ONE; + point p3 = add_points(res, p2, basic_type); + res = p3; } - return p1; + return res; } template point pedersen_plookup::compress_to_point(const field_t& left, const field_t& right) @@ -106,51 +136,34 @@ template field_t pedersen_plookup::compress(const field_t& le return compress_to_point(left, right).x; } -template point pedersen_plookup::commit(const std::vector& inputs) +template +point pedersen_plookup::merkle_damgard_compress(const std::vector& inputs, const field_t& iv) { - const size_t num_inputs = inputs.size(); - - size_t num_tree_levels = numeric::get_msb(num_inputs) + 1; - if (1UL << num_tree_levels < num_inputs) { - ++num_tree_levels; + if (inputs.size() == 0) { + return point{ 0, 0 }; } - std::vector previous_leaves(inputs.begin(), inputs.end()); - - for (size_t i = 0; i < num_tree_levels - 1; ++i) { - const size_t num_leaves = 1UL << (num_tree_levels - i); - std::vector current_leaves; - for (size_t j = 0; j < num_leaves; j += 2) { - field_t left; - field_t right; - if (j < previous_leaves.size()) { - left = previous_leaves[j]; - } else { - left = 0; - } - - if ((j + 1) < previous_leaves.size()) { - right = previous_leaves[j + 1]; - } else { - right = 0; - } - - current_leaves.push_back(compress(left, right)); - } - - previous_leaves.resize(current_leaves.size()); - std::copy(current_leaves.begin(), current_leaves.end(), previous_leaves.begin()); + auto result = plookup_read::get_lookup_accumulators(MultiTableId::PEDERSEN_IV, iv)[ColumnIdx::C2][0]; + auto num_inputs = inputs.size(); + for (size_t i = 0; i < num_inputs; i++) { + result = compress(result, inputs[i]); } - return compress_to_point(previous_leaves[0], previous_leaves[1]); + return compress_to_point(result, field_t(num_inputs)); +} + +template point pedersen_plookup::commit(const std::vector& inputs, const size_t hash_index) +{ + return merkle_damgard_compress(inputs, field_t(hash_index)); } -template field_t pedersen_plookup::compress(const std::vector& inputs) +template +field_t pedersen_plookup::compress(const std::vector& inputs, const size_t hash_index) { - return commit(inputs).x; + return commit(inputs, hash_index).x; } -template class pedersen_plookup; +template class pedersen_plookup; } // namespace stdlib } // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/hash/pedersen/pedersen_plookup.hpp b/cpp/src/aztec/stdlib/hash/pedersen/pedersen_plookup.hpp index b4c988efa2..b03603c68b 100644 --- a/cpp/src/aztec/stdlib/hash/pedersen/pedersen_plookup.hpp +++ b/cpp/src/aztec/stdlib/hash/pedersen/pedersen_plookup.hpp @@ -25,13 +25,22 @@ template class pedersen_plookup { public: static field_t compress(const field_t& left, const field_t& right); - static field_t compress(const std::vector& inputs); - static field_t compress(const packed_byte_array& input) { return compress(input.get_limbs()); }; + static field_t compress(const std::vector& inputs, const size_t hash_index = 0); + static field_t compress(const packed_byte_array& input) { return compress(input.get_limbs()); } + + template static field_t compress(const std::array& inputs) + { + std::vector in(inputs.begin(), inputs.end()); + return compress(in); + } + + static point merkle_damgard_compress(const std::vector& inputs, const field_t& iv); + + static point commit(const std::vector& inputs, const size_t hash_index = 0); - static point commit(const std::vector& inputs); static point compress_to_point(const field_t& left, const field_t& right); }; -extern template class pedersen_plookup; +extern template class pedersen_plookup; } // namespace stdlib } // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/hash/sha256/sha256.bench.cpp b/cpp/src/aztec/stdlib/hash/sha256/sha256.bench.cpp index 9a6142a907..f7be495c0b 100644 --- a/cpp/src/aztec/stdlib/hash/sha256/sha256.bench.cpp +++ b/cpp/src/aztec/stdlib/hash/sha256/sha256.bench.cpp @@ -1,11 +1,11 @@ #include "sha256.hpp" #include #include -#include -#include +#include +#include using namespace benchmark; -using namespace plonk::stdlib::types::plookup; +using namespace plonk::stdlib::types; constexpr size_t NUM_HASHES = 8; constexpr size_t BYTES_PER_CHUNK = 512; diff --git a/cpp/src/aztec/stdlib/hash/sha256/sha256.cpp b/cpp/src/aztec/stdlib/hash/sha256/sha256.cpp index 3d0510b876..955a509995 100644 --- a/cpp/src/aztec/stdlib/hash/sha256/sha256.cpp +++ b/cpp/src/aztec/stdlib/hash/sha256/sha256.cpp @@ -2,7 +2,7 @@ #include "sha256_plookup.hpp" #include #include -#include +#include #include namespace plonk { @@ -121,13 +121,13 @@ template byte_array sha256_block(const byte_array< ASSERT(input.size() == 64); std::array hash; - prepare_constants(hash); + prepare_constants(hash); std::array hash_input; for (size_t i = 0; i < 16; ++i) { hash_input[i] = uint32(input.slice(i * 4, 4)); } - hash = sha256_block(hash, hash_input); + hash = sha256_block(hash, hash_input); byte_array result(input.get_context()); for (size_t i = 0; i < 8; ++i) { @@ -168,23 +168,22 @@ template packed_byte_array sha256(const packed_byt constexpr size_t slices_per_block = 16; std::array rolling_hash; - prepare_constants(rolling_hash); + prepare_constants(rolling_hash); for (size_t i = 0; i < num_blocks; ++i) { std::array hash_input; for (size_t j = 0; j < 16; ++j) { hash_input[j] = uint32(slices[i * slices_per_block + j]); } - rolling_hash = sha256_block(rolling_hash, hash_input); + rolling_hash = sha256_block(rolling_hash, hash_input); } std::vector output(rolling_hash.begin(), rolling_hash.end()); return packed_byte_array(output, 4); } -template byte_array sha256_block(const byte_array& input); -template packed_byte_array sha256(const packed_byte_array& input); template byte_array sha256_block(const byte_array& input); +template packed_byte_array sha256(const packed_byte_array& input); template packed_byte_array sha256(const packed_byte_array& input); -template packed_byte_array sha256(const packed_byte_array& input); +template packed_byte_array sha256(const packed_byte_array& input); } // namespace stdlib } // namespace plonk diff --git a/cpp/src/aztec/stdlib/hash/sha256/sha256.hpp b/cpp/src/aztec/stdlib/hash/sha256/sha256.hpp index d01cdfc9d4..9e60609f0c 100644 --- a/cpp/src/aztec/stdlib/hash/sha256/sha256.hpp +++ b/cpp/src/aztec/stdlib/hash/sha256/sha256.hpp @@ -7,6 +7,7 @@ #include "sha256_plookup.hpp" namespace waffle { +class UltraComposer; class StandardComposer; class TurboComposer; } // namespace waffle @@ -15,8 +16,6 @@ namespace plonk { namespace stdlib { template class bit_array; -template void prepare_constants(std::array, 8>& input); - template std::array, 8> sha256_block(const std::array, 8>& h_init, const std::array, 16>& input); @@ -31,12 +30,15 @@ template field_t sha256_to_field(const packed_byte } extern template byte_array sha256_block(const byte_array& input); + extern template packed_byte_array sha256(const packed_byte_array& input); + extern template byte_array sha256_block(const byte_array& input); + extern template packed_byte_array sha256( const packed_byte_array& input); -extern template packed_byte_array sha256( - const packed_byte_array& input); + +extern template packed_byte_array sha256(const packed_byte_array& input); } // namespace stdlib } // namespace plonk diff --git a/cpp/src/aztec/stdlib/hash/sha256/sha256.test.cpp b/cpp/src/aztec/stdlib/hash/sha256/sha256.test.cpp index 80ba2837ec..45ea470ac4 100644 --- a/cpp/src/aztec/stdlib/hash/sha256/sha256.test.cpp +++ b/cpp/src/aztec/stdlib/hash/sha256/sha256.test.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include @@ -16,7 +16,7 @@ auto& engine = numeric::random::get_debug_engine(); namespace test_stdlib_sha256 { using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; constexpr uint64_t ror(uint64_t val, uint64_t shift) { @@ -137,19 +137,19 @@ TEST(stdlib_sha256, test_duplicate_proving_key) // TEST(stdlib_sha256_plookup, test_round) // { -// waffle::PlookupComposer composer = waffle::PlookupComposer(); +// waffle::UltraComposer composer = waffle::UltraComposer(); // std::array w_inputs; -// std::array, 64> w_elements; +// std::array, 64> w_elements; // for (size_t i = 0; i < 64; ++i) { // w_inputs[i] = engine.get_random_uint32(); -// w_elements[i] = plonk::stdlib::witness_t(&composer, barretenberg::fr(w_inputs[i])); +// w_elements[i] = plonk::stdlib::witness_t(&composer, barretenberg::fr(w_inputs[i])); // } // const auto expected = inner_block(w_inputs); -// const std::array, 8> result = +// const std::array, 8> result = // plonk::stdlib::sha256_inner_block(w_elements); // for (size_t i = 0; i < 8; ++i) { // EXPECT_EQ(uint256_t(result[i].get_value()).data[0] & 0xffffffffUL, @@ -167,12 +167,12 @@ TEST(stdlib_sha256, test_duplicate_proving_key) TEST(stdlib_sha256, test_plookup_55_bytes) { - typedef plonk::stdlib::field_t field_pt; - typedef plonk::stdlib::packed_byte_array packed_byte_array_pt; + typedef plonk::stdlib::field_t field_pt; + typedef plonk::stdlib::packed_byte_array packed_byte_array_pt; // 55 bytes is the largest number of bytes that can be hashed in a single block, // accounting for the single padding bit, and the 64 size bits required by the SHA-256 standard. - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); packed_byte_array_pt input(&composer, "An 8 character password? Snow White and the 7 Dwarves.."); packed_byte_array_pt output_bits = plonk::stdlib::sha256(input); @@ -189,9 +189,9 @@ TEST(stdlib_sha256, test_plookup_55_bytes) EXPECT_EQ(uint256_t(output[7].get_value()), 0x93791fc7U); printf("composer gates = %zu\n", composer.get_num_gates()); - auto prover = composer.create_prover(); + auto prover = composer.create_unrolled_prover(); - auto verifier = composer.create_verifier(); + auto verifier = composer.create_unrolled_verifier(); printf("constructing proof \n"); waffle::plonk_proof proof = prover.construct_proof(); printf("constructed proof \n"); @@ -232,10 +232,10 @@ TEST(stdlib_sha256, test_55_bytes) TEST(stdlib_sha256, test_NIST_vector_one_packed_byte_array) { - typedef plonk::stdlib::field_t field_pt; - typedef plonk::stdlib::packed_byte_array packed_byte_array_pt; + typedef plonk::stdlib::field_t field_pt; + typedef plonk::stdlib::packed_byte_array packed_byte_array_pt; - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); packed_byte_array_pt input(&composer, "abc"); packed_byte_array_pt output_bytes = plonk::stdlib::sha256(input); @@ -263,10 +263,10 @@ TEST(stdlib_sha256, test_NIST_vector_one_packed_byte_array) TEST(stdlib_sha256, test_NIST_vector_one) { - typedef plonk::stdlib::field_t field_pt; - typedef plonk::stdlib::packed_byte_array packed_byte_array_pt; + typedef plonk::stdlib::field_t field_pt; + typedef plonk::stdlib::packed_byte_array packed_byte_array_pt; - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); packed_byte_array_pt input(&composer, "abc"); @@ -390,10 +390,10 @@ TEST(stdlib_sha256, test_NIST_vector_four) HEAVY_TEST(stdlib_sha256, test_NIST_vector_five) { - typedef plonk::stdlib::field_t field_pt; - typedef plonk::stdlib::packed_byte_array packed_byte_array_pt; + typedef plonk::stdlib::field_t field_pt; + typedef plonk::stdlib::packed_byte_array packed_byte_array_pt; - waffle::PlookupComposer composer = waffle::PlookupComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); packed_byte_array_pt input( &composer, @@ -408,7 +408,7 @@ HEAVY_TEST(stdlib_sha256, test_NIST_vector_five) "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAA"); - packed_byte_array_pt output_bits = plonk::stdlib::sha256(input); + packed_byte_array_pt output_bits = plonk::stdlib::sha256(input); std::vector output = output_bits.to_unverified_byte_slices(4); diff --git a/cpp/src/aztec/stdlib/hash/sha256/sha256_plookup.cpp b/cpp/src/aztec/stdlib/hash/sha256/sha256_plookup.cpp index d92327fff0..06fc5fecca 100644 --- a/cpp/src/aztec/stdlib/hash/sha256/sha256_plookup.cpp +++ b/cpp/src/aztec/stdlib/hash/sha256/sha256_plookup.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include @@ -13,6 +13,7 @@ using namespace barretenberg; namespace plonk { namespace stdlib { namespace sha256_plookup { + namespace internal { constexpr size_t get_num_blocks(const size_t num_bits) @@ -23,7 +24,7 @@ constexpr size_t get_num_blocks(const size_t num_bits) } } // namespace internal -void prepare_constants(std::array, 8>& input) +void prepare_constants(std::array, 8>& input) { constexpr uint64_t init_constants[8]{ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 }; @@ -38,38 +39,37 @@ void prepare_constants(std::array, 8>& input) input[7] = init_constants[7]; } -sparse_witness_limbs convert_witness(const field_t& w) +sparse_witness_limbs convert_witness(const field_t& w) { - typedef field_t field_pt; + typedef field_t field_pt; sparse_witness_limbs result(w); - const auto sequence_elements = - plookup::read_sequence_from_table(waffle::PlookupMultiTableId::SHA256_WITNESS_INPUT, w); + const auto lookup = plookup_read::get_lookup_accumulators(MultiTableId::SHA256_WITNESS_INPUT, w); result.sparse_limbs = std::array{ - sequence_elements[1][0], - sequence_elements[1][1], - sequence_elements[1][2], - sequence_elements[1][3], + lookup[ColumnIdx::C2][0], + lookup[ColumnIdx::C2][1], + lookup[ColumnIdx::C2][2], + lookup[ColumnIdx::C2][3], }; result.rotated_limbs = std::array{ - sequence_elements[2][0], - sequence_elements[2][1], - sequence_elements[2][2], - sequence_elements[2][3], + lookup[ColumnIdx::C3][0], + lookup[ColumnIdx::C3][1], + lookup[ColumnIdx::C3][2], + lookup[ColumnIdx::C3][3], }; result.has_sparse_limbs = true; return result; } -std::array, 64> extend_witness( - const std::array, 16>& w_in) +std::array, 64> extend_witness( + const std::array, 16>& w_in) { - typedef field_t field_pt; + typedef field_t field_pt; - waffle::PlookupComposer* ctx = w_in[0].get_context(); + waffle::UltraComposer* ctx = w_in[0].get_context(); std::array w_sparse; for (size_t i = 0; i < 16; ++i) { @@ -128,7 +128,7 @@ std::array, 64> extend_witness( .add_two(w_right.rotated_limbs[3], left_xor_sparse) .normalize(); - field_pt xor_result = plookup::read_from_1_to_2_table(waffle::SHA256_WITNESS_OUTPUT, xor_result_sparse); + field_pt xor_result = plookup_read::read_from_1_to_2_table(SHA256_WITNESS_OUTPUT, xor_result_sparse); // TODO NORMALIZE WITH RANGE CHECK @@ -138,7 +138,7 @@ std::array, 64> extend_witness( w_out = field_pt(ctx, fr(w_out_raw.get_value().from_montgomery_form().data[0] & (uint64_t)0xffffffffULL)); } else { - w_out = witness_t( + w_out = witness_t( ctx, fr(w_out_raw.get_value().from_montgomery_form().data[0] & (uint64_t)0xffffffffULL)); } w_sparse[i] = sparse_witness_limbs(w_out); @@ -152,60 +152,61 @@ std::array, 64> extend_witness( return w_extended; } -sparse_value map_into_choose_sparse_form(const field_t& e) +sparse_value map_into_choose_sparse_form(const field_t& e) { sparse_value result; result.normal = e; - result.sparse = plookup::read_from_1_to_2_table(waffle::SHA256_CH_INPUT, e); + result.sparse = plookup_read::read_from_1_to_2_table(SHA256_CH_INPUT, e); return result; } -sparse_value map_into_maj_sparse_form(const field_t& e) +sparse_value map_into_maj_sparse_form(const field_t& e) { sparse_value result; result.normal = e; - result.sparse = plookup::read_from_1_to_2_table(waffle::SHA256_MAJ_INPUT, e); + result.sparse = plookup_read::read_from_1_to_2_table(SHA256_MAJ_INPUT, e); return result; } -field_t choose(sparse_value& e, const sparse_value& f, const sparse_value& g) +field_t choose(sparse_value& e, const sparse_value& f, const sparse_value& g) { - typedef field_t field_pt; + typedef field_t field_pt; - const auto lookups = plookup::read_sequence_from_table(waffle::SHA256_CH_INPUT, e.normal); - const auto rotation_coefficients = waffle::sha256_tables::get_choose_rotation_multipliers(); + const auto lookup = plookup_read::get_lookup_accumulators(SHA256_CH_INPUT, e.normal); + const auto rotation_coefficients = sha256_tables::get_choose_rotation_multipliers(); - field_pt rotation_result = lookups[2][0]; + field_pt rotation_result = lookup[ColumnIdx::C3][0]; - e.sparse = lookups[1][0]; + e.sparse = lookup[ColumnIdx::C2][0]; - field_pt sparse_limb_3 = lookups[1][2]; + field_pt sparse_limb_3 = lookup[ColumnIdx::C2][2]; + // where is the middle limb used field_pt xor_result = (rotation_result * fr(7)) .add_two(e.sparse * (rotation_coefficients[0] * fr(7) + fr(1)), sparse_limb_3 * (rotation_coefficients[2] * fr(7))); field_pt choose_result_sparse = xor_result.add_two(f.sparse + f.sparse, g.sparse + g.sparse + g.sparse).normalize(); - field_pt choose_result = plookup::read_from_1_to_2_table(waffle::SHA256_CH_OUTPUT, choose_result_sparse); + field_pt choose_result = plookup_read::read_from_1_to_2_table(SHA256_CH_OUTPUT, choose_result_sparse); return choose_result; } -field_t majority(sparse_value& a, const sparse_value& b, const sparse_value& c) +field_t majority(sparse_value& a, const sparse_value& b, const sparse_value& c) { - typedef field_t field_pt; - - const auto lookups = plookup::read_sequence_from_table(waffle::SHA256_MAJ_INPUT, a.normal); - const auto rotation_coefficients = waffle::sha256_tables::get_majority_rotation_multipliers(); - - field_pt rotation_result = lookups[2][0]; + typedef field_t field_pt; - a.sparse = lookups[1][0]; + const auto lookup = plookup_read::get_lookup_accumulators(SHA256_MAJ_INPUT, a.normal); + const auto rotation_coefficients = sha256_tables::get_majority_rotation_multipliers(); - field_pt sparse_accumulator_2 = lookups[1][1]; + field_pt rotation_result = + lookup[ColumnIdx::C3][0]; // last index of first row gives accumulating sum of "non-trival" wraps + a.sparse = lookup[ColumnIdx::C2][0]; + // use these values to compute trivial wraps somehow + field_pt sparse_accumulator_2 = lookup[ColumnIdx::C2][1]; field_pt xor_result = (rotation_result * fr(4)) .add_two(a.sparse * (rotation_coefficients[0] * fr(4) + fr(1)), @@ -213,24 +214,18 @@ field_t majority(sparse_value& a, const sparse_value& b field_pt majority_result_sparse = xor_result.add_two(b.sparse, c.sparse).normalize(); - field_pt majority_result = plookup::read_from_1_to_2_table(waffle::SHA256_MAJ_OUTPUT, majority_result_sparse); + field_pt majority_result = plookup_read::read_from_1_to_2_table(SHA256_MAJ_OUTPUT, majority_result_sparse); return majority_result; } -field_t add_normalize(const field_t& a, - const field_t& b) +field_t add_normalize(const field_t& a, + const field_t& b) { - typedef field_t field_pt; - typedef witness_t witness_pt; + typedef field_t field_pt; + typedef witness_t witness_pt; - waffle::PlookupComposer* ctx = a.get_context() ? a.get_context() : b.get_context(); - - if (a.witness_index == IS_CONSTANT && b.witness_index == IS_CONSTANT) { - auto sum = uint256_t(a.get_value() + b.get_value()); - uint64_t normalized = static_cast(sum.data[0]); - return field_pt(ctx, normalized); - } + waffle::UltraComposer* ctx = a.get_context() ? a.get_context() : b.get_context(); uint256_t sum = a.get_value() + b.get_value(); @@ -244,15 +239,14 @@ field_t add_normalize(const field_t, 8> sha256_block( - const std::array, 8>& h_init, - const std::array, 16>& input) +std::array, 8> sha256_block(const std::array, 8>& h_init, + const std::array, 16>& input) { - typedef field_t field_pt; + typedef field_t field_pt; constexpr uint64_t round_constants[64]{ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, @@ -285,6 +279,7 @@ std::array, 8> sha256_block( /** * Apply SHA-256 compression function to the message schedule **/ + // As opposed to standard sha description - Maj and Choose functions also include required rotations for round for (size_t i = 0; i < 64; ++i) { auto ch = choose(e, f, g); auto maj = majority(a, b, c); @@ -312,14 +307,24 @@ std::array, 8> sha256_block( output[5] = add_normalize(f.normal, h_init[5]); output[6] = add_normalize(g.normal, h_init[6]); output[7] = add_normalize(h.normal, h_init[7]); + + /** + * At this point, a malicilous prover could tweak the add_normalise function and the result could be 'overflowed'. + * Thus, we need 32-bit range checks on the outputs. Note that we won't need range checks while applying the SHA-256 + * compression function because the outputs of the lookup table ensures that the output is contrained to 32 bits. + */ + for (size_t i = 0; i < 8; i++) { + output[i].create_range_constraint(32); + } + return output; } -packed_byte_array sha256(const packed_byte_array& input) +packed_byte_array sha256(const packed_byte_array& input) { - typedef field_t field_pt; + typedef field_t field_pt; - waffle::PlookupComposer* ctx = input.get_context(); + waffle::UltraComposer* ctx = input.get_context(); auto message_schedule(input); @@ -352,7 +357,7 @@ packed_byte_array sha256(const packed_byte_array output(rolling_hash.begin(), rolling_hash.end()); - return packed_byte_array(output, 4); + return packed_byte_array(output, 4); } } // namespace sha256_plookup diff --git a/cpp/src/aztec/stdlib/hash/sha256/sha256_plookup.hpp b/cpp/src/aztec/stdlib/hash/sha256/sha256_plookup.hpp index 3571c9cd48..e29925dd86 100644 --- a/cpp/src/aztec/stdlib/hash/sha256/sha256_plookup.hpp +++ b/cpp/src/aztec/stdlib/hash/sha256/sha256_plookup.hpp @@ -10,7 +10,7 @@ #include "../../primitives/packed_byte_array/packed_byte_array.hpp" namespace waffle { -class PlookupComposer; +class UltraComposer; } // namespace waffle namespace plonk { @@ -18,23 +18,23 @@ namespace stdlib { namespace sha256_plookup { struct sparse_ch_value { - field_t normal; - field_t sparse; - field_t rot6; - field_t rot11; - field_t rot25; + field_t normal; + field_t sparse; + field_t rot6; + field_t rot11; + field_t rot25; }; struct sparse_maj_value { - field_t normal; - field_t sparse; - field_t rot2; - field_t rot13; - field_t rot22; + field_t normal; + field_t sparse; + field_t rot2; + field_t rot13; + field_t rot22; }; struct sparse_witness_limbs { - sparse_witness_limbs(const field_t& in = 0) + sparse_witness_limbs(const field_t& in = 0) { normal = in; has_sparse_limbs = false; @@ -45,21 +45,21 @@ struct sparse_witness_limbs { sparse_witness_limbs& operator=(const sparse_witness_limbs& other) = default; sparse_witness_limbs& operator=(sparse_witness_limbs&& other) = default; - field_t normal; + field_t normal; - std::array, 4> sparse_limbs; + std::array, 4> sparse_limbs; - std::array, 4> rotated_limbs; + std::array, 4> rotated_limbs; bool has_sparse_limbs = false; }; struct sparse_value { - sparse_value(const field_t& in = 0) + sparse_value(const field_t& in = 0) { normal = in; if (normal.witness_index == IS_CONSTANT) { - sparse = field_t( + sparse = field_t( in.get_context(), barretenberg::fr(numeric::map_into_sparse_form<16>(uint256_t(in.get_value()).data[0]))); } @@ -71,23 +71,22 @@ struct sparse_value { sparse_value& operator=(const sparse_value& other) = default; sparse_value& operator=(sparse_value&& other) = default; - field_t normal; - field_t sparse; + field_t normal; + field_t sparse; }; -sparse_witness_limbs convert_witness(const field_t& w); +sparse_witness_limbs convert_witness(const field_t& w); -std::array, 64> extend_witness( - const std::array, 16>& w_in); +std::array, 64> extend_witness( + const std::array, 16>& w_in); -field_t choose(sparse_value& e, const sparse_value& f, const sparse_value& g); -field_t majority(sparse_value& a, const sparse_value& b, const sparse_value& c); +field_t choose(sparse_value& e, const sparse_value& f, const sparse_value& g); +field_t majority(sparse_value& a, const sparse_value& b, const sparse_value& c); -std::array, 8> sha256_block( - const std::array, 8>& h_init, - const std::array, 16>& input); +std::array, 8> sha256_block(const std::array, 8>& h_init, + const std::array, 16>& input); -packed_byte_array sha256(const packed_byte_array& input); +packed_byte_array sha256(const packed_byte_array& input); } // namespace sha256_plookup } // namespace stdlib } // namespace plonk diff --git a/cpp/src/aztec/stdlib/merkle_tree/CMakeLists.txt b/cpp/src/aztec/stdlib/merkle_tree/CMakeLists.txt index 068c32e736..8a0981448c 100644 --- a/cpp/src/aztec/stdlib/merkle_tree/CMakeLists.txt +++ b/cpp/src/aztec/stdlib/merkle_tree/CMakeLists.txt @@ -27,4 +27,4 @@ if(NOT WASM) link_libraries(leveldb) endif() -barretenberg_module(stdlib_merkle_tree stdlib_primitives stdlib_blake2s stdlib_pedersen) \ No newline at end of file +barretenberg_module(stdlib_merkle_tree stdlib_primitives stdlib_blake3s stdlib_pedersen) \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/merkle_tree/hash.test.cpp b/cpp/src/aztec/stdlib/merkle_tree/hash.test.cpp index 1a2041f020..0b2cb852ef 100644 --- a/cpp/src/aztec/stdlib/merkle_tree/hash.test.cpp +++ b/cpp/src/aztec/stdlib/merkle_tree/hash.test.cpp @@ -1,10 +1,10 @@ #include "hash.hpp" #include #include -#include +#include using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; TEST(stdlib_merkle_tree_hash, compress_native_vs_circuit) { diff --git a/cpp/src/aztec/stdlib/merkle_tree/membership.test.cpp b/cpp/src/aztec/stdlib/merkle_tree/membership.test.cpp index d09c65bdbc..89f3ad2118 100644 --- a/cpp/src/aztec/stdlib/merkle_tree/membership.test.cpp +++ b/cpp/src/aztec/stdlib/merkle_tree/membership.test.cpp @@ -4,10 +4,10 @@ #include "memory_store.hpp" #include "memory_tree.hpp" #include -#include +#include using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; using namespace plonk::stdlib::merkle_tree; TEST(stdlib_merkle_tree, test_check_membership) diff --git a/cpp/src/aztec/stdlib/merkle_tree/memory_tree.test.cpp b/cpp/src/aztec/stdlib/merkle_tree/memory_tree.test.cpp index 371fe5bd4d..5e82de2a05 100644 --- a/cpp/src/aztec/stdlib/merkle_tree/memory_tree.test.cpp +++ b/cpp/src/aztec/stdlib/merkle_tree/memory_tree.test.cpp @@ -1,6 +1,6 @@ #include "memory_tree.hpp" #include -#include +#include using namespace barretenberg; using namespace plonk::stdlib::merkle_tree; diff --git a/cpp/src/aztec/stdlib/merkle_tree/merkle_tree.test.cpp b/cpp/src/aztec/stdlib/merkle_tree/merkle_tree.test.cpp index 06d92ff137..8f2efa0f0b 100644 --- a/cpp/src/aztec/stdlib/merkle_tree/merkle_tree.test.cpp +++ b/cpp/src/aztec/stdlib/merkle_tree/merkle_tree.test.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include using namespace barretenberg; using namespace plonk::stdlib::merkle_tree; diff --git a/cpp/src/aztec/stdlib/primitives/bigfield/bigfield.hpp b/cpp/src/aztec/stdlib/primitives/bigfield/bigfield.hpp index a3974cb47c..69fafc867f 100644 --- a/cpp/src/aztec/stdlib/primitives/bigfield/bigfield.hpp +++ b/cpp/src/aztec/stdlib/primitives/bigfield/bigfield.hpp @@ -25,15 +25,6 @@ template class bigfield { size_t num_bits; }; - // used in dual_multiply_add to store the result of one of the multiplications, - // as we will likely re-use it - struct cached_product { - field_t lo_cache = field_t(0); - field_t hi_cache = field_t(0); - field_t prime_cache = field_t(0); - bool cache_exists = false; - }; - struct Limb { Limb() {} Limb(const field_t& input, const uint256_t max = uint256_t(0)) @@ -74,18 +65,36 @@ template class bigfield { const field_t& c, const field_t& d, const bool can_overflow = false) - : bigfield((a + b * shift_1), (c + d * shift_1), can_overflow) { - const auto limb_range_checks = [](const field_t& limb, const bool overflow) { - if (limb.is_constant()) { - limb.create_range_constraint(overflow ? NUM_LIMB_BITS : NUM_LAST_LIMB_BITS); - } - }; - limb_range_checks(a, true); - limb_range_checks(b, true); - limb_range_checks(c, true); - limb_range_checks(d, can_overflow); - } + 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] = + 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); + }; + + // 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) + { + 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] = + Limb(field_t(d), can_overflow ? DEFAULT_MAXIMUM_LIMB : DEFAULT_MAXIMUM_MOST_SIGNIFICANT_LIMB); + prime_basis_limb = prime_limb; + }; + bigfield(const byte_array& bytes); bigfield(const bigfield& other); bigfield(bigfield&& other); @@ -106,7 +115,7 @@ template class bigfield { // code assumes modulus is at most 256 bits so good to define it via a uint256_t static constexpr uint256_t modulus = (uint256_t(T::modulus_0, T::modulus_1, T::modulus_2, T::modulus_3)); static constexpr uint512_t modulus_u512 = uint512_t(modulus); - static constexpr uint64_t NUM_LIMB_BITS = waffle::NUM_LIMB_BITS_IN_FIELD_SIMULATION; + static constexpr uint64_t NUM_LIMB_BITS = 68; static constexpr uint64_t NUM_LAST_LIMB_BITS = modulus_u512.get_msb() + 1 - (NUM_LIMB_BITS * 3); static constexpr uint1024_t DEFAULT_MAXIMUM_REMAINDER = (uint1024_t(1) << (NUM_LIMB_BITS * 3 + NUM_LAST_LIMB_BITS)) - uint1024_t(1); @@ -318,7 +327,7 @@ template class bigfield { } /** - * @brief Compute the maximum number of bits for quotient range proof to protect against CRT undeflow + * @brief Compute the maximum number of bits for quotient range proof to protect against CRT underflow * * @param remainders_max Maximum sizes of resulting remainders * @return Desired length of range proof @@ -437,7 +446,7 @@ template class bigfield { const std::vector& to_add); /** - * @brief Check for 2 conditions (CRT modulus is overflown of the maximum quotient doesn't fit into range proof). + * @brief Check for 2 conditions (CRT modulus is overflown or the maximum quotient doesn't fit into range proof). * Also returns the length of quotient's range proof if there is no need to reduce. * * @param as_max Vector of left multiplicands' maximum values diff --git a/cpp/src/aztec/stdlib/primitives/bigfield/bigfield.test.cpp b/cpp/src/aztec/stdlib/primitives/bigfield/bigfield.test.cpp index 251fc45de0..9325a51e77 100644 --- a/cpp/src/aztec/stdlib/primitives/bigfield/bigfield.test.cpp +++ b/cpp/src/aztec/stdlib/primitives/bigfield/bigfield.test.cpp @@ -15,12 +15,14 @@ #include #include +#include #include #define GET_COMPOSER_NAME_STRING(composer) \ - (typeid(composer) == typeid(waffle::StandardComposer) \ - ? "StandardPlonk" \ - : typeid(composer) == typeid(waffle::TurboComposer) ? "TurboPlonk" : "NULLPlonk") + (typeid(composer) == typeid(waffle::StandardComposer) ? "StandardPlonk" \ + : typeid(composer) == typeid(waffle::TurboComposer) ? "TurboPlonk" \ + : "NULLPlonk") + namespace test_stdlib_bigfield { using namespace barretenberg; using namespace plonk; @@ -66,29 +68,38 @@ template class stdlib_bigfield : public testing::Test { static void test_bad_mul() { - auto composer = Composer(); - size_t num_repetitions = 1; - for (size_t i = 0; i < num_repetitions; ++i) { - fq inputs[2]{ fq::zero(), fq::random_element() }; - fq_ct a(witness_ct(&composer, barretenberg::fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&composer, - barretenberg::fr( - uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct b(witness_ct(&composer, barretenberg::fr(uint256_t(inputs[1]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(&composer, - barretenberg::fr( - uint256_t(inputs[1]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - - a.bad_mul(b); - } + uint256_t value(2); + fq_ct tval = fq_ct::create_from_u512_as_witness(&composer, value); + fq_ct tval1 = tval - tval; + fq_ct tval2 = tval1 / tval; + (void)tval2; auto prover = composer.create_prover(); auto verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, false); + EXPECT_EQ(proof_result, true); + } + + static std::pair get_random_element(Composer* ctx) + { + fq elt = fq::random_element(); + return std::make_pair(elt, fq_ct::from_witness(ctx, elt)); } + static std::pair, std::vector> get_random_elements(Composer* ctx, size_t num) + { + std::vector elts(num); + std::vector big_elts(num); + for (size_t i = 0; i < num; ++i) { + auto [elt, big_elt] = get_random_element(ctx); + elts[i] = elt; + big_elts[i] = big_elt; + } + return std::make_pair(elts, big_elts); + } + + public: static void test_mul() { auto composer = Composer(); @@ -105,7 +116,7 @@ template class stdlib_bigfield : public testing::Test { fq_ct c = a * b; uint64_t after = composer.get_num_gates(); if (i == num_repetitions - 1) { - std::cout << "num gates per mul = " << after - before << std::endl; + std::cerr << "num gates per mul = " << after - before << std::endl; benchmark_info(GET_COMPOSER_NAME_STRING(Composer), "Bigfield", "MUL", "Gate Count", after - before); } // uint256_t modulus{ Bn254FqParams::modulus_0, @@ -146,7 +157,7 @@ template class stdlib_bigfield : public testing::Test { fq_ct c = a.sqr(); uint64_t after = composer.get_num_gates(); if (i == num_repetitions - 1) { - std::cout << "num gates per mul = " << after - before << std::endl; + std::cerr << "num gates per mul = " << after - before << std::endl; benchmark_info(GET_COMPOSER_NAME_STRING(Composer), "Bigfield", "SQR", "Gate Count", after - before); } // uint256_t modulus{ Bn254FqParams::modulus_0, @@ -195,7 +206,7 @@ template class stdlib_bigfield : public testing::Test { fq_ct d = a.madd(b, { c }); uint64_t after = composer.get_num_gates(); if (i == num_repetitions - 1) { - std::cout << "num gates per mul = " << after - before << std::endl; + std::cerr << "num gates per mul = " << after - before << std::endl; benchmark_info(GET_COMPOSER_NAME_STRING(Composer), "Bigfield", "MADD", "Gate Count", after - before); } // uint256_t modulus{ Bn254FqParams::modulus_0, @@ -227,7 +238,7 @@ template class stdlib_bigfield : public testing::Test { { auto composer = Composer(); size_t num_repetitions = 1; - const size_t number_of_madds = 128; + const size_t number_of_madds = 32; for (size_t i = 0; i < num_repetitions; ++i) { fq mul_left_values[number_of_madds]; fq mul_right_values[number_of_madds]; @@ -258,7 +269,7 @@ template class stdlib_bigfield : public testing::Test { fq_ct f = fq_ct::mult_madd(mul_left, mul_right, to_add); uint64_t after = composer.get_num_gates(); if (i == num_repetitions - 1) { - std::cout << "num gates with mult_madd = " << after - before << std::endl; + std::cerr << "num gates with mult_madd = " << after - before << std::endl; benchmark_info( GET_COMPOSER_NAME_STRING(Composer), "Bigfield", "MULT_MADD", "Gate Count", after - before); } @@ -270,7 +281,7 @@ template class stdlib_bigfield : public testing::Test { } after = composer.get_num_gates(); if (i == num_repetitions - 1) { - std::cout << "num gates with regular multiply_add = " << after - before << std::endl; + std::cerr << "num gates with regular multiply_add = " << after - before << std::endl; } **/ @@ -286,7 +297,9 @@ template class stdlib_bigfield : public testing::Test { EXPECT_EQ(result.hi.data[2], 0ULL); EXPECT_EQ(result.hi.data[3], 0ULL); } - std::cout << "composer failed + err = " << composer.failed << " , " << composer.err << std::endl; + if (composer.failed()) { + info("Composer failed with error: ", composer.err()); + }; auto prover = composer.create_prover(); auto verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); @@ -322,7 +335,7 @@ template class stdlib_bigfield : public testing::Test { fq_ct f = fq_ct::dual_madd(a, b, c, d, { e }); uint64_t after = composer.get_num_gates(); if (i == num_repetitions - 1) { - std::cout << "num gates per mul = " << after - before << std::endl; + std::cerr << "num gates per mul = " << after - before << std::endl; } // uint256_t modulus{ Bn254FqParams::modulus_0, // Bn254FqParams::modulus_1, @@ -342,7 +355,9 @@ template class stdlib_bigfield : public testing::Test { EXPECT_EQ(result.hi.data[2], 0ULL); EXPECT_EQ(result.hi.data[3], 0ULL); } - std::cout << "composer failed + err = " << composer.failed << " , " << composer.err << std::endl; + if (composer.failed()) { + info("Composer failed with error: ", composer.err()); + }; auto prover = composer.create_prover(); auto verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); @@ -559,6 +574,33 @@ template class stdlib_bigfield : public testing::Test { EXPECT_EQ(proof_result, true); } + static void test_msub_div() + { + size_t num_repetitions = 8; + for (size_t i = 0; i < num_repetitions; ++i) { + auto composer = Composer(); + auto [mul_l, mul_l_ct] = get_random_element(&composer); + auto [mul_r1, mul_r1_ct] = get_random_element(&composer); + auto [mul_r2, mul_r2_ct] = get_random_element(&composer); + auto [divisor1, divisor1_ct] = get_random_element(&composer); + auto [divisor2, divisor2_ct] = get_random_element(&composer); + auto [to_sub1, to_sub1_ct] = get_random_element(&composer); + auto [to_sub2, to_sub2_ct] = get_random_element(&composer); + + fq_ct result_ct = fq_ct::msub_div( + { mul_l_ct }, { mul_r1_ct - mul_r2_ct }, divisor1_ct - divisor2_ct, { to_sub1_ct, to_sub2_ct }); + fq expected = (-(mul_l * (mul_r1 - mul_r2) + to_sub1 + to_sub2)) / (divisor1 - divisor2); + EXPECT_EQ(result_ct.get_value().lo, uint256_t(expected)); + EXPECT_EQ(result_ct.get_value().hi, uint256_t(0)); + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } + } + static void test_conditional_negate() { auto composer = Composer(); @@ -630,7 +672,7 @@ template class stdlib_bigfield : public testing::Test { fq_ct x3 = lambda.sqr() - (x2 + x1); fq_ct y3 = (x1 - x3) * lambda - y1; uint64_t after = composer.get_num_gates(); - std::cout << "added gates = " << after - before << std::endl; + std::cerr << "added gates = " << after - before << std::endl; g1::affine_element P3(g1::element(P1) + g1::element(P2)); fq expected_x = P3.x; fq expected_y = P3.y; @@ -820,20 +862,13 @@ template class stdlib_bigfield : public testing::Test { }; // Define types for which the above tests will be constructed. -typedef testing::Types - ComposerTypes; +typedef testing::Types ComposerTypes; + // Define the suite of tests. TYPED_TEST_SUITE(stdlib_bigfield, ComposerTypes); -TYPED_TEST(stdlib_bigfield, division_formula_bug) -{ - TestFixture::test_division_formula_bug(); -} TYPED_TEST(stdlib_bigfield, badmul) { - TestFixture::test_bad_mul(); + TestFixture::test_division_formula_bug(); } TYPED_TEST(stdlib_bigfield, mul) { @@ -871,6 +906,10 @@ TYPED_TEST(stdlib_bigfield, sub_and_mul) { TestFixture::test_sub_and_mul(); } +TYPED_TEST(stdlib_bigfield, msub_div) +{ + TestFixture::test_msub_div(); +} TYPED_TEST(stdlib_bigfield, conditional_negate) { TestFixture::test_conditional_negate(); @@ -928,8 +967,8 @@ TYPED_TEST(stdlib_bigfield, division_context) // // barretenberg::Bn254FqParams::modulus_3 }; // fq expected = (inputs[0] / (inputs[1] - inputs[2])); -// std::cout << "denominator = " << inputs[1] - inputs[2] << std::endl; -// std::cout << "expected = " << expected << std::endl; +// std::cerr << "denominator = " << inputs[1] - inputs[2] << std::endl; +// std::cerr << "expected = " << expected << std::endl; // expected = expected.from_montgomery_form(); // uint512_t result = c.get_value(); @@ -952,7 +991,7 @@ TYPED_TEST(stdlib_bigfield, division_context) // // PLOOKUP TESTS // TEST(stdlib_bigfield_plookup, test_mul) // { -// waffle::PlookupComposer composer = waffle::PlookupComposer(); +// waffle::UltraComposer composer = waffle::UltraComposer(); // size_t num_repetitions = 1; // for (size_t i = 0; i < num_repetitions; ++i) { // fq inputs[3]{ fq::random_element(), fq::random_element(), fq::random_element() }; @@ -968,12 +1007,12 @@ TYPED_TEST(stdlib_bigfield, division_context) // barretenberg::fr( // uint256_t(inputs[1]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * // 4)))); -// std::cout << "starting mul" << std::endl; +// std::cerr << "starting mul" << std::endl; // uint64_t before = composer.get_num_gates(); // fq_ct c = a * b; // uint64_t after = composer.get_num_gates(); // if (i == num_repetitions - 1) { -// std::cout << "num gates per mul = " << after - before << std::endl; +// std::cerr << "num gates per mul = " << after - before << std::endl; // } // fq expected = (inputs[0] * inputs[1]); @@ -999,7 +1038,7 @@ TYPED_TEST(stdlib_bigfield, division_context) // TEST(stdlib_bigfield_plookup, test_sqr) // { -// waffle::PlookupComposer composer = waffle::PlookupComposer(); +// waffle::UltraComposer composer = waffle::UltraComposer(); // size_t num_repetitions = 10; // for (size_t i = 0; i < num_repetitions; ++i) { // fq inputs[3]{ fq::random_element(), fq::random_element(), fq::random_element() }; @@ -1013,7 +1052,7 @@ TYPED_TEST(stdlib_bigfield, division_context) // fq_ct c = a.sqr(); // uint64_t after = composer.get_num_gates(); // if (i == num_repetitions - 1) { -// std::cout << "num gates per sqr = " << after - before << std::endl; +// std::cerr << "num gates per sqr = " << after - before << std::endl; // } // fq expected = (inputs[0].sqr()); diff --git a/cpp/src/aztec/stdlib/primitives/bigfield/bigfield_impl.hpp b/cpp/src/aztec/stdlib/primitives/bigfield/bigfield_impl.hpp index e488445e36..d32dbdd0df 100644 --- a/cpp/src/aztec/stdlib/primitives/bigfield/bigfield_impl.hpp +++ b/cpp/src/aztec/stdlib/primitives/bigfield/bigfield_impl.hpp @@ -42,39 +42,45 @@ bigfield::bigfield(const field_t& low_bits_in, const bool can_overflow, const size_t maximum_bitlength) { - const auto low_bits = low_bits_in.normalize(); - const auto high_bits = high_bits_in.normalize(); - context = low_bits.context == nullptr ? high_bits.context : low_bits.context; + context = low_bits_in.context == nullptr ? high_bits_in.context : low_bits_in.context; field_t limb_0(context); field_t limb_1(context); field_t limb_2(context); field_t limb_3(context); - if (low_bits.witness_index != IS_CONSTANT) { + if (low_bits_in.witness_index != IS_CONSTANT) { std::vector low_accumulator; if constexpr (C::type == waffle::PLOOKUP) { - // Enforce that low_bits indeed only contains 2*NUM_LIMB_BITS bits - low_accumulator = - context->decompose_into_default_range(low_bits.witness_index, static_cast(NUM_LIMB_BITS * 2)); - // If this doesn't hold we're using a default plookup range size that doesn't work well with the limb size - // here - ASSERT(low_accumulator.size() % 2 == 0); - size_t mid_index = low_accumulator.size() / 2 - 1; - limb_0.witness_index = low_accumulator[mid_index]; // Q:safer to just slice this from low_bits? - limb_1 = (low_bits - limb_0) * shift_right_1; + // 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); + 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)); + + // // Enforce that low_bits_in indeed only contains 2*NUM_LIMB_BITS bits + // low_accumulator = context->decompose_into_default_range(low_bits_in.witness_index, + // static_cast(NUM_LIMB_BITS * 2)); + // // If this doesn't hold we're using a default plookup range size that doesn't work well with the limb + // size + // // here + // ASSERT(low_accumulator.size() % 2 == 0); + // size_t mid_index = low_accumulator.size() / 2 - 1; + // limb_0.witness_index = low_accumulator[mid_index]; // Q:safer to just slice this from 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.witness_index, - static_cast(NUM_LIMB_BITS * 2)); + low_accumulator = context->decompose_into_base4_accumulators( + low_bits_in.witness_index, static_cast(NUM_LIMB_BITS * 2), "bigfield: low_bits_in too large."); mid_index = static_cast((NUM_LIMB_BITS / 2) - 1); // Turbo plonk range constraint returns an array of partial sums, midpoint will happen to hold the big limb // value limb_1.witness_index = low_accumulator[mid_index]; - // We can get the first half bits of low_bits from the variables we already created - limb_0 = (low_bits - (limb_1 * shift_1)); + // We can get the first half bits of low_bits_in from the variables we already created + limb_0 = (low_bits_in - (limb_1 * shift_1)); } } else { - uint256_t slice_0 = uint256_t(low_bits.additive_constant).slice(0, NUM_LIMB_BITS); - uint256_t slice_1 = uint256_t(low_bits.additive_constant).slice(NUM_LIMB_BITS, 2 * NUM_LIMB_BITS); + uint256_t slice_0 = uint256_t(low_bits_in.additive_constant).slice(0, NUM_LIMB_BITS); + uint256_t slice_1 = uint256_t(low_bits_in.additive_constant).slice(NUM_LIMB_BITS, 2 * NUM_LIMB_BITS); limb_0 = field_t(context, barretenberg::fr(slice_0)); limb_1 = field_t(context, barretenberg::fr(slice_1)); } @@ -91,25 +97,26 @@ 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.witness_index != IS_CONSTANT) { + if (high_bits_in.witness_index != IS_CONSTANT) { std::vector high_accumulator; if constexpr (C::type == waffle::PLOOKUP) { - ASSERT(NUM_LIMB_BITS % 2 == 0); // required for one of the intermediate sums giving the third limb - high_accumulator = context->decompose_into_default_range(high_bits.witness_index, - static_cast(num_high_limb_bits)); - size_t mid_index = (NUM_LIMB_BITS / waffle::PlookupComposer::DEFAULT_PLOOKUP_RANGE_BITNUM) / 2 - 1; - limb_2.witness_index = high_accumulator[mid_index]; - limb_3 = (high_bits - limb_2) * shift_right_1; + const auto limb_witnesses = + context->decompose_non_native_field_double_width_limb(high_bits_in.normalize().witness_index); + 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.witness_index, - static_cast(num_high_limb_bits)); + high_accumulator = context->decompose_into_base4_accumulators(high_bits_in.witness_index, + static_cast(num_high_limb_bits), + "bigfield: high_bits_in too large."); limb_3.witness_index = high_accumulator[static_cast((num_last_limb_bits / 2) - 1)]; - limb_2 = (high_bits - (limb_3 * shift_1)); + limb_2 = (high_bits_in - (limb_3 * shift_1)); } } else { - uint256_t slice_2 = uint256_t(high_bits.additive_constant).slice(0, NUM_LIMB_BITS); - uint256_t slice_3 = uint256_t(high_bits.additive_constant).slice(NUM_LIMB_BITS, num_high_limb_bits); + uint256_t slice_2 = uint256_t(high_bits_in.additive_constant).slice(0, NUM_LIMB_BITS); + uint256_t slice_3 = uint256_t(high_bits_in.additive_constant).slice(NUM_LIMB_BITS, num_high_limb_bits); limb_2 = field_t(context, barretenberg::fr(slice_2)); limb_3 = field_t(context, barretenberg::fr(slice_3)); } @@ -117,7 +124,7 @@ bigfield::bigfield(const field_t& low_bits_in, binary_basis_limbs[1] = Limb(limb_1, DEFAULT_MAXIMUM_LIMB); binary_basis_limbs[2] = Limb(limb_2, DEFAULT_MAXIMUM_LIMB); binary_basis_limbs[3] = Limb(limb_3, can_overflow ? DEFAULT_MAXIMUM_LIMB : DEFAULT_MAXIMUM_MOST_SIGNIFICANT_LIMB); - prime_basis_limb = low_bits + (high_bits * shift_2); + prime_basis_limb = low_bits_in + (high_bits_in * shift_2); } template @@ -149,9 +156,45 @@ bigfield bigfield::create_from_u512_as_witness(C* ctx, const uint512 limbs[2] = value.slice(NUM_LIMB_BITS * 2, NUM_LIMB_BITS * 3).lo; limbs[3] = value.slice(NUM_LIMB_BITS * 3, NUM_LIMB_BITS * 4).lo; - return bigfield(witness_t(ctx, fr(limbs[0] + limbs[1] * shift_1)), - witness_t(ctx, fr(limbs[2] + limbs[3] * shift_1)), - can_overflow); + if constexpr (C::type == waffle::PLOOKUP) { + field_t limb_0(ctx); + field_t limb_1(ctx); + field_t limb_2(ctx); + field_t limb_3(ctx); + field_t prime_limb(ctx); + limb_0.witness_index = ctx->add_variable(barretenberg::fr(limbs[0])); + limb_1.witness_index = ctx->add_variable(barretenberg::fr(limbs[1])); + limb_2.witness_index = ctx->add_variable(barretenberg::fr(limbs[2])); + limb_3.witness_index = ctx->add_variable(barretenberg::fr(limbs[3])); + 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, + shift_1, + shift_2, + shift_3, + -1, + 0 }, + true); + ctx->range_constrain_two_limbs(limb_0.witness_index, limb_1.witness_index); + ctx->range_constrain_two_limbs(limb_2.witness_index, limb_3.witness_index); + + bigfield result(ctx); + result.binary_basis_limbs[0] = Limb(limb_0, DEFAULT_MAXIMUM_LIMB); + result.binary_basis_limbs[1] = Limb(limb_1, DEFAULT_MAXIMUM_LIMB); + result.binary_basis_limbs[2] = Limb(limb_2, DEFAULT_MAXIMUM_LIMB); + result.binary_basis_limbs[3] = + Limb(limb_3, can_overflow ? DEFAULT_MAXIMUM_LIMB : DEFAULT_MAXIMUM_MOST_SIGNIFICANT_LIMB); + result.prime_basis_limb = prime_limb; + return result; + } else { + return bigfield(witness_t(ctx, fr(limbs[0] + limbs[1] * shift_1)), + witness_t(ctx, fr(limbs[2] + limbs[3] * shift_1)), + can_overflow); + } } template bigfield::bigfield(const byte_array& bytes) @@ -164,8 +207,9 @@ template bigfield::bigfield(const byte_array& const field_t lo_nibble(witness_t(ctx, lo_nibble_val)); const field_t hi_nibble(witness_t(ctx, hi_nibble_val)); - lo_nibble.create_range_constraint(4); - hi_nibble.create_range_constraint(4); + lo_nibble.create_range_constraint(4, "bigfield: lo_nibble too large"); + hi_nibble.create_range_constraint(4, "bigfield: hi_nibble too large"); + const field_t sum = lo_nibble + (hi_nibble * 16); sum.assert_equal(split_byte); return std::make_pair, field_t>((field_t)lo_nibble, (field_t)hi_nibble); @@ -259,6 +303,62 @@ template bigfield bigfield::operator+(const result.binary_basis_limbs[3].maximum_value = binary_basis_limbs[3].maximum_value + other.binary_basis_limbs[3].maximum_value; + if constexpr (C::type == waffle::ComposerType::PLOOKUP) { + if (prime_basis_limb.multiplicative_constant == 1 && other.prime_basis_limb.multiplicative_constant == 1 && + !is_constant() && !other.is_constant()) { + bool limbconst = binary_basis_limbs[0].element.is_constant(); + limbconst = limbconst || binary_basis_limbs[1].element.is_constant(); + limbconst = limbconst || binary_basis_limbs[2].element.is_constant(); + limbconst = limbconst || binary_basis_limbs[3].element.is_constant(); + limbconst = limbconst || prime_basis_limb.is_constant(); + limbconst = limbconst || other.binary_basis_limbs[0].element.is_constant(); + limbconst = limbconst || other.binary_basis_limbs[1].element.is_constant(); + 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); + if (!limbconst) { + std::pair x0{ binary_basis_limbs[0].element.witness_index, + binary_basis_limbs[0].element.multiplicative_constant }; + std::pair x1{ binary_basis_limbs[1].element.witness_index, + binary_basis_limbs[1].element.multiplicative_constant }; + std::pair x2{ binary_basis_limbs[2].element.witness_index, + binary_basis_limbs[2].element.multiplicative_constant }; + std::pair x3{ binary_basis_limbs[3].element.witness_index, + binary_basis_limbs[3].element.multiplicative_constant }; + std::pair y0{ other.binary_basis_limbs[0].element.witness_index, + other.binary_basis_limbs[0].element.multiplicative_constant }; + std::pair y1{ other.binary_basis_limbs[1].element.witness_index, + other.binary_basis_limbs[1].element.multiplicative_constant }; + std::pair y2{ other.binary_basis_limbs[2].element.witness_index, + other.binary_basis_limbs[2].element.multiplicative_constant }; + std::pair y3{ other.binary_basis_limbs[3].element.witness_index, + other.binary_basis_limbs[3].element.multiplicative_constant }; + barretenberg::fr c0(binary_basis_limbs[0].element.additive_constant + + other.binary_basis_limbs[0].element.additive_constant); + barretenberg::fr c1(binary_basis_limbs[1].element.additive_constant + + other.binary_basis_limbs[1].element.additive_constant); + barretenberg::fr c2(binary_basis_limbs[2].element.additive_constant + + other.binary_basis_limbs[2].element.additive_constant); + barretenberg::fr c3(binary_basis_limbs[3].element.additive_constant + + other.binary_basis_limbs[3].element.additive_constant); + + uint32_t xp(prime_basis_limb.witness_index); + uint32_t yp(other.prime_basis_limb.witness_index); + barretenberg::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]); + result.binary_basis_limbs[1].element = field_t::from_witness_index(ctx, output_witnesses[1]); + result.binary_basis_limbs[2].element = field_t::from_witness_index(ctx, output_witnesses[2]); + result.binary_basis_limbs[3].element = field_t::from_witness_index(ctx, output_witnesses[3]); + result.prime_basis_limb = field_t::from_witness_index(ctx, output_witnesses[4]); + return result; + } + } + } + result.binary_basis_limbs[0].element = binary_basis_limbs[0].element + other.binary_basis_limbs[0].element; result.binary_basis_limbs[1].element = binary_basis_limbs[1].element + other.binary_basis_limbs[1].element; result.binary_basis_limbs[2].element = binary_basis_limbs[2].element + other.binary_basis_limbs[2].element; @@ -309,6 +409,23 @@ template bigfield bigfield::operator-(const return operator+(bigfield(ctx, uint256_t(neg_right.lo))); } + /** + * Plookup bigfield subtractoin + * + * We have a special addition gate we can toggle, that will compute: (w_1 + w_4 - w_4_omega + q_arith = 0) + * This is in addition to the regular addition gate + * + * We can arrange our wires in memory like this: + * + * | 1 | 2 | 3 | 4 | + * |-----|-----|-----|-----| + * | b.p | a.0 | b.0 | c.p | (b.p + c.p - a.p = 0) AND (a.0 - b.0 - c.0 = 0) + * | a.p | a.1 | b.1 | c.0 | (a.1 - b.1 - c.1 = 0) + * | a.2 | b.2 | c.2 | c.1 | (a.2 - b.2 - c.2 = 0) + * | a.3 | b.3 | c.3 | --- | (a.3 - b.3 - c.3 = 0) + * + **/ + bigfield result(ctx); /** @@ -393,6 +510,64 @@ template bigfield bigfield::operator-(const result.binary_basis_limbs[1].element = binary_basis_limbs[1].element + barretenberg::fr(to_add_1); result.binary_basis_limbs[2].element = binary_basis_limbs[2].element + barretenberg::fr(to_add_2); result.binary_basis_limbs[3].element = binary_basis_limbs[3].element + barretenberg::fr(to_add_3); + + if constexpr (C::type == waffle::ComposerType::PLOOKUP) { + if (result.prime_basis_limb.multiplicative_constant == 1 && + other.prime_basis_limb.multiplicative_constant == 1 && !result.is_constant() && !other.is_constant()) { + bool limbconst = result.binary_basis_limbs[0].element.is_constant(); + limbconst = limbconst || result.binary_basis_limbs[1].element.is_constant(); + limbconst = limbconst || result.binary_basis_limbs[2].element.is_constant(); + limbconst = limbconst || result.binary_basis_limbs[3].element.is_constant(); + limbconst = limbconst || result.prime_basis_limb.is_constant(); + limbconst = limbconst || other.binary_basis_limbs[0].element.is_constant(); + limbconst = limbconst || other.binary_basis_limbs[1].element.is_constant(); + 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(); + if (!limbconst) { + std::pair x0{ result.binary_basis_limbs[0].element.witness_index, + binary_basis_limbs[0].element.multiplicative_constant }; + std::pair x1{ result.binary_basis_limbs[1].element.witness_index, + binary_basis_limbs[1].element.multiplicative_constant }; + std::pair x2{ result.binary_basis_limbs[2].element.witness_index, + binary_basis_limbs[2].element.multiplicative_constant }; + std::pair x3{ result.binary_basis_limbs[3].element.witness_index, + binary_basis_limbs[3].element.multiplicative_constant }; + std::pair y0{ other.binary_basis_limbs[0].element.witness_index, + other.binary_basis_limbs[0].element.multiplicative_constant }; + std::pair y1{ other.binary_basis_limbs[1].element.witness_index, + other.binary_basis_limbs[1].element.multiplicative_constant }; + std::pair y2{ other.binary_basis_limbs[2].element.witness_index, + other.binary_basis_limbs[2].element.multiplicative_constant }; + std::pair y3{ other.binary_basis_limbs[3].element.witness_index, + other.binary_basis_limbs[3].element.multiplicative_constant }; + barretenberg::fr c0(result.binary_basis_limbs[0].element.additive_constant - + other.binary_basis_limbs[0].element.additive_constant); + barretenberg::fr c1(result.binary_basis_limbs[1].element.additive_constant - + other.binary_basis_limbs[1].element.additive_constant); + barretenberg::fr c2(result.binary_basis_limbs[2].element.additive_constant - + other.binary_basis_limbs[2].element.additive_constant); + barretenberg::fr c3(result.binary_basis_limbs[3].element.additive_constant - + other.binary_basis_limbs[3].element.additive_constant); + + uint32_t xp(result.prime_basis_limb.witness_index); + uint32_t yp(other.prime_basis_limb.witness_index); + barretenberg::fr cp(result.prime_basis_limb.additive_constant - + other.prime_basis_limb.additive_constant); + + const auto output_witnesses = ctx->evaluate_non_native_field_subtraction( + { 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]); + result.binary_basis_limbs[1].element = field_t::from_witness_index(ctx, output_witnesses[1]); + result.binary_basis_limbs[2].element = field_t::from_witness_index(ctx, output_witnesses[2]); + result.binary_basis_limbs[3].element = field_t::from_witness_index(ctx, output_witnesses[3]); + result.prime_basis_limb = field_t::from_witness_index(ctx, output_witnesses[4]); + return result; + } + } + } + result.binary_basis_limbs[0].element -= other.binary_basis_limbs[0].element; result.binary_basis_limbs[1].element -= other.binary_basis_limbs[1].element; result.binary_basis_limbs[2].element -= other.binary_basis_limbs[2].element; @@ -421,7 +596,6 @@ template bigfield bigfield::operator*(const // First we do basic reduction checks of individual elements reduction_check(); other.reduction_check(); - C* ctx = context ? context : other.context; // Now we can actually compute the quotient and remainder values const auto [quotient_value, remainder_value] = compute_quotient_remainder_values(*this, other, {}); @@ -516,7 +690,6 @@ bigfield bigfield::internal_div(const std::vector& numerat } denominator.reduction_check(); - C* ctx = denominator.context; uint512_t numerator_values(0); bool numerator_constant = true; @@ -547,7 +720,6 @@ bigfield bigfield::internal_div(const std::vector& numerat return inverse; } else { // We only add the check if the result is non-constant - std::vector numerator_max; for (const auto& n : numerators) { numerator_max.push_back(n.get_maximum_value()); @@ -614,7 +786,6 @@ bigfield bigfield::div_check_denominator_nonzero(const std::vector bigfield bigfield::sqr() const { reduction_check(); - C* ctx = context; const auto [quotient_value, remainder_value] = compute_quotient_remainder_values(*this, *this, {}); @@ -781,6 +952,8 @@ bigfield bigfield::madd(const bigfield& to_mul, const std::vector::perform_reductions_for_mult_madd(std::vector& mul } else { uint1024_t new_product = DEFAULT_MAXIMUM_REMAINDER * original_right; if (new_product > original_product) { - throw_or_abort("This should never happen"); + throw_or_abort("bigfield: This should never happen"); } maxval_updates.emplace_back( std::tuple(original_product - new_product, i, 0)); @@ -863,7 +1036,7 @@ void bigfield::perform_reductions_for_mult_madd(std::vector& mul } else { uint1024_t new_product = DEFAULT_MAXIMUM_REMAINDER * original_left; if (new_product > original_product) { - throw_or_abort("This should never happen"); + throw_or_abort("bigfield: This should never happen"); } maxval_updates.emplace_back( std::tuple(original_product - new_product, i, 1)); @@ -888,7 +1061,7 @@ void bigfield::perform_reductions_for_mult_madd(std::vector& mul // We choose the largest update auto [update_size, largest_update_product_index, multiplicand_index] = maximum_value_updates[0]; if (!update_size) { - throw_or_abort("Can't reduce further"); + throw_or_abort("bigfield: Can't reduce further"); } // Reduce the larger of the multiplicands that compose the product if (multiplicand_index == 0) { @@ -1105,6 +1278,11 @@ bigfield bigfield::dual_madd(const bigfield& left_a, const bigfield& right_b, const std::vector& to_add) { + left_a.reduction_check(); + right_a.reduction_check(); + left_b.reduction_check(); + right_b.reduction_check(); + std::vector mul_left = { left_a, left_b }; std::vector mul_right = { right_a, right_b }; @@ -1120,7 +1298,12 @@ bigfield bigfield::dual_madd(const bigfield& left_a, * Algorithm is constructed in this way to ensure that all computed terms are positive * * i.e. we evaluate: - * result * divisor + (\sum{mul_left[i] * mul_right[i]) + ...to_sub) = 0 + * result * divisor + (\sum{mul_left[i] * mul_right[i]) + ...to_add) = 0 + * + * It is critical that ALL the terms on the LHS are positive to eliminate the possiblity of underflows + * when calling `evaluate_multiple_multiply_add` + * + * only requires one quotient and remainder + overflow limbs * * We proxy this to mult_madd, so it only requires one quotient and remainder + overflow limbs **/ @@ -1186,7 +1369,6 @@ bigfield bigfield::msub_div(const std::vector& mul_left, eval_right.emplace_back(in); } - // Proxy to mult_madd with fixed remainder==0 mult_madd(eval_left, eval_right, to_sub, true); return result; } @@ -1406,20 +1588,24 @@ template void bigfield::assert_is_in_field() cons 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 (C::type == waffle::PLOOKUP) { - r0 = r0.normalize(); - r1 = r1.normalize(); - r2 = r2.normalize(); - r3 = r3.normalize(); 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 { - r0.create_range_constraint(static_cast(NUM_LIMB_BITS)); - r1.create_range_constraint(static_cast(NUM_LIMB_BITS)); - r2.create_range_constraint(static_cast(NUM_LIMB_BITS)); - r3.create_range_constraint(static_cast(NUM_LIMB_BITS)); + 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."); } } @@ -1430,7 +1616,7 @@ template void bigfield::assert_equal(const bigfie C* ctx = this->context ? this->context : other.context; if (is_constant() && other.is_constant()) { - std::cerr << "calling assert equal on 2 CONSTANT bigfield elements...is this intended?" << std::endl; + 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 @@ -1457,7 +1643,7 @@ template void bigfield::assert_equal(const bigfie const auto [quotient_512, remainder_512] = (diff_val).divmod(modulus); if (remainder_512 != 0) - std::cerr << "remainder not zero!" << std::endl; + std::cerr << "bigfield: remainder not zero!" << std::endl; ASSERT(remainder_512 == 0); bigfield quotient; @@ -1542,7 +1728,9 @@ template void bigfield::self_reduce() const if constexpr (C::type == waffle::PLOOKUP) { context->decompose_into_default_range(quotient_limb.witness_index, static_cast(maximum_quotient_bits)); } else { - quotient_limb.create_range_constraint(static_cast(maximum_quotient_bits)); + context->decompose_into_base4_accumulators(quotient_limb.witness_index, + static_cast(maximum_quotient_bits), + "bigfield: quotient_limb too large."); } ASSERT((uint1024_t(1) << maximum_quotient_bits) * uint1024_t(modulus_u512) + DEFAULT_MAXIMUM_REMAINDER < @@ -1639,119 +1827,241 @@ void bigfield::unsafe_evaluate_multiply_add(const bigfield& input_left, ++max_hi_bits; } - const field_t b0 = left.binary_basis_limbs[1].element.madd( - to_mul.binary_basis_limbs[0].element, quotient.binary_basis_limbs[1].element * neg_modulus_limbs[0]); - const field_t b1 = left.binary_basis_limbs[0].element.madd( - to_mul.binary_basis_limbs[1].element, quotient.binary_basis_limbs[0].element * neg_modulus_limbs[1]); - const field_t c0 = left.binary_basis_limbs[1].element.madd( - to_mul.binary_basis_limbs[1].element, quotient.binary_basis_limbs[1].element * neg_modulus_limbs[1]); - const field_t c1 = left.binary_basis_limbs[2].element.madd( - to_mul.binary_basis_limbs[0].element, quotient.binary_basis_limbs[2].element * neg_modulus_limbs[0]); - const field_t c2 = left.binary_basis_limbs[0].element.madd( - to_mul.binary_basis_limbs[2].element, quotient.binary_basis_limbs[0].element * neg_modulus_limbs[2]); - const field_t d0 = left.binary_basis_limbs[3].element.madd( - to_mul.binary_basis_limbs[0].element, quotient.binary_basis_limbs[3].element * neg_modulus_limbs[0]); - const field_t d1 = left.binary_basis_limbs[2].element.madd( - to_mul.binary_basis_limbs[1].element, quotient.binary_basis_limbs[2].element * neg_modulus_limbs[1]); - const field_t d2 = left.binary_basis_limbs[1].element.madd( - to_mul.binary_basis_limbs[2].element, quotient.binary_basis_limbs[1].element * neg_modulus_limbs[2]); - 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 - // 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} - - const field_t r0 = left.binary_basis_limbs[0].element.madd( - to_mul.binary_basis_limbs[0].element, quotient.binary_basis_limbs[0].element * neg_modulus_limbs[0]); + if constexpr (C::type == waffle::PLOOKUP) { + // The plookup custom bigfield gate requires inputs are witnesses. + // If we're using constant values, instantiate them as circuit variables + const auto convert_constant_to_witness = [ctx](const bigfield& input) { + bigfield output(input); + output.prime_basis_limb = + field_t::from_witness_index(ctx, ctx->put_constant_variable(input.prime_basis_limb.get_value())); + output.binary_basis_limbs[0].element = field_t::from_witness_index( + ctx, ctx->put_constant_variable(input.binary_basis_limbs[0].element.get_value())); + output.binary_basis_limbs[1].element = field_t::from_witness_index( + ctx, ctx->put_constant_variable(input.binary_basis_limbs[1].element.get_value())); + output.binary_basis_limbs[2].element = field_t::from_witness_index( + ctx, ctx->put_constant_variable(input.binary_basis_limbs[2].element.get_value())); + output.binary_basis_limbs[3].element = field_t::from_witness_index( + ctx, ctx->put_constant_variable(input.binary_basis_limbs[3].element.get_value())); + output.context = ctx; + return output; + }; + if (left.is_constant()) { + left = convert_constant_to_witness(left); + } + if (to_mul.is_constant()) { + to_mul = convert_constant_to_witness(to_mul); + } + if (quotient.is_constant()) { + quotient = convert_constant_to_witness(quotient); + } + if (remainders[0].is_constant()) { + remainders[0] = convert_constant_to_witness(remainders[0]); + } - field_t r1 = b0.add_two(b1, -remainders[0].binary_basis_limbs[1].element); - const field_t r2 = c0.add_two(c1, c2); - const field_t r3 = d0 + d1.add_two(d2, d3); + std::vector> limb_0_accumulator{ remainders[0].binary_basis_limbs[0].element }; + std::vector> limb_2_accumulator{ remainders[0].binary_basis_limbs[2].element }; + std::vector> prime_limb_accumulator{ remainders[0].prime_basis_limb }; + for (size_t i = 1; i < remainders.size(); ++i) { + limb_0_accumulator.emplace_back(remainders[i].binary_basis_limbs[0].element); + limb_0_accumulator.emplace_back(remainders[i].binary_basis_limbs[1].element * shift_1); + limb_2_accumulator.emplace_back(remainders[i].binary_basis_limbs[2].element); + limb_2_accumulator.emplace_back(remainders[i].binary_basis_limbs[3].element * shift_1); + prime_limb_accumulator.emplace_back(remainders[i].prime_basis_limb); + } + for (const auto& add : to_add) { + limb_0_accumulator.emplace_back(-add.binary_basis_limbs[0].element); + limb_0_accumulator.emplace_back(-add.binary_basis_limbs[1].element * shift_1); + limb_2_accumulator.emplace_back(-add.binary_basis_limbs[2].element); + limb_2_accumulator.emplace_back(-add.binary_basis_limbs[3].element * shift_1); + prime_limb_accumulator.emplace_back(-add.prime_basis_limb); + } - field_t carry_lo_0 = r0 * shift_right_2; - field_t carry_lo_1 = r1 * (shift_1 * shift_right_2); - field_t carry_lo_2 = -(remainders[0].binary_basis_limbs[0].element * shift_right_2); - field_t carry_lo = carry_lo_0.add_two(carry_lo_1, carry_lo_2); - for (const auto& add_element : to_add) { - carry_lo = carry_lo.add_two(add_element.binary_basis_limbs[0].element * shift_right_2, - add_element.binary_basis_limbs[1].element * (shift_1 * shift_right_2)); - } - for (size_t i = 1; i < remainders.size(); ++i) { - carry_lo = carry_lo.add_two(-remainders[i].binary_basis_limbs[0].element * shift_right_2, - -remainders[i].binary_basis_limbs[1].element * (shift_1 * shift_right_2)); - } - field_t t1 = carry_lo.add_two(-remainders[0].binary_basis_limbs[2].element, - -(remainders[0].binary_basis_limbs[3].element * shift_1)); - 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; - field_t carry_hi = carry_hi_0.add_two(carry_hi_1, carry_hi_2); + const auto& t0 = remainders[0].binary_basis_limbs[1].element; + const auto& t1 = remainders[0].binary_basis_limbs[3].element; + bool needs_normalize = (t0.additive_constant != 0 || t0.multiplicative_constant != 1); + needs_normalize = needs_normalize || (t1.additive_constant != 0 || t1.multiplicative_constant != 1); - for (const auto& add_element : to_add) { - carry_hi = carry_hi.add_two(add_element.binary_basis_limbs[2].element * shift_right_2, - add_element.binary_basis_limbs[3].element * (shift_1 * shift_right_2)); - } - for (size_t i = 1; i < remainders.size(); ++i) { - carry_hi = carry_hi.add_two(-remainders[i].binary_basis_limbs[2].element * shift_right_2, - -remainders[i].binary_basis_limbs[3].element * (shift_1 * shift_right_2)); - } - barretenberg::fr neg_prime = -barretenberg::fr(uint256_t(target_basis.modulus)); + if (needs_normalize) { + limb_0_accumulator.emplace_back(remainders[0].binary_basis_limbs[1].element * shift_1); + limb_2_accumulator.emplace_back(remainders[0].binary_basis_limbs[3].element * shift_1); + } - field_t linear_terms(ctx, barretenberg::fr(0)); - if (to_add.size() >= 2) { - for (size_t i = 0; i < to_add.size(); i += 2) { - linear_terms = linear_terms.add_two(to_add[i].prime_basis_limb, to_add[i + 1].prime_basis_limb); + field_t remainder_limbs[4]{ + field_t::accumulate(limb_0_accumulator), + needs_normalize ? field_t::from_witness_index(ctx, ctx->zero_idx) + : remainders[0].binary_basis_limbs[1].element, + field_t::accumulate(limb_2_accumulator), + needs_normalize ? field_t::from_witness_index(ctx, ctx->zero_idx) + : remainders[0].binary_basis_limbs[3].element, + }; + field_t remainder_prime_limb = field_t::accumulate(prime_limb_accumulator); + + waffle::UltraComposer::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, + }, + { + 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, + }, + { + 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, + }, + { + 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, + }, + { 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, false); + + barretenberg::fr neg_prime = -barretenberg::fr(uint256_t(target_basis.modulus)); + field_t::evaluate_polynomial_identity(left.prime_basis_limb, + to_mul.prime_basis_limb, + quotient.prime_basis_limb * neg_prime, + -remainder_prime_limb); + + field_t lo = field_t::from_witness_index(ctx, lo_idx); + 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); + } 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); } - } - if ((to_add.size() & 1UL) == 1UL) { - linear_terms += to_add[to_add.size() - 1].prime_basis_limb; - } - if (remainders.size() >= 2) { - for (size_t i = 0; i < remainders.size(); i += 2) { - linear_terms = linear_terms.add_two(-remainders[i].prime_basis_limb, -remainders[i + 1].prime_basis_limb); + } else { + const field_t b0 = left.binary_basis_limbs[1].element.madd( + to_mul.binary_basis_limbs[0].element, quotient.binary_basis_limbs[1].element * neg_modulus_limbs[0]); + const field_t b1 = left.binary_basis_limbs[0].element.madd( + to_mul.binary_basis_limbs[1].element, quotient.binary_basis_limbs[0].element * neg_modulus_limbs[1]); + const field_t c0 = left.binary_basis_limbs[1].element.madd( + to_mul.binary_basis_limbs[1].element, quotient.binary_basis_limbs[1].element * neg_modulus_limbs[1]); + const field_t c1 = left.binary_basis_limbs[2].element.madd( + to_mul.binary_basis_limbs[0].element, quotient.binary_basis_limbs[2].element * neg_modulus_limbs[0]); + const field_t c2 = left.binary_basis_limbs[0].element.madd( + to_mul.binary_basis_limbs[2].element, quotient.binary_basis_limbs[0].element * neg_modulus_limbs[2]); + const field_t d0 = left.binary_basis_limbs[3].element.madd( + to_mul.binary_basis_limbs[0].element, quotient.binary_basis_limbs[3].element * neg_modulus_limbs[0]); + const field_t d1 = left.binary_basis_limbs[2].element.madd( + to_mul.binary_basis_limbs[1].element, quotient.binary_basis_limbs[2].element * neg_modulus_limbs[1]); + const field_t d2 = left.binary_basis_limbs[1].element.madd( + to_mul.binary_basis_limbs[2].element, quotient.binary_basis_limbs[1].element * neg_modulus_limbs[2]); + 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 + // 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} + + const field_t r0 = left.binary_basis_limbs[0].element.madd( + to_mul.binary_basis_limbs[0].element, quotient.binary_basis_limbs[0].element * neg_modulus_limbs[0]); + + field_t r1 = b0.add_two(b1, -remainders[0].binary_basis_limbs[1].element); + const field_t r2 = c0.add_two(c1, c2); + const field_t r3 = d0 + d1.add_two(d2, d3); + + field_t carry_lo_0 = r0 * shift_right_2; + field_t carry_lo_1 = r1 * (shift_1 * shift_right_2); + field_t carry_lo_2 = -(remainders[0].binary_basis_limbs[0].element * shift_right_2); + field_t carry_lo = carry_lo_0.add_two(carry_lo_1, carry_lo_2); + for (const auto& add_element : to_add) { + carry_lo = carry_lo.add_two(add_element.binary_basis_limbs[0].element * shift_right_2, + add_element.binary_basis_limbs[1].element * (shift_1 * shift_right_2)); } - } - if ((remainders.size() & 1UL) == 1UL) { - linear_terms += -remainders[remainders.size() - 1].prime_basis_limb; - } + for (size_t i = 1; i < remainders.size(); ++i) { + carry_lo = carry_lo.add_two(-remainders[i].binary_basis_limbs[0].element * shift_right_2, + -remainders[i].binary_basis_limbs[1].element * (shift_1 * shift_right_2)); + } + field_t t1 = carry_lo.add_two(-remainders[0].binary_basis_limbs[2].element, + -(remainders[0].binary_basis_limbs[3].element * shift_1)); + 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; + field_t carry_hi = carry_hi_0.add_two(carry_hi_1, carry_hi_2); + + for (const auto& add_element : to_add) { + carry_hi = carry_hi.add_two(add_element.binary_basis_limbs[2].element * shift_right_2, + add_element.binary_basis_limbs[3].element * (shift_1 * shift_right_2)); + } + for (size_t i = 1; i < remainders.size(); ++i) { + carry_hi = carry_hi.add_two(-remainders[i].binary_basis_limbs[2].element * shift_right_2, + -remainders[i].binary_basis_limbs[3].element * (shift_1 * shift_right_2)); + } + barretenberg::fr neg_prime = -barretenberg::fr(uint256_t(target_basis.modulus)); - // This is where we show our identity is zero mod r (to use CRT we show it's zero mod r and mod 2^t) - field_t::evaluate_polynomial_identity( - left.prime_basis_limb, to_mul.prime_basis_limb, quotient.prime_basis_limb * neg_prime, linear_terms); + field_t linear_terms(ctx, barretenberg::fr(0)); + if (to_add.size() >= 2) { + for (size_t i = 0; i < to_add.size(); i += 2) { + linear_terms = linear_terms.add_two(to_add[i].prime_basis_limb, to_add[i + 1].prime_basis_limb); + } + } + if ((to_add.size() & 1UL) == 1UL) { + linear_terms += to_add[to_add.size() - 1].prime_basis_limb; + } + if (remainders.size() >= 2) { + for (size_t i = 0; i < remainders.size(); i += 2) { + linear_terms = + linear_terms.add_two(-remainders[i].prime_basis_limb, -remainders[i + 1].prime_basis_limb); + } + } + if ((remainders.size() & 1UL) == 1UL) { + linear_terms += -remainders[remainders.size() - 1].prime_basis_limb; + } - 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); + // This is where we show our identity is zero mod r (to use CRT we show it's zero mod r and mod 2^t) + field_t::evaluate_polynomial_identity( + left.prime_basis_limb, to_mul.prime_basis_limb, quotient.prime_basis_limb * neg_prime, linear_terms); - const barretenberg::fr carry_lo_shift(uint256_t(uint256_t(1) << carry_lo_msb)); - // std::cerr <<"lowmsb:" << carry_lo_msb << " highmsb:" <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)); + 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); - } else { + const barretenberg::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, static_cast(carry_lo_msb + carry_hi_msb)); + carry_combined.witness_index, + static_cast(carry_lo_msb + carry_hi_msb), + "bigfield: carry_combined too large in unsafe_evaluate_multiply_add."); field_t accumulator_midpoint = field_t::from_witness_index(ctx, accumulators[static_cast((carry_hi_msb / 2) - 1)]); carry_hi.assert_equal(accumulator_midpoint, "bigfield multiply range check failed"); } else { carry_lo = carry_lo.normalize(); carry_hi = carry_hi.normalize(); - ctx->decompose_into_base4_accumulators(carry_lo.witness_index, static_cast(carry_lo_msb)); - ctx->decompose_into_base4_accumulators(carry_hi.witness_index, static_cast(carry_hi_msb)); + ctx->decompose_into_base4_accumulators(carry_lo.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, + static_cast(carry_hi_msb), + "bigfield: carry_hi too large in unsafe_evaluate_multiply_add."); } } } - /** * Evaluate a quadratic relation involving multiple multiplications * @@ -1877,198 +2187,404 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vector> limb_0_accumulator; - std::vector> limb_2_accumulator; - std::vector> prime_limb_accumulator; - - // Add remaining products into the limb accumulators. - // We negate the product values because the accumulator values itself will be negated - // TODO: why do we do this double negation exactly? seems a bit pointless. I think it stems from the fact that - // the accumulators originaly tracked the remainder term (which is negated) - - for (size_t i = 1; i < num_multiplications; ++i) { - field_t lo_2 = left[i].binary_basis_limbs[0].element * right[i].binary_basis_limbs[0].element; - lo_2 = left[i].binary_basis_limbs[1].element.madd(right[i].binary_basis_limbs[0].element * shift_1, lo_2); - lo_2 = left[i].binary_basis_limbs[0].element.madd(right[i].binary_basis_limbs[1].element * shift_1, lo_2); - field_t hi_2 = left[i].binary_basis_limbs[1].element * right[i].binary_basis_limbs[1].element; - hi_2 = left[i].binary_basis_limbs[2].element.madd(right[i].binary_basis_limbs[0].element, hi_2); - hi_2 = left[i].binary_basis_limbs[0].element.madd(right[i].binary_basis_limbs[2].element, hi_2); - hi_2 = left[i].binary_basis_limbs[3].element.madd(right[i].binary_basis_limbs[0].element * shift_1, hi_2); - hi_2 = left[i].binary_basis_limbs[2].element.madd(right[i].binary_basis_limbs[1].element * shift_1, hi_2); - hi_2 = left[i].binary_basis_limbs[1].element.madd(right[i].binary_basis_limbs[2].element * shift_1, hi_2); - hi_2 = left[i].binary_basis_limbs[0].element.madd(right[i].binary_basis_limbs[3].element * shift_1, hi_2); - - limb_0_accumulator.emplace_back(-lo_2); - limb_2_accumulator.emplace_back(-hi_2); - prime_limb_accumulator.emplace_back(-(left[i].prime_basis_limb * right[i].prime_basis_limb)); - } - - // add cached products into the limb accumulators. - // We negate the cache values because the accumulator values itself will be negated - // TODO: why do we do this double negation exactly? seems a bit pointless. I think it stems from the fact that - // the accumulators originaly tracked the remainder term (which is negated) - - // Update the accumulators with the remainder terms. First check we actually have remainder terms! - //(not present when we're checking a product is 0 mod p). See `assert_is_in_field` - bool no_remainders = remainders.size() == 0; - if (!no_remainders) { - limb_0_accumulator.emplace_back(remainders[0].binary_basis_limbs[0].element); - limb_2_accumulator.emplace_back(remainders[0].binary_basis_limbs[2].element); - prime_limb_accumulator.emplace_back(remainders[0].prime_basis_limb); - } - for (size_t i = 1; i < remainders.size(); ++i) { - limb_0_accumulator.emplace_back(remainders[i].binary_basis_limbs[0].element); - limb_0_accumulator.emplace_back(remainders[i].binary_basis_limbs[1].element * shift_1); - limb_2_accumulator.emplace_back(remainders[i].binary_basis_limbs[2].element); - limb_2_accumulator.emplace_back(remainders[i].binary_basis_limbs[3].element * shift_1); - prime_limb_accumulator.emplace_back(remainders[i].prime_basis_limb); - } - // Update limb accumulators with linear addition terms - for (const auto& add : to_add) { - limb_0_accumulator.emplace_back(-add.binary_basis_limbs[0].element); - limb_0_accumulator.emplace_back(-add.binary_basis_limbs[1].element * shift_1); - limb_2_accumulator.emplace_back(-add.binary_basis_limbs[2].element); - limb_2_accumulator.emplace_back(-add.binary_basis_limbs[3].element * shift_1); - prime_limb_accumulator.emplace_back(-add.prime_basis_limb); - } - - // Accumulate the accumulators! At this point we know that `accumulated_lo` and `accumulated_hi` have not - // overflowed, as the terms inside `limb_0_accumulator/ilmb_2_accumulator` are roughtly in the range 0 - 2^{3t} - // which is << native modulus - field_t accumulated_lo = field_t::accumulate(limb_0_accumulator); - field_t accumulated_hi = field_t::accumulate(limb_2_accumulator); - - // If our accumulators are constant, instantiate them as witnesses. - // TODO: why do we need this? We should be able to handle these values as constants. Needs investigation - if (accumulated_lo.is_constant()) { - accumulated_lo = field_t::from_witness_index(ctx, ctx->put_constant_variable(accumulated_lo.get_value())); - } - if (accumulated_hi.is_constant()) { - accumulated_hi = field_t::from_witness_index(ctx, ctx->put_constant_variable(accumulated_hi.get_value())); - } - - // Compute our 4 remainder limbs. Add our accumuated_lo and accumulated_hi values into the remainder term - field_t remainder1 = no_remainders ? field_t::from_witness_index(ctx, ctx->zero_idx) - : remainders[0].binary_basis_limbs[1].element; - if (remainder1.is_constant()) { - remainder1 = field_t::from_witness_index(ctx, ctx->put_constant_variable(remainder1.get_value())); - } - field_t remainder3 = no_remainders ? field_t::from_witness_index(ctx, ctx->zero_idx) - : remainders[0].binary_basis_limbs[3].element; - if (remainder3.is_constant()) { - remainder3 = field_t::from_witness_index(ctx, ctx->put_constant_variable(remainder3.get_value())); - } - field_t remainder_limbs[4]{ - accumulated_lo, - remainder1, - accumulated_hi, - remainder3, - }; - field_t remainder_prime_limb = field_t::accumulate(prime_limb_accumulator); - - // The following code should be identical to the `evaluate_multiply_add` method.] - - // 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} - field_t r0 = left[0].binary_basis_limbs[0].element.madd( - right[0].binary_basis_limbs[0].element, quotient.binary_basis_limbs[0].element * neg_modulus_limbs[0]); - field_t r1 = b0.add_two(b1, -remainder_limbs[1]); - const field_t r2 = c0.add_two(c1, c2); - const field_t r3 = d0 + d1.add_two(d2, d3); + if constexpr (C::type == waffle::PLOOKUP) { + // The plookup custom bigfield gate requires inputs are witnesses. + // If we're using constant values, instantiate them as circuit variables + + const auto convert_constant_to_witness = [ctx](const bigfield& input) { + bigfield output(input); + output.prime_basis_limb = + field_t::from_witness_index(ctx, ctx->put_constant_variable(input.prime_basis_limb.get_value())); + output.binary_basis_limbs[0].element = field_t::from_witness_index( + ctx, ctx->put_constant_variable(input.binary_basis_limbs[0].element.get_value())); + output.binary_basis_limbs[1].element = field_t::from_witness_index( + ctx, ctx->put_constant_variable(input.binary_basis_limbs[1].element.get_value())); + output.binary_basis_limbs[2].element = field_t::from_witness_index( + ctx, ctx->put_constant_variable(input.binary_basis_limbs[2].element.get_value())); + output.binary_basis_limbs[3].element = field_t::from_witness_index( + ctx, ctx->put_constant_variable(input.binary_basis_limbs[3].element.get_value())); + output.context = ctx; + return output; + }; - field_t carry_lo_0 = r0 * shift_right_2; - field_t carry_lo_1 = r1 * (shift_1 * shift_right_2); - field_t carry_lo_2 = -(remainder_limbs[0] * shift_right_2); - field_t carry_lo = carry_lo_0.add_two(carry_lo_1, carry_lo_2); + // evalaute a nnf mul and add into existing lohi output for our extra product terms + // we need to add the result of (left_b * right_b) into lo_1_idx and hi_1_idx + // our custom gate evaluates: ((a * b) + (q * neg_modulus) - r) / 2^{136} = lo + hi * 2^{136} + // where q is a 'quotient' bigfield and neg_modulus is defined by selector polynomial values + // The custom gate costs 7 constraints, which is cheaper than computing `a * b` using multiplication + + // addition gates But....we want to obtain `left_a * right_b + lo_1 + hi_1 * 2^{136} = lo + hi * 2^{136}` If + // we set `neg_modulus = [2^{136}, 0, 0, 0]` and `q = [lo_1, 0, hi_1, 0]`, then we will add `lo_1` into + // `lo`, and `lo_1/2^{136} + hi_1` into `hi`. we can then subtract off `lo_1/2^{136}` from `hi`, by setting + // `r = [0, 0, lo_1, 0]` This saves us 2 addition gates as we don't have to add together the outputs of two + // calls to `evaluate_non_native_field_multiplication` + std::vector> limb_0_accumulator; + std::vector> limb_2_accumulator; + std::vector> prime_limb_accumulator; + + for (size_t i = 0; i < num_multiplications; ++i) { + if (i == 0 && left[0].is_constant()) { + left[0] = convert_constant_to_witness(left[0]); + } + if (i == 0 && right[0].is_constant()) { + right[0] = convert_constant_to_witness(right[0]); + } + if (i > 0 && left[i].is_constant()) { + left[i] = convert_constant_to_witness(left[i]); + } + if (i > 0 && right[i].is_constant()) { + right[i] = convert_constant_to_witness(right[i]); + } - field_t t1 = carry_lo.add_two(-remainder_limbs[2], -(remainder_limbs[3] * shift_1)); - 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; - field_t carry_hi = carry_hi_0.add_two(carry_hi_1, carry_hi_2); + if (i > 0) { + waffle::UltraComposer::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, + }, + { + 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, + }, + { + 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, + }; + + const auto [lo_2_idx, hi_2_idx] = ctx->evaluate_partial_non_native_field_multiplication(mul_witnesses); + + field_t lo_2 = field_t::from_witness_index(ctx, lo_2_idx); + field_t hi_2 = field_t::from_witness_index(ctx, hi_2_idx); + + limb_0_accumulator.emplace_back(-lo_2); + limb_2_accumulator.emplace_back(-hi_2); + prime_limb_accumulator.emplace_back(-(left[i].prime_basis_limb * right[i].prime_basis_limb)); + } + } + if (quotient.is_constant()) { + quotient = convert_constant_to_witness(quotient); + } - barretenberg::fr neg_prime = -barretenberg::fr(uint256_t(target_basis.modulus)); + bool no_remainders = remainders.size() == 0; + if (!no_remainders) { + limb_0_accumulator.emplace_back(remainders[0].binary_basis_limbs[0].element); + limb_2_accumulator.emplace_back(remainders[0].binary_basis_limbs[2].element); + prime_limb_accumulator.emplace_back(remainders[0].prime_basis_limb); + } + for (size_t i = 1; i < remainders.size(); ++i) { + limb_0_accumulator.emplace_back(remainders[i].binary_basis_limbs[0].element); + limb_0_accumulator.emplace_back(remainders[i].binary_basis_limbs[1].element * shift_1); + limb_2_accumulator.emplace_back(remainders[i].binary_basis_limbs[2].element); + limb_2_accumulator.emplace_back(remainders[i].binary_basis_limbs[3].element * shift_1); + prime_limb_accumulator.emplace_back(remainders[i].prime_basis_limb); + } + for (const auto& add : to_add) { + limb_0_accumulator.emplace_back(-add.binary_basis_limbs[0].element); + limb_0_accumulator.emplace_back(-add.binary_basis_limbs[1].element * shift_1); + limb_2_accumulator.emplace_back(-add.binary_basis_limbs[2].element); + limb_2_accumulator.emplace_back(-add.binary_basis_limbs[3].element * shift_1); + prime_limb_accumulator.emplace_back(-add.prime_basis_limb); + } - field_t linear_terms(ctx, barretenberg::fr(0)); + field_t accumulated_lo = field_t::accumulate(limb_0_accumulator); + field_t accumulated_hi = field_t::accumulate(limb_2_accumulator); + if (accumulated_lo.is_constant()) { + accumulated_lo = + field_t::from_witness_index(ctx, ctx->put_constant_variable(accumulated_lo.get_value())); + } + if (accumulated_hi.is_constant()) { + accumulated_hi = + field_t::from_witness_index(ctx, ctx->put_constant_variable(accumulated_hi.get_value())); + } + field_t remainder1 = no_remainders ? field_t::from_witness_index(ctx, ctx->zero_idx) + : remainders[0].binary_basis_limbs[1].element; + if (remainder1.is_constant()) { + remainder1 = field_t::from_witness_index(ctx, ctx->put_constant_variable(remainder1.get_value())); + } + field_t remainder3 = no_remainders ? field_t::from_witness_index(ctx, ctx->zero_idx) + : remainders[0].binary_basis_limbs[3].element; + if (remainder3.is_constant()) { + remainder3 = field_t::from_witness_index(ctx, ctx->put_constant_variable(remainder3.get_value())); + } + field_t remainder_limbs[4]{ + accumulated_lo, + remainder1, + accumulated_hi, + remainder3, + }; + field_t remainder_prime_limb = field_t::accumulate(prime_limb_accumulator); + + waffle::UltraComposer::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, + }, + { + 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, + }, + { + 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, + }, + { + 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, + }, + { neg_modulus_limbs[0], neg_modulus_limbs[1], neg_modulus_limbs[2], neg_modulus_limbs[3] }, + modulus, + }; - linear_terms += -remainder_prime_limb; + const auto [lo_1_idx, hi_1_idx] = ctx->evaluate_non_native_field_multiplication(witnesses, false, false); - // This is where we show our identity is zero mod r (to use Chinese Remainder Theorem we show it's zero mod r - // and mod 2^t) - field_t::evaluate_polynomial_identity( - left[0].prime_basis_limb, right[0].prime_basis_limb, quotient.prime_basis_limb * neg_prime, linear_terms); + barretenberg::fr neg_prime = -barretenberg::fr(uint256_t(target_basis.modulus)); - 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); + field_t::evaluate_polynomial_identity(left[0].prime_basis_limb, + right[0].prime_basis_limb, + quotient.prime_basis_limb * neg_prime, + -remainder_prime_limb); - const barretenberg::fr carry_lo_shift(uint256_t(uint256_t(1) << carry_lo_msb)); + field_t lo = field_t::from_witness_index(ctx, lo_1_idx); + field_t hi = field_t::from_witness_index(ctx, hi_1_idx); - if constexpr (C::type == waffle::PLOOKUP) { - 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)); + 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); - } 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, static_cast(carry_lo_msb + carry_hi_msb)); - field_t accumulator_midpoint = - field_t::from_witness_index(ctx, accumulators[static_cast((carry_hi_msb / 2) - 1)]); - carry_hi.assert_equal(accumulator_midpoint, "bigfield multiply range check failed"); + // 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); } 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); + } + /* NOTE TO AUDITOR: An extraneous block + if constexpr (C::type == waffle::PLOOKUP) { + 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)); + } + was removed from the the `else` block below. See the conversation at + https://github.com/AztecProtocol/aztec2-internal/pull/1023 + We should make sure that no constraint like this is needed but missing (e.g., an equivalent constraint + was just imposed?). */ + } else { + field_t b0 = left[0].binary_basis_limbs[1].element.madd( + right[0].binary_basis_limbs[0].element, quotient.binary_basis_limbs[1].element * neg_modulus_limbs[0]); + field_t b1 = left[0].binary_basis_limbs[0].element.madd( + right[0].binary_basis_limbs[1].element, quotient.binary_basis_limbs[0].element * neg_modulus_limbs[1]); + field_t c0 = left[0].binary_basis_limbs[1].element.madd( + right[0].binary_basis_limbs[1].element, quotient.binary_basis_limbs[1].element * neg_modulus_limbs[1]); + field_t c1 = left[0].binary_basis_limbs[2].element.madd( + right[0].binary_basis_limbs[0].element, quotient.binary_basis_limbs[2].element * neg_modulus_limbs[0]); + field_t c2 = left[0].binary_basis_limbs[0].element.madd( + right[0].binary_basis_limbs[2].element, quotient.binary_basis_limbs[0].element * neg_modulus_limbs[2]); + field_t d0 = left[0].binary_basis_limbs[3].element.madd( + right[0].binary_basis_limbs[0].element, quotient.binary_basis_limbs[3].element * neg_modulus_limbs[0]); + field_t d1 = left[0].binary_basis_limbs[2].element.madd( + right[0].binary_basis_limbs[1].element, quotient.binary_basis_limbs[2].element * neg_modulus_limbs[1]); + field_t d2 = left[0].binary_basis_limbs[1].element.madd( + right[0].binary_basis_limbs[2].element, quotient.binary_basis_limbs[1].element * neg_modulus_limbs[2]); + field_t d3 = left[0].binary_basis_limbs[0].element.madd( + right[0].binary_basis_limbs[3].element, quotient.binary_basis_limbs[0].element * neg_modulus_limbs[3]); + + /** + * Compute "limb accumulators" + * `limb_0_accumulator` contains contributions in the range 0 - 2^{3t} + * `limb_2_accumulator` contains contributiosn in the range 2^{2t} - 2^{5t} (t = MAX_NUM_LIMB_BITS) + * Actual range will vary a few bits because of lazy reduction techniques + * + * We store these values in an "accumulator" vector in order to efficiently add them into a sum. + * i.e. limb_0 =- field_t::accumulate(limb_0_accumulator) + * This costs us fewer gates than addition operations because we can add 2 values into a sum in a single + *TurboPlonk gate. + **/ + + std::vector> limb_0_accumulator; + std::vector> limb_2_accumulator; + std::vector> prime_limb_accumulator; + + // Add remaining products into the limb accumulators. + // We negate the product values because the accumulator values itself will be negated + // TODO: why do we do this double negation exactly? seems a bit pointless. I think it stems from the fact + // that the accumulators originaly tracked the remainder term (which is negated) + + for (size_t i = 1; i < num_multiplications; ++i) { + field_t lo_2 = left[i].binary_basis_limbs[0].element * right[i].binary_basis_limbs[0].element; + lo_2 = left[i].binary_basis_limbs[1].element.madd(right[i].binary_basis_limbs[0].element * shift_1, lo_2); + lo_2 = left[i].binary_basis_limbs[0].element.madd(right[i].binary_basis_limbs[1].element * shift_1, lo_2); + field_t hi_2 = left[i].binary_basis_limbs[1].element * right[i].binary_basis_limbs[1].element; + hi_2 = left[i].binary_basis_limbs[2].element.madd(right[i].binary_basis_limbs[0].element, hi_2); + hi_2 = left[i].binary_basis_limbs[0].element.madd(right[i].binary_basis_limbs[2].element, hi_2); + hi_2 = left[i].binary_basis_limbs[3].element.madd(right[i].binary_basis_limbs[0].element * shift_1, hi_2); + hi_2 = left[i].binary_basis_limbs[2].element.madd(right[i].binary_basis_limbs[1].element * shift_1, hi_2); + hi_2 = left[i].binary_basis_limbs[1].element.madd(right[i].binary_basis_limbs[2].element * shift_1, hi_2); + hi_2 = left[i].binary_basis_limbs[0].element.madd(right[i].binary_basis_limbs[3].element * shift_1, hi_2); + + limb_0_accumulator.emplace_back(-lo_2); + limb_2_accumulator.emplace_back(-hi_2); + prime_limb_accumulator.emplace_back(-(left[i].prime_basis_limb * right[i].prime_basis_limb)); + } + // add cached products into the limb accumulators. + // We negate the cache values because the accumulator values itself will be negated + // TODO: why do we do this double negation exactly? seems a bit pointless. I think it stems from the fact + // that the accumulators originaly tracked the remainder term (which is negated) + + // Update the accumulators with the remainder terms. First check we actually have remainder terms! + //(not present when we're checking a product is 0 mod p). See `assert_is_in_field` + + bool no_remainders = remainders.size() == 0; + if (!no_remainders) { + limb_0_accumulator.emplace_back(remainders[0].binary_basis_limbs[0].element); + limb_2_accumulator.emplace_back(remainders[0].binary_basis_limbs[2].element); + prime_limb_accumulator.emplace_back(remainders[0].prime_basis_limb); + } + for (size_t i = 1; i < remainders.size(); ++i) { + limb_0_accumulator.emplace_back(remainders[i].binary_basis_limbs[0].element); + limb_0_accumulator.emplace_back(remainders[i].binary_basis_limbs[1].element * shift_1); + limb_2_accumulator.emplace_back(remainders[i].binary_basis_limbs[2].element); + limb_2_accumulator.emplace_back(remainders[i].binary_basis_limbs[3].element * shift_1); + prime_limb_accumulator.emplace_back(remainders[i].prime_basis_limb); + } + for (const auto& add : to_add) { + limb_0_accumulator.emplace_back(-add.binary_basis_limbs[0].element); + limb_0_accumulator.emplace_back(-add.binary_basis_limbs[1].element * shift_1); + limb_2_accumulator.emplace_back(-add.binary_basis_limbs[2].element); + limb_2_accumulator.emplace_back(-add.binary_basis_limbs[3].element * shift_1); + prime_limb_accumulator.emplace_back(-add.prime_basis_limb); + } + + field_t accumulated_lo = field_t::accumulate(limb_0_accumulator); + field_t accumulated_hi = field_t::accumulate(limb_2_accumulator); + if (accumulated_lo.is_constant()) { + accumulated_lo = + field_t::from_witness_index(ctx, ctx->put_constant_variable(accumulated_lo.get_value())); + } + if (accumulated_hi.is_constant()) { + accumulated_hi = + field_t::from_witness_index(ctx, ctx->put_constant_variable(accumulated_hi.get_value())); + } + field_t remainder1 = no_remainders ? field_t::from_witness_index(ctx, ctx->zero_idx) + : remainders[0].binary_basis_limbs[1].element; + if (remainder1.is_constant()) { + remainder1 = field_t::from_witness_index(ctx, ctx->put_constant_variable(remainder1.get_value())); + } + field_t remainder3 = no_remainders ? field_t::from_witness_index(ctx, ctx->zero_idx) + : remainders[0].binary_basis_limbs[3].element; + if (remainder3.is_constant()) { + remainder3 = field_t::from_witness_index(ctx, ctx->put_constant_variable(remainder3.get_value())); + } + field_t remainder_limbs[4]{ + accumulated_lo, + remainder1, + accumulated_hi, + remainder3, + }; + field_t remainder_prime_limb = field_t::accumulate(prime_limb_accumulator); + + // 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} + + field_t r0 = left[0].binary_basis_limbs[0].element.madd( + right[0].binary_basis_limbs[0].element, quotient.binary_basis_limbs[0].element * neg_modulus_limbs[0]); + field_t r1 = b0.add_two(b1, -remainder_limbs[1]); + const field_t r2 = c0.add_two(c1, c2); + const field_t r3 = d0 + d1.add_two(d2, d3); + + field_t carry_lo_0 = r0 * shift_right_2; + field_t carry_lo_1 = r1 * (shift_1 * shift_right_2); + field_t carry_lo_2 = -(remainder_limbs[0] * shift_right_2); + 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)); + 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; + field_t carry_hi = carry_hi_0.add_two(carry_hi_1, carry_hi_2); + + barretenberg::fr neg_prime = -barretenberg::fr(uint256_t(target_basis.modulus)); + + field_t linear_terms(ctx, barretenberg::fr(0)); + + linear_terms += -remainder_prime_limb; + + // This is where we show our identity is zero mod r (to use CRT we show it's zero mod r and mod 2^t) + field_t::evaluate_polynomial_identity( + left[0].prime_basis_limb, right[0].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 barretenberg::fr carry_lo_shift(uint256_t(uint256_t(1) << carry_lo_msb)); + + if constexpr (C::type == waffle::PLOOKUP) { carry_lo = carry_lo.normalize(); carry_hi = carry_hi.normalize(); - ctx->decompose_into_base4_accumulators(carry_lo.witness_index, static_cast(carry_lo_msb)); - ctx->decompose_into_base4_accumulators(carry_hi.witness_index, static_cast(carry_hi_msb)); + 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)); + + } 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, + 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(ctx, accumulators[static_cast((carry_hi_msb / 2) - 1)]); + carry_hi.assert_equal(accumulator_midpoint, "bigfield multiply range check failed"); + } else { + carry_lo = carry_lo.normalize(); + carry_hi = carry_hi.normalize(); + ctx->decompose_into_base4_accumulators( + carry_lo.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, + static_cast(carry_hi_msb), + "bigfield: carry_hi too large in unsafe_evaluate_multiple_multiply_add."); + } } } } -/** - * `evaluate_square_add` - * - * This is extremely similar to `evaluate_multiply_add`, - * but we need fewer gates to compute the limb products of (a * a) vs (a * b) - * TODO: reduce code duplication! Most of this code is re-used from evaluate_multiply_add - **/ template void bigfield::unsafe_evaluate_square_add(const bigfield& left, const std::vector& to_add, @@ -2207,15 +2723,21 @@ void bigfield::unsafe_evaluate_square_add(const bigfield& left, 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, static_cast(carry_lo_msb + carry_hi_msb)); + carry_combined.witness_index, + static_cast(carry_lo_msb + carry_hi_msb), + "bigfield: carry_combined too large in unsafe_evaluate_square_add."); field_t accumulator_midpoint = field_t::from_witness_index(ctx, accumulators[static_cast((carry_hi_msb / 2) - 1)]); carry_hi.assert_equal(accumulator_midpoint, "bigfield multiply range check failed"); } else { carry_lo = carry_lo.normalize(); carry_hi = carry_hi.normalize(); - ctx->decompose_into_base4_accumulators(carry_lo.witness_index, static_cast(carry_lo_msb)); - ctx->decompose_into_base4_accumulators(carry_hi.witness_index, static_cast(carry_hi_msb)); + ctx->decompose_into_base4_accumulators(carry_lo.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, + static_cast(carry_hi_msb), + "bigfield: carry_hi too large in unsafe_evaluate_square_add"); } } } @@ -2288,57 +2810,5 @@ std::pair bigfield::get_quotient_reduction_info(const std::v return std::pair(false, num_quotient_bits); } -/** - * USED FOR TESTS ONLY! DO NOT USE IN PRODUCTION CODE! - */ -template bigfield bigfield::bad_mul(const bigfield& other) const -{ - reduction_check(); - other.reduction_check(); - - C* ctx = context ? context : other.context; - - // const uint1024_t left(get_value()); - // const uint1024_t right(other.get_value()); - /** - * Tn/p = q - * - * q = |Tn/p| - * qp + r > Tn - **/ - const uint1024_t modulus(target_basis.modulus); - const uint1024_t one(1); - const uint1024_t t = one << (68 * 4); - const uint1024_t n = uint1024_t(uint512_t(barretenberg::fr::modulus)); - const uint1024_t t_n = t * n; - - const auto [quotient_1024, remainder_1024] = (t_n).divmod(modulus); - - const uint512_t quotient_value = quotient_1024.lo; - const uint512_t remainder_value = remainder_1024.lo; - - bigfield remainder; - bigfield quotient; - if (is_constant() && other.is_constant()) { - remainder = bigfield(ctx, uint256_t(remainder_value.lo)); - 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 - const size_t num_quotient_bits = get_quotient_max_bits({ DEFAULT_MAXIMUM_REMAINDER }); - quotient = bigfield(witness_t(ctx, fr(quotient_value.slice(0, NUM_LIMB_BITS * 2).lo)), - witness_t(ctx, fr(quotient_value.slice(NUM_LIMB_BITS * 2, NUM_LIMB_BITS * 4).lo)), - false, - num_quotient_bits); - remainder = bigfield( - witness_t(ctx, fr(remainder_value.slice(0, NUM_LIMB_BITS * 2).lo)), - witness_t(ctx, fr(remainder_value.slice(NUM_LIMB_BITS * 2, NUM_LIMB_BITS * 3 + NUM_LAST_LIMB_BITS).lo))); - }; - - unsafe_evaluate_multiply_add(*this, other, {}, quotient, { remainder }); - return remainder; -} - } // namespace stdlib } // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/biggroup/biggroup.hpp b/cpp/src/aztec/stdlib/primitives/biggroup/biggroup.hpp index de52687d45..0ea6424e8c 100644 --- a/cpp/src/aztec/stdlib/primitives/biggroup/biggroup.hpp +++ b/cpp/src/aztec/stdlib/primitives/biggroup/biggroup.hpp @@ -5,6 +5,13 @@ #include "../field/field.hpp" #include +#include "../composers/composers_fwd.hpp" +#include "../memory/rom_table.hpp" +#include "../memory/twin_rom_table.hpp" +#include +#include +#include + #include "../composers/composers_fwd.hpp" namespace plonk { @@ -13,6 +20,18 @@ namespace stdlib { // ( ͡° ͜ʖ ͡°) template class element { public: + struct secp256k1_wnaf { + std::vector> wnaf; + field_t positive_skew; + field_t negative_skew; + field_t least_significant_wnaf_fragment; + bool has_wnaf_fragment = false; + }; + struct secp256k1_wnaf_pair { + secp256k1_wnaf klo; + secp256k1_wnaf khi; + }; + element(); element(const typename NativeGroup::affine_element& input); element(const Fq& x, const Fq& y); @@ -29,7 +48,7 @@ template class element { return out; } - void validate_on_curve() + void validate_on_curve() const { Fq xx = x.sqr(); Fq rhs = y.sqr(); @@ -158,15 +177,14 @@ template class element { // splitting each scalar multiplier up into a 4-bit sliding window wNAF. // more efficient than batch_mul if num_points < 4 // only works with Plookup! - // template - // static element wnaf_batch_mul(const std::vector& points, const std::vector& scalars); + template + static element wnaf_batch_mul(const std::vector& points, const std::vector& scalars); static element batch_mul(const std::vector& points, const std::vector& scalars, const size_t max_num_bits = 0); // we want to conditionally compile this method iff our curve params are the BN254 curve. - // This is a bit tricky to do with `std::enable_if`, because `bn254_endo_batch_mul` is a member function of a - // class + // This is a bit tricky to do with `std::enable_if`, because `bn254_endo_batch_mul` is a member function of a class // template // && the compiler can't perform partial template specialization on member functions of class templates // => our template parameter cannot be a value but must instead by a type @@ -184,17 +202,25 @@ template class element { const std::vector& small_scalars, const size_t max_num_small_bits); - static element mixed_batch_mul(const std::vector& big_points, - const std::vector& big_scalars, - const std::vector& small_points, - const std::vector& small_scalars, - const size_t max_num_small_bits); + template ::value>> + static element bn254_endo_batch_mul_with_generator(const std::vector& big_points, + const std::vector& big_scalars, + const std::vector& small_points, + const std::vector& small_scalars, + const Fr& generator_scalar, + const size_t max_num_small_bits); + + template ::value>> + static element secp256k1_ecdsa_mul(const element& pubkey, const Fr& u1, const Fr& u2); static std::vector> compute_naf(const Fr& scalar, const size_t max_num_bits = 0); template static std::vector> compute_wnaf(const Fr& scalar); + template + static secp256k1_wnaf_pair compute_secp256k1_endo_wnaf(const Fr& scalar); + Composer* get_context() const { if (x.context != nullptr) { @@ -227,332 +253,291 @@ template class element { Fq y; private: + template ::value>> + static std::array, 5> create_group_element_rom_tables( + const std::array& elements); + + template ::value>> + static element read_group_element_rom_tables(const std::array, 5>& tables, + const field_t& index); + static std::pair compute_offset_generators(const size_t num_rounds); - struct twin_lookup_table { - twin_lookup_table(const std::array& inputs) - { - T0 = inputs[1] + inputs[0]; - T1 = inputs[1] - inputs[0]; - } + template ::value>> + struct four_bit_table_plookup { + four_bit_table_plookup(){}; + four_bit_table_plookup(const element& input); - twin_lookup_table(const twin_lookup_table& other) = default; - twin_lookup_table& operator=(const twin_lookup_table& other) = default; + four_bit_table_plookup(const four_bit_table_plookup& other) = default; + four_bit_table_plookup& operator=(const four_bit_table_plookup& other) = default; - element get(const bool_t& v0, const bool_t& v1) const - { - bool_t table_selector = v0 ^ v1; - bool_t sign_selector = v1; - Fq to_add_x = T0.x.conditional_select(T1.x, table_selector); - Fq to_add_y = T0.y.conditional_select(T1.y, table_selector); - element to_add(to_add_x, to_add_y.conditional_negate(sign_selector)); - return to_add; - } + element operator[](const field_t& index) const; + element operator[](const size_t idx) const { return element_table[idx]; } + std::array element_table; + std::array, 5> coordinates; + }; - element operator[](const size_t idx) const - { - if (idx == 0) { - return T0; - } - return T1; - } + template ::value>> + struct eight_bit_fixed_base_table { + enum CurveType { BN254, SECP256K1, SECP256R1 }; + eight_bit_fixed_base_table(const CurveType input_curve_type, bool use_endo) + : curve_type(input_curve_type) + , use_endomorphism(use_endo){}; - element T0; - element T1; + eight_bit_fixed_base_table(const eight_bit_fixed_base_table& other) = default; + eight_bit_fixed_base_table& operator=(const eight_bit_fixed_base_table& other) = default; + + element operator[](const field_t& index) const; + + element operator[](const size_t idx) const; + + CurveType curve_type; + bool use_endomorphism; }; - struct triple_lookup_table { - triple_lookup_table(const std::array& inputs) - { - element T0 = inputs[1] + inputs[0]; - element T1 = inputs[1] - inputs[0]; - element_table[0] = inputs[2] + T0; // C + B + A - element_table[1] = inputs[2] + T1; // C + B - A - element_table[2] = inputs[2] - T1; // C - B + A - element_table[3] = inputs[2] - T0; // C - B - A - - x_b0_table = field_t::preprocess_two_bit_table(element_table[0].x.binary_basis_limbs[0].element, - element_table[1].x.binary_basis_limbs[0].element, - element_table[2].x.binary_basis_limbs[0].element, - element_table[3].x.binary_basis_limbs[0].element); - x_b1_table = field_t::preprocess_two_bit_table(element_table[0].x.binary_basis_limbs[1].element, - element_table[1].x.binary_basis_limbs[1].element, - element_table[2].x.binary_basis_limbs[1].element, - element_table[3].x.binary_basis_limbs[1].element); - x_b2_table = field_t::preprocess_two_bit_table(element_table[0].x.binary_basis_limbs[2].element, - element_table[1].x.binary_basis_limbs[2].element, - element_table[2].x.binary_basis_limbs[2].element, - element_table[3].x.binary_basis_limbs[2].element); - x_b3_table = field_t::preprocess_two_bit_table(element_table[0].x.binary_basis_limbs[3].element, - element_table[1].x.binary_basis_limbs[3].element, - element_table[2].x.binary_basis_limbs[3].element, - element_table[3].x.binary_basis_limbs[3].element); - - y_b0_table = field_t::preprocess_two_bit_table(element_table[0].y.binary_basis_limbs[0].element, - element_table[1].y.binary_basis_limbs[0].element, - element_table[2].y.binary_basis_limbs[0].element, - element_table[3].y.binary_basis_limbs[0].element); - y_b1_table = field_t::preprocess_two_bit_table(element_table[0].y.binary_basis_limbs[1].element, - element_table[1].y.binary_basis_limbs[1].element, - element_table[2].y.binary_basis_limbs[1].element, - element_table[3].y.binary_basis_limbs[1].element); - y_b2_table = field_t::preprocess_two_bit_table(element_table[0].y.binary_basis_limbs[2].element, - element_table[1].y.binary_basis_limbs[2].element, - element_table[2].y.binary_basis_limbs[2].element, - element_table[3].y.binary_basis_limbs[2].element); - y_b3_table = field_t::preprocess_two_bit_table(element_table[0].y.binary_basis_limbs[3].element, - element_table[1].y.binary_basis_limbs[3].element, - element_table[2].y.binary_basis_limbs[3].element, - element_table[3].y.binary_basis_limbs[3].element); + template ::value>> + static std::pair, four_bit_table_plookup<>> create_endo_pair_four_bit_table_plookup( + const element& input) + { + four_bit_table_plookup<> P1; + four_bit_table_plookup<> endoP1; + element d2 = input.dbl(); + + P1.element_table[8] = input; + for (size_t i = 9; i < 16; ++i) { + P1.element_table[i] = P1.element_table[i - 1] + d2; + } + for (size_t i = 0; i < 8; ++i) { + // TODO: DO WE NEED TO REDUCE THESE ELEMENTS???? + P1.element_table[i] = (-P1.element_table[15 - i]); + } + for (size_t i = 0; i < 16; ++i) { + endoP1.element_table[i].y = P1.element_table[15 - i].y; } + uint256_t beta_val = barretenberg::field::cube_root_of_unity(); + Fq beta(barretenberg::fr(beta_val.slice(0, 136)), barretenberg::fr(beta_val.slice(136, 256)), false); + for (size_t i = 0; i < 8; ++i) { + endoP1.element_table[i].x = P1.element_table[i].x * beta; + endoP1.element_table[15 - i].x = endoP1.element_table[i].x; + } + P1.coordinates = create_group_element_rom_tables<16>(P1.element_table); + endoP1.coordinates = create_group_element_rom_tables<16>(endoP1.element_table); + auto result = std::make_pair, four_bit_table_plookup<>>( + (four_bit_table_plookup<>)P1, (four_bit_table_plookup<>)endoP1); + return result; + } - triple_lookup_table(const triple_lookup_table& other) = default; - triple_lookup_table& operator=(const triple_lookup_table& other) = default; + /** + * Creates a lookup table for a set of 2, 3 or 4 points + * + * The lookup table computes linear combinations of all of the points + * + * e.g. for 3 points A, B, C, the table represents the following values: + * + * 0 0 0 -> C+B+A + * 0 0 1 -> C+B-A + * 0 1 0 -> C-B+A + * 0 1 1 -> C-B-A + * 1 0 0 -> -C+B+A + * 1 0 1 -> -C+B-A + * 1 1 0 -> -C-B+A + * 1 1 1 -> -C-B-A + * + * The table KEY is 3 1-bit NAF entries that correspond to scalar multipliers for + * base points A, B, C + **/ + template struct lookup_table_base { + static constexpr size_t table_size = (1ULL << (length - 1)); + lookup_table_base(const std::array& inputs); + lookup_table_base(const lookup_table_base& other) = default; + lookup_table_base& operator=(const lookup_table_base& other) = default; - element get(const bool_t& v0, const bool_t& v1, const bool_t& v2) const - { - bool_t t0 = v2 ^ v0; - bool_t t1 = v2 ^ v1; - - field_t x_b0 = field_t::select_from_two_bit_table(x_b0_table, t1, t0); - field_t x_b1 = field_t::select_from_two_bit_table(x_b1_table, t1, t0); - field_t x_b2 = field_t::select_from_two_bit_table(x_b2_table, t1, t0); - field_t x_b3 = field_t::select_from_two_bit_table(x_b3_table, t1, t0); - - field_t y_b0 = field_t::select_from_two_bit_table(y_b0_table, t1, t0); - field_t y_b1 = field_t::select_from_two_bit_table(y_b1_table, t1, t0); - field_t y_b2 = field_t::select_from_two_bit_table(y_b2_table, t1, t0); - field_t y_b3 = field_t::select_from_two_bit_table(y_b3_table, t1, t0); - - Fq to_add_x; - Fq to_add_y; - to_add_x.binary_basis_limbs[0] = typename Fq::Limb(x_b0, Fq::DEFAULT_MAXIMUM_LIMB); - to_add_x.binary_basis_limbs[1] = typename Fq::Limb(x_b1, Fq::DEFAULT_MAXIMUM_LIMB); - to_add_x.binary_basis_limbs[2] = typename Fq::Limb(x_b2, Fq::DEFAULT_MAXIMUM_LIMB); - to_add_x.binary_basis_limbs[3] = typename Fq::Limb(x_b3, Fq::DEFAULT_MAXIMUM_MOST_SIGNIFICANT_LIMB); - to_add_x.prime_basis_limb = - to_add_x.binary_basis_limbs[0].element.add_two(to_add_x.binary_basis_limbs[1].element * Fq::shift_1, - to_add_x.binary_basis_limbs[2].element * Fq::shift_2); - to_add_x.prime_basis_limb += to_add_x.binary_basis_limbs[3].element * Fq::shift_3; - - to_add_y.binary_basis_limbs[0] = typename Fq::Limb(y_b0, Fq::DEFAULT_MAXIMUM_LIMB); - to_add_y.binary_basis_limbs[1] = typename Fq::Limb(y_b1, Fq::DEFAULT_MAXIMUM_LIMB); - to_add_y.binary_basis_limbs[2] = typename Fq::Limb(y_b2, Fq::DEFAULT_MAXIMUM_LIMB); - to_add_y.binary_basis_limbs[3] = typename Fq::Limb(y_b3, Fq::DEFAULT_MAXIMUM_MOST_SIGNIFICANT_LIMB); - to_add_y.prime_basis_limb = - to_add_y.binary_basis_limbs[0].element.add_two(to_add_y.binary_basis_limbs[1].element * Fq::shift_1, - to_add_y.binary_basis_limbs[2].element * Fq::shift_2); - to_add_y.prime_basis_limb += to_add_y.binary_basis_limbs[3].element * Fq::shift_3; - element to_add(to_add_x, to_add_y.conditional_negate(v2)); - - return to_add; - } + element get(const std::array, length>& bits) const; element operator[](const size_t idx) const { return element_table[idx]; } - std::array, 4> x_b0_table; - std::array, 4> x_b1_table; - std::array, 4> x_b2_table; - std::array, 4> x_b3_table; + std::array, table_size> x_b0_table; + std::array, table_size> x_b1_table; + std::array, table_size> x_b2_table; + std::array, table_size> x_b3_table; + + std::array, table_size> y_b0_table; + std::array, table_size> y_b1_table; + std::array, table_size> y_b2_table; + std::array, table_size> y_b3_table; + element twin0; + element twin1; + std::array element_table; + }; + + /** + * The Plookup version of the above lookup table + * + * Uses ROM tables to efficiently access lookup table + **/ + template ::value>> + struct lookup_table_plookup { + static constexpr size_t table_size = (1ULL << (length)); + lookup_table_plookup() {} + lookup_table_plookup(const std::array& inputs); + lookup_table_plookup(const lookup_table_plookup& other) = default; + lookup_table_plookup& operator=(const lookup_table_plookup& other) = default; + + element get(const std::array, length>& bits) const; - std::array, 4> y_b0_table; - std::array, 4> y_b1_table; - std::array, 4> y_b2_table; - std::array, 4> y_b3_table; + element operator[](const size_t idx) const { return element_table[idx]; } - std::array element_table; + std::array element_table; + std::array, 5> coordinates; }; - struct quad_lookup_table { - quad_lookup_table(const std::array& inputs) - { - element T0 = inputs[1] + inputs[0]; - element T1 = inputs[1] - inputs[0]; - element T2 = inputs[3] + inputs[2]; - element T3 = inputs[3] - inputs[2]; - - element_table[0] = T2 + T0; // D + C + B + A - element_table[1] = T2 + T1; // D + C + B - A - element_table[2] = T2 - T1; // D + C - B + A - element_table[3] = T2 - T0; // D + C - B - A - element_table[4] = T3 + T0; // D - C + B + A - element_table[5] = T3 + T1; // D - C + B - A - element_table[6] = T3 - T1; // D - C - B + A - element_table[7] = T3 - T0; // D - C - B - A - - x_b0_table = - field_t::preprocess_three_bit_table(element_table[0].x.binary_basis_limbs[0].element, - element_table[1].x.binary_basis_limbs[0].element, - element_table[2].x.binary_basis_limbs[0].element, - element_table[3].x.binary_basis_limbs[0].element, - element_table[4].x.binary_basis_limbs[0].element, - element_table[5].x.binary_basis_limbs[0].element, - element_table[6].x.binary_basis_limbs[0].element, - element_table[7].x.binary_basis_limbs[0].element); - x_b1_table = - field_t::preprocess_three_bit_table(element_table[0].x.binary_basis_limbs[1].element, - element_table[1].x.binary_basis_limbs[1].element, - element_table[2].x.binary_basis_limbs[1].element, - element_table[3].x.binary_basis_limbs[1].element, - element_table[4].x.binary_basis_limbs[1].element, - element_table[5].x.binary_basis_limbs[1].element, - element_table[6].x.binary_basis_limbs[1].element, - element_table[7].x.binary_basis_limbs[1].element); - x_b2_table = - field_t::preprocess_three_bit_table(element_table[0].x.binary_basis_limbs[2].element, - element_table[1].x.binary_basis_limbs[2].element, - element_table[2].x.binary_basis_limbs[2].element, - element_table[3].x.binary_basis_limbs[2].element, - element_table[4].x.binary_basis_limbs[2].element, - element_table[5].x.binary_basis_limbs[2].element, - element_table[6].x.binary_basis_limbs[2].element, - element_table[7].x.binary_basis_limbs[2].element); - x_b3_table = - field_t::preprocess_three_bit_table(element_table[0].x.binary_basis_limbs[3].element, - element_table[1].x.binary_basis_limbs[3].element, - element_table[2].x.binary_basis_limbs[3].element, - element_table[3].x.binary_basis_limbs[3].element, - element_table[4].x.binary_basis_limbs[3].element, - element_table[5].x.binary_basis_limbs[3].element, - element_table[6].x.binary_basis_limbs[3].element, - element_table[7].x.binary_basis_limbs[3].element); - - y_b0_table = - field_t::preprocess_three_bit_table(element_table[0].y.binary_basis_limbs[0].element, - element_table[1].y.binary_basis_limbs[0].element, - element_table[2].y.binary_basis_limbs[0].element, - element_table[3].y.binary_basis_limbs[0].element, - element_table[4].y.binary_basis_limbs[0].element, - element_table[5].y.binary_basis_limbs[0].element, - element_table[6].y.binary_basis_limbs[0].element, - element_table[7].y.binary_basis_limbs[0].element); - y_b1_table = - field_t::preprocess_three_bit_table(element_table[0].y.binary_basis_limbs[1].element, - element_table[1].y.binary_basis_limbs[1].element, - element_table[2].y.binary_basis_limbs[1].element, - element_table[3].y.binary_basis_limbs[1].element, - element_table[4].y.binary_basis_limbs[1].element, - element_table[5].y.binary_basis_limbs[1].element, - element_table[6].y.binary_basis_limbs[1].element, - element_table[7].y.binary_basis_limbs[1].element); - y_b2_table = - field_t::preprocess_three_bit_table(element_table[0].y.binary_basis_limbs[2].element, - element_table[1].y.binary_basis_limbs[2].element, - element_table[2].y.binary_basis_limbs[2].element, - element_table[3].y.binary_basis_limbs[2].element, - element_table[4].y.binary_basis_limbs[2].element, - element_table[5].y.binary_basis_limbs[2].element, - element_table[6].y.binary_basis_limbs[2].element, - element_table[7].y.binary_basis_limbs[2].element); - y_b3_table = - field_t::preprocess_three_bit_table(element_table[0].y.binary_basis_limbs[3].element, - element_table[1].y.binary_basis_limbs[3].element, - element_table[2].y.binary_basis_limbs[3].element, - element_table[3].y.binary_basis_limbs[3].element, - element_table[4].y.binary_basis_limbs[3].element, - element_table[5].y.binary_basis_limbs[3].element, - element_table[6].y.binary_basis_limbs[3].element, - element_table[7].y.binary_basis_limbs[3].element); - } - quad_lookup_table(const quad_lookup_table& other) = default; - quad_lookup_table& operator=(const quad_lookup_table& other) = default; + using twin_lookup_table = typename std::conditional, + lookup_table_base<2>>::type; - element get(const bool_t& v0, - const bool_t& v1, - const bool_t& v2, - const bool_t& v3) const - { - bool_t t0 = v3 ^ v0; - bool_t t1 = v3 ^ v1; - bool_t t2 = v3 ^ v2; - - field_t x_b0 = field_t::select_from_three_bit_table(x_b0_table, t2, t1, t0); - field_t x_b1 = field_t::select_from_three_bit_table(x_b1_table, t2, t1, t0); - field_t x_b2 = field_t::select_from_three_bit_table(x_b2_table, t2, t1, t0); - field_t x_b3 = field_t::select_from_three_bit_table(x_b3_table, t2, t1, t0); - - field_t y_b0 = field_t::select_from_three_bit_table(y_b0_table, t2, t1, t0); - field_t y_b1 = field_t::select_from_three_bit_table(y_b1_table, t2, t1, t0); - field_t y_b2 = field_t::select_from_three_bit_table(y_b2_table, t2, t1, t0); - field_t y_b3 = field_t::select_from_three_bit_table(y_b3_table, t2, t1, t0); - - Fq to_add_x; - Fq to_add_y; - to_add_x.binary_basis_limbs[0] = typename Fq::Limb(x_b0, Fq::DEFAULT_MAXIMUM_LIMB); - to_add_x.binary_basis_limbs[1] = typename Fq::Limb(x_b1, Fq::DEFAULT_MAXIMUM_LIMB); - to_add_x.binary_basis_limbs[2] = typename Fq::Limb(x_b2, Fq::DEFAULT_MAXIMUM_LIMB); - to_add_x.binary_basis_limbs[3] = typename Fq::Limb(x_b3, Fq::DEFAULT_MAXIMUM_MOST_SIGNIFICANT_LIMB); - to_add_x.prime_basis_limb = - to_add_x.binary_basis_limbs[0].element.add_two(to_add_x.binary_basis_limbs[1].element * Fq::shift_1, - to_add_x.binary_basis_limbs[2].element * Fq::shift_2); - to_add_x.prime_basis_limb += to_add_x.binary_basis_limbs[3].element * Fq::shift_3; - - to_add_y.binary_basis_limbs[0] = typename Fq::Limb(y_b0, Fq::DEFAULT_MAXIMUM_LIMB); - to_add_y.binary_basis_limbs[1] = typename Fq::Limb(y_b1, Fq::DEFAULT_MAXIMUM_LIMB); - to_add_y.binary_basis_limbs[2] = typename Fq::Limb(y_b2, Fq::DEFAULT_MAXIMUM_LIMB); - to_add_y.binary_basis_limbs[3] = typename Fq::Limb(y_b3, Fq::DEFAULT_MAXIMUM_MOST_SIGNIFICANT_LIMB); - to_add_y.prime_basis_limb = - to_add_y.binary_basis_limbs[0].element.add_two(to_add_y.binary_basis_limbs[1].element * Fq::shift_1, - to_add_y.binary_basis_limbs[2].element * Fq::shift_2); - to_add_y.prime_basis_limb += to_add_y.binary_basis_limbs[3].element * Fq::shift_3; - - element to_add(to_add_x, to_add_y.conditional_negate(v3)); - - return to_add; - } + using triple_lookup_table = typename std::conditional, + lookup_table_base<3>>::type; - element operator[](const size_t idx) const { return element_table[idx]; } + using quad_lookup_table = typename std::conditional, + lookup_table_base<4>>::type; - std::array, 8> x_b0_table; - std::array, 8> x_b1_table; - std::array, 8> x_b2_table; - std::array, 8> x_b3_table; + /** + * Creates a pair of 4-bit lookup tables, the former corresponding to 4 input points, + * the latter corresponding to the endomorphism equivalent of the 4 input points (e.g. x -> \beta * x, y -> -y) + **/ + static std::pair create_endo_pair_quad_lookup_table( + const std::array& inputs) + { + quad_lookup_table base_table(inputs); + quad_lookup_table endo_table; + uint256_t beta_val = barretenberg::field::cube_root_of_unity(); + Fq beta(barretenberg::fr(beta_val.slice(0, 136)), barretenberg::fr(beta_val.slice(136, 256)), false); + if constexpr (Composer::type == waffle::ComposerType::PLOOKUP) { + for (size_t i = 0; i < 8; ++i) { + endo_table.element_table[i + 8].x = base_table[7 - i].x * beta; + endo_table.element_table[i + 8].y = base_table[7 - i].y; + + endo_table.element_table[7 - i] = (-endo_table.element_table[i + 8]).reduce(); + } - std::array, 8> y_b0_table; - std::array, 8> y_b1_table; - std::array, 8> y_b2_table; - std::array, 8> y_b3_table; + endo_table.coordinates = create_group_element_rom_tables<16>(endo_table.element_table); + } else { + std::array endo_inputs(inputs); + for (auto& input : endo_inputs) { + input.x *= beta; + input.y = -input.y; + } + endo_table = quad_lookup_table(endo_inputs); + } + return std::make_pair((quad_lookup_table)base_table, + (quad_lookup_table)endo_table); + } - std::array element_table; - }; + /** + * Creates a pair of 5-bit lookup tables, the former corresponding to 5 input points, + * the latter corresponding to the endomorphism equivalent of the 5 input points (e.g. x -> \beta * x, y -> -y) + **/ + template ::value>> + static std::pair, lookup_table_plookup<5, X>> create_endo_pair_five_lookup_table( + const std::array& inputs) + { + lookup_table_plookup<5> base_table(inputs); + lookup_table_plookup<5> endo_table; + uint256_t beta_val = barretenberg::field::cube_root_of_unity(); + Fq beta(barretenberg::fr(beta_val.slice(0, 136)), barretenberg::fr(beta_val.slice(136, 256)), false); + if constexpr (Composer::type == waffle::ComposerType::PLOOKUP) { + for (size_t i = 0; i < 16; ++i) { + endo_table.element_table[i + 16].x = base_table[15 - i].x * beta; + endo_table.element_table[i + 16].y = base_table[15 - i].y; + + endo_table.element_table[15 - i] = (-endo_table.element_table[i + 16]).reduce(); + } - struct batch_lookup_table { - batch_lookup_table(const std::vector& points) + endo_table.coordinates = create_group_element_rom_tables<32>(endo_table.element_table); + } + return std::make_pair, lookup_table_plookup<5>>((lookup_table_plookup<5>)base_table, + (lookup_table_plookup<5>)endo_table); + } + + /** + * Helper class to split a set of points into lookup table subsets + * + * UltraPlonk version + **/ + template ::value>> + struct batch_lookup_table_plookup { + batch_lookup_table_plookup(const std::vector& points) { num_points = points.size(); - num_quads = num_points / 4; + num_fives = num_points / 5; - has_triple = ((num_quads * 4) < num_points - 2) && (num_points >= 3); + if (num_fives * 5 == (num_points - 1)) { + num_fives -= 1; + num_sixes = 1; + } else { + num_sixes = 0; + } - has_twin = ((num_quads * 4 + (size_t)has_triple * 3) < num_points - 1) && (num_points >= 2); + has_quad = ((num_fives * 5 + num_sixes * 6) < num_points - 3) && (num_points >= 4); - has_singleton = num_points != ((num_quads * 4) + ((size_t)has_triple * 3) + ((size_t)has_twin * 2)); + has_triple = ((num_fives * 5 + num_sixes * 6 + (size_t)has_quad * 4) < num_points - 2) && (num_points >= 3); - for (size_t i = 0; i < num_quads; ++i) { - quad_tables.push_back( - quad_lookup_table({ points[4 * i], points[4 * i + 1], points[4 * i + 2], points[4 * i + 3] })); + has_twin = + ((num_fives * 5 + num_sixes * 6 + (size_t)has_quad * 4 + (size_t)has_triple * 3) < num_points - 1) && + (num_points >= 2); + + has_singleton = num_points != ((num_fives * 5 + num_sixes * 6) + ((size_t)has_quad * 4) + + ((size_t)has_triple * 3) + ((size_t)has_twin * 2)); + + for (size_t i = 0; i < num_fives; ++i) { + five_tables.push_back(lookup_table_plookup<5>( + { points[5 * i], points[5 * i + 1], points[5 * i + 2], points[5 * i + 3], points[5 * i + 4] })); + } + + if (num_sixes == 1) { + six_tables.push_back(lookup_table_plookup<6>({ points[5 * num_fives], + points[5 * num_fives + 1], + points[5 * num_fives + 2], + points[5 * num_fives + 3], + points[5 * num_fives + 4], + points[5 * num_fives + 5] })); + } + + if (has_quad) { + quad_tables.push_back(quad_lookup_table({ points[5 * num_fives], + points[5 * num_fives + 1], + points[5 * num_fives + 2], + points[5 * num_fives + 3] })); } if (has_triple) { triple_tables.push_back(triple_lookup_table( - { points[4 * num_quads], points[4 * num_quads + 1], points[4 * num_quads + 2] })); + { points[5 * num_fives], points[5 * num_fives + 1], points[5 * num_fives + 2] })); } if (has_twin) { - twin_tables.push_back(twin_lookup_table({ points[4 * num_quads], points[4 * num_quads + 1] })); + twin_tables.push_back(twin_lookup_table({ points[5 * num_fives], points[5 * num_fives + 1] })); } if (has_singleton) { singletons.push_back(points[points.size() - 1]); - // singletons[0].x.self_reduce(); - // singletons[0].y.self_reduce(); } } element get_initial_entry() const { std::vector add_accumulator; - for (size_t i = 0; i < num_quads; ++i) { - add_accumulator.push_back(quad_tables[i][0]); + for (size_t i = 0; i < num_sixes; ++i) { + add_accumulator.push_back(six_tables[i][0]); + } + for (size_t i = 0; i < num_fives; ++i) { + add_accumulator.push_back(five_tables[i][0]); + } + if (has_quad) { + add_accumulator.push_back(quad_tables[0][0]); } if (has_twin) { add_accumulator.push_back(twin_tables[0][0]); @@ -571,30 +556,216 @@ template class element { return accumulator; } + chain_add_accumulator get_chain_initial_entry() const + { + std::vector add_accumulator; + for (size_t i = 0; i < num_sixes; ++i) { + add_accumulator.push_back(six_tables[i][0]); + } + for (size_t i = 0; i < num_fives; ++i) { + add_accumulator.push_back(five_tables[i][0]); + } + if (has_quad) { + add_accumulator.push_back(quad_tables[0][0]); + } + if (has_twin) { + add_accumulator.push_back(twin_tables[0][0]); + } + if (has_triple) { + add_accumulator.push_back(triple_tables[0][0]); + } + if (has_singleton) { + add_accumulator.push_back(singletons[0]); + } + if (add_accumulator.size() >= 2) { + chain_add_accumulator output = element::chain_add_start(add_accumulator[0], add_accumulator[1]); + for (size_t i = 2; i < add_accumulator.size(); ++i) { + output = element::chain_add(add_accumulator[i], output); + } + return output; + } + return chain_add_accumulator(add_accumulator[0]); + } + + element::chain_add_accumulator get_chain_add_accumulator(std::vector>& naf_entries) const + { + std::vector round_accumulator; + for (size_t j = 0; j < num_fives; ++j) { + round_accumulator.push_back(five_tables[j].get({ naf_entries[5 * j], + naf_entries[5 * j + 1], + naf_entries[5 * j + 2], + naf_entries[5 * j + 3], + naf_entries[5 * j + 4] })); + } + + if (num_sixes == 1) { + round_accumulator.push_back(six_tables[0].get({ naf_entries[num_fives * 5], + naf_entries[num_fives * 5 + 1], + naf_entries[num_fives * 5 + 2], + naf_entries[num_fives * 5 + 3], + naf_entries[num_fives * 5 + 4], + naf_entries[num_fives * 5 + 5] })); + } + + if (has_quad) { + round_accumulator.push_back(quad_tables[0].get({ naf_entries[num_fives * 5], + naf_entries[num_fives * 5 + 1], + naf_entries[num_fives * 5 + 2], + naf_entries[num_fives * 5 + 3] })); + } + + if (has_triple) { + round_accumulator.push_back(triple_tables[0].get( + { naf_entries[num_fives * 5], naf_entries[num_fives * 5 + 1], naf_entries[num_fives * 5 + 2] })); + } + if (has_twin) { + round_accumulator.push_back( + twin_tables[0].get({ naf_entries[num_fives * 5], naf_entries[num_fives * 5 + 1] })); + } + if (has_singleton) { + round_accumulator.push_back(singletons[0].conditional_negate(naf_entries[num_points - 1])); + } + + element::chain_add_accumulator accumulator; + if (round_accumulator.size() == 1) { + return element::chain_add_accumulator(round_accumulator[0]); + } else if (round_accumulator.size() == 2) { + return element::chain_add_start(round_accumulator[0], round_accumulator[1]); + } else { + accumulator = element::chain_add_start(round_accumulator[0], round_accumulator[1]); + for (size_t j = 2; j < round_accumulator.size(); ++j) { + accumulator = element::chain_add(round_accumulator[j], accumulator); + } + } + return (accumulator); + } + element get(std::vector>& naf_entries) const { std::vector round_accumulator; - for (size_t j = 0; j < num_quads; ++j) { - round_accumulator.push_back(quad_tables[j].get( - naf_entries[4 * j], naf_entries[4 * j + 1], naf_entries[4 * j + 2], naf_entries[4 * j + 3])); + for (size_t j = 0; j < num_fives; ++j) { + round_accumulator.push_back(five_tables[j].get({ naf_entries[5 * j], + naf_entries[5 * j + 1], + naf_entries[5 * j + 2], + naf_entries[5 * j + 3], + naf_entries[5 * j + 4] })); } + + if (num_sixes == 1) { + round_accumulator.push_back(six_tables[0].get({ naf_entries[num_fives * 5], + naf_entries[num_fives * 5 + 1], + naf_entries[num_fives * 5 + 2], + naf_entries[num_fives * 5 + 3], + naf_entries[num_fives * 5 + 4], + naf_entries[num_fives * 5 + 5] })); + } + + if (has_quad) { + round_accumulator.push_back(quad_tables[0].get(naf_entries[num_fives * 5], + naf_entries[num_fives * 5 + 1], + naf_entries[num_fives * 5 + 2], + naf_entries[num_fives * 5 + 3])); + } + if (has_triple) { round_accumulator.push_back(triple_tables[0].get( - naf_entries[num_quads * 4], naf_entries[num_quads * 4 + 1], naf_entries[num_quads * 4 + 2])); + naf_entries[num_fives * 5], naf_entries[num_fives * 5 + 1], naf_entries[num_fives * 5 + 2])); } if (has_twin) { round_accumulator.push_back( - twin_tables[0].get(naf_entries[num_quads * 4], naf_entries[num_quads * 4 + 1])); + twin_tables[0].get(naf_entries[num_fives * 5], naf_entries[num_fives * 5 + 1])); } if (has_singleton) { round_accumulator.push_back(singletons[0].conditional_negate(naf_entries[num_points - 1])); } element result = round_accumulator[0]; - for (size_t j = 1; j < round_accumulator.size(); ++j) { - result = result + round_accumulator[j]; + element::chain_add_accumulator accumulator; + if (round_accumulator.size() == 1) { + return result; + } else if (round_accumulator.size() == 2) { + return result + round_accumulator[1]; + } else { + accumulator = element::chain_add_start(round_accumulator[0], round_accumulator[1]); + for (size_t j = 2; j < round_accumulator.size(); ++j) { + accumulator = element::chain_add(round_accumulator[j], accumulator); + } + } + return element::chain_add_end(accumulator); + } + + std::vector> six_tables; + std::vector> five_tables; + std::vector quad_tables; + std::vector triple_tables; + std::vector twin_tables; + std::vector singletons; + size_t num_points; + + size_t num_sixes; + size_t num_fives; + bool has_quad; + bool has_triple; + bool has_twin; + bool has_singleton; + }; + + /** + * Helper class to split a set of points into lookup table subsets + * + * TurboPlonk version + **/ + struct batch_lookup_table_base { + batch_lookup_table_base(const std::vector& points) + { + num_points = points.size(); + num_quads = num_points / 4; + + has_triple = ((num_quads * 4) < num_points - 2) && (num_points >= 3); + + has_twin = ((num_quads * 4 + (size_t)has_triple * 3) < num_points - 1) && (num_points >= 2); + + has_singleton = num_points != (num_quads * 4 + ((size_t)has_triple * 3) + ((size_t)has_twin * 2)); + + for (size_t i = 0; i < num_quads; ++i) { + quad_tables.push_back( + quad_lookup_table({ points[4 * i], points[4 * i + 1], points[4 * i + 2], points[4 * i + 3] })); + } + + if (has_triple) { + triple_tables.push_back(triple_lookup_table( + { points[4 * num_quads], points[4 * num_quads + 1], points[4 * num_quads + 2] })); + } + if (has_twin) { + twin_tables.push_back(twin_lookup_table({ points[4 * num_quads], points[4 * num_quads + 1] })); + } + + if (has_singleton) { + singletons.push_back(points[points.size() - 1]); + } + } + + element get_initial_entry() const + { + std::vector add_accumulator; + for (size_t i = 0; i < num_quads; ++i) { + add_accumulator.push_back(quad_tables[i][0]); + } + if (has_twin) { + add_accumulator.push_back(twin_tables[0][0]); + } + if (has_triple) { + add_accumulator.push_back(triple_tables[0][0]); + } + if (has_singleton) { + add_accumulator.push_back(singletons[0]); + } + + element accumulator = add_accumulator[0]; + for (size_t i = 1; i < add_accumulator.size(); ++i) { + accumulator = accumulator + add_accumulator[i]; } - return result; + return accumulator; } chain_add_accumulator get_chain_initial_entry() const @@ -626,17 +797,17 @@ template class element { { std::vector round_accumulator; for (size_t j = 0; j < num_quads; ++j) { - round_accumulator.push_back(quad_tables[j].get( - naf_entries[4 * j], naf_entries[4 * j + 1], naf_entries[4 * j + 2], naf_entries[4 * j + 3])); + round_accumulator.push_back(quad_tables[j].get(std::array, 4>{ + naf_entries[4 * j], naf_entries[4 * j + 1], naf_entries[4 * j + 2], naf_entries[4 * j + 3] })); } if (has_triple) { - round_accumulator.push_back(triple_tables[0].get( - naf_entries[num_quads * 4], naf_entries[num_quads * 4 + 1], naf_entries[num_quads * 4 + 2])); + round_accumulator.push_back(triple_tables[0].get(std::array, 3>{ + naf_entries[num_quads * 4], naf_entries[num_quads * 4 + 1], naf_entries[num_quads * 4 + 2] })); } if (has_twin) { - round_accumulator.push_back( - twin_tables[0].get(naf_entries[num_quads * 4], naf_entries[num_quads * 4 + 1])); + round_accumulator.push_back(twin_tables[0].get( + std::array, 2>{ naf_entries[num_quads * 4], naf_entries[num_quads * 4 + 1] })); } if (has_singleton) { round_accumulator.push_back(singletons[0].conditional_negate(naf_entries[num_points - 1])); @@ -658,16 +829,118 @@ template class element { } return (accumulator); } + + element get(std::vector>& naf_entries) const + { + std::vector round_accumulator; + for (size_t j = 0; j < num_quads; ++j) { + round_accumulator.push_back(quad_tables[j].get( + { naf_entries[4 * j], naf_entries[4 * j + 1], naf_entries[4 * j + 2], naf_entries[4 * j + 3] })); + } + + if (has_triple) { + round_accumulator.push_back(triple_tables[0].get(std::array, 3>{ + naf_entries[num_quads * 4], naf_entries[num_quads * 4 + 1], naf_entries[num_quads * 4 + 2] })); + } + if (has_twin) { + round_accumulator.push_back( + twin_tables[0].get({ naf_entries[num_quads * 4], naf_entries[num_quads * 4 + 1] })); + } + if (has_singleton) { + round_accumulator.push_back(singletons[0].conditional_negate(naf_entries[num_points - 1])); + } + + element result = round_accumulator[0]; + element::chain_add_accumulator accumulator; + if (round_accumulator.size() == 1) { + return result; + } else if (round_accumulator.size() == 2) { + return result + round_accumulator[1]; + } else { + accumulator = element::chain_add_start(round_accumulator[0], round_accumulator[1]); + for (size_t j = 2; j < round_accumulator.size(); ++j) { + accumulator = element::chain_add(round_accumulator[j], accumulator); + } + } + return element::chain_add_end(accumulator); + } + + // chain_add_accumulator get_chain_initial_entry() const + // { + // std::vector add_accumulator; + // for (size_t i = 0; i < num_quads; ++i) { + // add_accumulator.push_back(quad_tables[i][0]); + // } + // if (has_twin) { + // add_accumulator.push_back(twin_tables[0][0]); + // } + // if (has_triple) { + // add_accumulator.push_back(triple_tables[0][0]); + // } + // if (has_singleton) { + // add_accumulator.push_back(singletons[0]); + // } + // if (add_accumulator.size() >= 2) { + // chain_add_accumulator output = element::chain_add_start(add_accumulator[0], add_accumulator[1]); + // for (size_t i = 2; i < add_accumulator.size(); ++i) { + // output = element::chain_add(add_accumulator[i], output); + // } + // return output; + // } + // return chain_add_accumulator(add_accumulator[0]); + // } + + // element::chain_add_accumulator get_chain_add_accumulator(std::vector>& naf_entries) const + // { + // std::vector round_accumulator; + // for (size_t j = 0; j < num_quads; ++j) { + // round_accumulator.push_back(quad_tables[j].get( + // naf_entries[4 * j], naf_entries[4 * j + 1], naf_entries[4 * j + 2], naf_entries[4 * j + 3])); + // } + + // if (has_triple) { + // round_accumulator.push_back(triple_tables[0].get( + // naf_entries[num_quads * 4], naf_entries[num_quads * 4 + 1], naf_entries[num_quads * 4 + 2])); + // } + // if (has_twin) { + // round_accumulator.push_back( + // twin_tables[0].get(naf_entries[num_quads * 4], naf_entries[num_quads * 4 + 1])); + // } + // if (has_singleton) { + // round_accumulator.push_back(singletons[0].conditional_negate(naf_entries[num_points - 1])); + // } + + // element::chain_add_accumulator accumulator; + // if (round_accumulator.size() == 1) { + // accumulator.x3_prev = round_accumulator[0].x; + // accumulator.y3_prev = round_accumulator[0].y; + // accumulator.is_element = true; + // return accumulator; + // } else if (round_accumulator.size() == 2) { + // return element::chain_add_start(round_accumulator[0], round_accumulator[1]); + // } else { + // accumulator = element::chain_add_start(round_accumulator[0], round_accumulator[1]); + // for (size_t j = 2; j < round_accumulator.size(); ++j) { + // accumulator = element::chain_add(round_accumulator[j], accumulator); + // } + // } + // return (accumulator); + // } std::vector quad_tables; std::vector triple_tables; std::vector twin_tables; std::vector singletons; size_t num_points; + size_t num_quads; bool has_triple; bool has_twin; bool has_singleton; }; + + using batch_lookup_table = typename std::conditional, + batch_lookup_table_base>::type; }; template @@ -678,4 +951,9 @@ inline std::ostream& operator<<(std::ostream& os, element const& v } // namespace stdlib } // namespace plonk -#include "biggroup_impl.hpp" \ No newline at end of file +#include "biggroup_nafs.hpp" +#include "biggroup_tables.hpp" +#include "biggroup_impl.hpp" +#include "biggroup_batch_mul.hpp" +#include "biggroup_secp256k1.hpp" +#include "biggroup_bn254.hpp" diff --git a/cpp/src/aztec/stdlib/primitives/biggroup/biggroup.test.cpp b/cpp/src/aztec/stdlib/primitives/biggroup/biggroup.test.cpp index f1d6596616..e5107225af 100644 --- a/cpp/src/aztec/stdlib/primitives/biggroup/biggroup.test.cpp +++ b/cpp/src/aztec/stdlib/primitives/biggroup/biggroup.test.cpp @@ -1,91 +1,66 @@ #include +#include #include "../bigfield/bigfield.hpp" #include "../biggroup/biggroup.hpp" #include "../bool/bool.hpp" #include "../field/field.hpp" -#include -#include - -#include - #include #include -#include +#include #include #include -using namespace barretenberg; -using namespace plonk; +namespace test_stdlib_biggroup { +namespace { +auto& engine = numeric::random::get_debug_engine(); +} #define GET_COMPOSER_NAME_STRING(composer) \ (typeid(composer) == typeid(waffle::StandardComposer) \ ? "StandardPlonk" \ : typeid(composer) == typeid(waffle::TurboComposer) ? "TurboPlonk" : "NULLPlonk") -// SEE BOTTOM FOR REMNANTS OF TESTS FOR PLOOKUP AND NOTE ON UPDATING THOSE - -template class stdlib_biggroup : public testing::Test { - typedef stdlib::bn254 bn254; - typedef stdlib::secp256r1_ct secp256r1_ct; - typedef typename bn254::fr_ct fr_ct; - typedef typename bn254::bigfr_ct bigfr_ct; - typedef typename bn254::g1_ct g1_ct; - typedef typename bn254::g1_bigfr_ct g1_bigfr_ct; - typedef typename bn254::fq_ct fq_ct; - typedef typename bn254::public_witness_ct public_witness_ct; - typedef typename bn254::witness_ct witness_ct; - - static g1_bigfr_ct convert_inputs_bigfr(Composer* ctx, const g1::affine_element& input) - { - uint256_t x_u256(input.x); - uint256_t y_u256(input.y); - - fq_ct x(witness_ct(ctx, fr(x_u256.slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(ctx, fr(x_u256.slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct y(witness_ct(ctx, fr(y_u256.slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(ctx, fr(y_u256.slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - - return g1_bigfr_ct(x, y); - } - - static bigfr_ct convert_inputs_bigfr(Composer* ctx, const fr& scalar) - { - uint256_t scalar_u256(scalar); - - bigfr_ct x(witness_ct(ctx, fr(scalar_u256.slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(ctx, fr(scalar_u256.slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - - return x; - } - - static g1_ct convert_inputs(Composer* ctx, const g1::affine_element& input) - { - uint256_t x_u256(input.x); - uint256_t y_u256(input.y); +// using namespace barretenberg; +using namespace plonk; - fq_ct x(witness_ct(ctx, fr(x_u256.slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(ctx, fr(x_u256.slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); - fq_ct y(witness_ct(ctx, fr(y_u256.slice(0, fq_ct::NUM_LIMB_BITS * 2))), - witness_ct(ctx, fr(y_u256.slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); +// One can only define a TYPED_TEST with a single template paramter. +// Our workaround is to pass parameters of the following type. +template struct TestType { + public: + using Curve = _Curve; + static const bool use_bigfield = _use_bigfield; + using element_ct = + typename std::conditional<_use_bigfield, typename Curve::g1_bigfr_ct, typename Curve::g1_ct>::type; + // the field of scalars acting on element_ct + using scalar_ct = typename std::conditional<_use_bigfield, typename Curve::bigfr_ct, typename Curve::fr_ct>::type; +}; - return g1_ct(x, y); - } +template class stdlib_biggroup : public testing::Test { + using Curve = typename TestType::Curve; + using element_ct = typename TestType::element_ct; + using scalar_ct = typename TestType::scalar_ct; - static typename secp256r1_ct::bigfr_ct convert_inputs_bigfr_secp256r1(Composer* ctx, const secp256r1::fr& scalar) - { - uint256_t scalar_u256(scalar); + using fq = typename Curve::fq; + using fr = typename Curve::fr; + using g1 = typename Curve::g1; + using affine_element = typename g1::affine_element; + using element = typename g1::element; - typename secp256r1_ct::bigfr_ct x( - witness_ct(ctx, fr(scalar_u256.slice(0, secp256r1_ct::fq_ct::NUM_LIMB_BITS * 2))), - witness_ct( - ctx, - fr(scalar_u256.slice(secp256r1_ct::fq_ct::NUM_LIMB_BITS * 2, secp256r1_ct::fq_ct::NUM_LIMB_BITS * 4)))); + using Composer = typename Curve::Composer; - return x; - } + static constexpr auto EXPECT_VERIFICATION = [](Composer& composer, bool expected_result = true) { + info("composer gates = ", composer.get_num_gates()); + auto prover = composer.create_prover(); + info("creating verifier"); + auto verifier = composer.create_verifier(); + info("creating proof"); + waffle::plonk_proof proof = prover.construct_proof(); + bool verified = verifier.verify_proof(proof); + EXPECT_EQ(verified, expected_result); + }; public: static void test_add() @@ -93,20 +68,21 @@ template class stdlib_biggroup : public testing::Test { auto composer = Composer("../srs_db/ignition/"); size_t num_repetitions = 10; for (size_t i = 0; i < num_repetitions; ++i) { - g1::affine_element input_a(g1::element::random_element()); - g1::affine_element input_b(g1::element::random_element()); + affine_element input_a(element::random_element()); + affine_element input_b(element::random_element()); + + element_ct a = element_ct::from_witness(&composer, input_a); + element_ct b = element_ct::from_witness(&composer, input_b); - g1_bigfr_ct a = convert_inputs_bigfr(&composer, input_a); - g1_bigfr_ct b = convert_inputs_bigfr(&composer, input_b); uint64_t before = composer.get_num_gates(); - g1_bigfr_ct c = a + b; + element_ct c = a + b; uint64_t after = composer.get_num_gates(); if (i == num_repetitions - 1) { std::cout << "num gates per add = " << after - before << std::endl; benchmark_info(GET_COMPOSER_NAME_STRING(Composer), "Biggroup", "ADD", "Gate Count", after - before); } - g1::affine_element c_expected(g1::element(input_a) + g1::element(input_b)); + affine_element c_expected(element(input_a) + element(input_b)); uint256_t c_x_u256 = c.x.get_value().lo; uint256_t c_y_u256 = c.y.get_value().lo; @@ -117,11 +93,8 @@ template class stdlib_biggroup : public testing::Test { EXPECT_EQ(c_x_result, c_expected.x); EXPECT_EQ(c_y_result, c_expected.y); } - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); + + EXPECT_VERIFICATION(composer); } static void test_sub() @@ -129,15 +102,15 @@ template class stdlib_biggroup : public testing::Test { auto composer = Composer("../srs_db/ignition/"); size_t num_repetitions = 10; for (size_t i = 0; i < num_repetitions; ++i) { - g1::affine_element input_a(g1::element::random_element()); - g1::affine_element input_b(g1::element::random_element()); + affine_element input_a(element::random_element()); + affine_element input_b(element::random_element()); - g1_bigfr_ct a = convert_inputs_bigfr(&composer, input_a); - g1_bigfr_ct b = convert_inputs_bigfr(&composer, input_b); + element_ct a = element_ct::from_witness(&composer, input_a); + element_ct b = element_ct::from_witness(&composer, input_b); - g1_bigfr_ct c = a - b; + element_ct c = a - b; - g1::affine_element c_expected(g1::element(input_a) - g1::element(input_b)); + affine_element c_expected(element(input_a) - element(input_b)); uint256_t c_x_u256 = c.x.get_value().lo; uint256_t c_y_u256 = c.y.get_value().lo; @@ -148,11 +121,8 @@ template class stdlib_biggroup : public testing::Test { EXPECT_EQ(c_x_result, c_expected.x); EXPECT_EQ(c_y_result, c_expected.y); } - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); + + EXPECT_VERIFICATION(composer); } static void test_dbl() @@ -160,13 +130,13 @@ template class stdlib_biggroup : public testing::Test { auto composer = Composer("../srs_db/ignition/"); size_t num_repetitions = 10; for (size_t i = 0; i < num_repetitions; ++i) { - g1::affine_element input_a(g1::element::random_element()); + affine_element input_a(element::random_element()); - g1_bigfr_ct a = convert_inputs_bigfr(&composer, input_a); + element_ct a = element_ct::from_witness(&composer, input_a); - g1_bigfr_ct c = a.dbl(); + element_ct c = a.dbl(); - g1::affine_element c_expected(g1::element(input_a).dbl()); + affine_element c_expected(element(input_a).dbl()); uint256_t c_x_u256 = c.x.get_value().lo; uint256_t c_y_u256 = c.y.get_value().lo; @@ -177,11 +147,8 @@ template class stdlib_biggroup : public testing::Test { EXPECT_EQ(c_x_result, c_expected.x); EXPECT_EQ(c_y_result, c_expected.y); } - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); + + EXPECT_VERIFICATION(composer); } static void test_montgomery_ladder() @@ -189,15 +156,15 @@ template class stdlib_biggroup : public testing::Test { auto composer = Composer("../srs_db/ignition/"); size_t num_repetitions = 1; for (size_t i = 0; i < num_repetitions; ++i) { - g1::affine_element input_a(g1::element::random_element()); - g1::affine_element input_b(g1::element::random_element()); + affine_element input_a(element::random_element()); + affine_element input_b(element::random_element()); - g1_bigfr_ct a = convert_inputs_bigfr(&composer, input_a); - g1_bigfr_ct b = convert_inputs_bigfr(&composer, input_b); + element_ct a = element_ct::from_witness(&composer, input_a); + element_ct b = element_ct::from_witness(&composer, input_b); - g1_bigfr_ct c = a.montgomery_ladder(b); + element_ct c = a.montgomery_ladder(b); - g1::affine_element c_expected(g1::element(input_a).dbl() + g1::element(input_b)); + affine_element c_expected(element(input_a).dbl() + element(input_b)); uint256_t c_x_u256 = c.x.get_value().lo; uint256_t c_y_u256 = c.y.get_value().lo; @@ -208,11 +175,8 @@ template class stdlib_biggroup : public testing::Test { EXPECT_EQ(c_x_result, c_expected.x); EXPECT_EQ(c_y_result, c_expected.y); } - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); + + EXPECT_VERIFICATION(composer); } static void test_mul() @@ -220,18 +184,18 @@ template class stdlib_biggroup : public testing::Test { auto composer = Composer("../srs_db/ignition/"); size_t num_repetitions = 1; for (size_t i = 0; i < num_repetitions; ++i) { - g1::affine_element input(g1::element::random_element()); + affine_element input(element::random_element()); fr scalar(fr::random_element()); if (uint256_t(scalar).get_bit(0)) { scalar -= fr(1); // make sure to add skew } - g1_ct P = convert_inputs(&composer, input); - fr_ct x = witness_ct(&composer, scalar); + element_ct P = element_ct::from_witness(&composer, input); + scalar_ct x = scalar_ct::from_witness(&composer, scalar); - std::cout << "gates before mul " << composer.get_num_gates() << std::endl; - g1_ct c = P * x; - std::cout << "composer aftr mul " << composer.get_num_gates() << std::endl; - g1::affine_element c_expected(g1::element(input) * scalar); + std::cerr << "gates before mul " << composer.get_num_gates() << std::endl; + element_ct c = P * x; + std::cerr << "composer aftr mul " << composer.get_num_gates() << std::endl; + affine_element c_expected(element(input) * scalar); fq c_x_result(c.x.get_value().lo); fq c_y_result(c.y.get_value().lo); @@ -239,14 +203,8 @@ template class stdlib_biggroup : public testing::Test { EXPECT_EQ(c_x_result, c_expected.x); EXPECT_EQ(c_y_result, c_expected.y); } - std::cout << "composer gates = " << composer.get_num_gates() << std::endl; - auto prover = composer.create_prover(); - std::cout << "creating verifier " << std::endl; - auto verifier = composer.create_verifier(); - std::cout << "creating proof " << std::endl; - waffle::plonk_proof proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); + + EXPECT_VERIFICATION(composer); } static void test_twin_mul() @@ -254,39 +212,33 @@ template class stdlib_biggroup : public testing::Test { auto composer = Composer("../srs_db/ignition/"); size_t num_repetitions = 1; for (size_t i = 0; i < num_repetitions; ++i) { - g1::affine_element input_a(g1::element::random_element()); - g1::affine_element input_b(g1::element::random_element()); + affine_element input_a(element::random_element()); + affine_element input_b(element::random_element()); fr scalar_a(fr::random_element()); fr scalar_b(fr::random_element()); if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) { - scalar_a -= fr(1); // make a have skew + scalar_a -= fr(1); // skew bit is 1 } if ((uint256_t(scalar_b).get_bit(0) & 1) == 0) { - scalar_b += fr(1); // make b not have skew + scalar_b += fr(1); // skew bit is 0 } - g1_bigfr_ct P_a = convert_inputs_bigfr(&composer, input_a); - bigfr_ct x_a = convert_inputs_bigfr(&composer, scalar_a); - g1_bigfr_ct P_b = convert_inputs_bigfr(&composer, input_b); - bigfr_ct x_b = convert_inputs_bigfr(&composer, scalar_b); - - g1_bigfr_ct c = g1_bigfr_ct::batch_mul({ P_a, P_b }, { x_a, x_b }); - g1::element input_c = (g1::element(input_a) * scalar_a); - g1::element input_d = (g1::element(input_b) * scalar_b); - g1::affine_element expected(input_c + input_d); + element_ct P_a = element_ct::from_witness(&composer, input_a); + scalar_ct x_a = scalar_ct::from_witness(&composer, scalar_a); + element_ct P_b = element_ct::from_witness(&composer, input_b); + scalar_ct x_b = scalar_ct::from_witness(&composer, scalar_b); + + element_ct c = element_ct::batch_mul({ P_a, P_b }, { x_a, x_b }); + element input_c = (element(input_a) * scalar_a); + element input_d = (element(input_b) * scalar_b); + affine_element expected(input_c + input_d); fq c_x_result(c.x.get_value().lo); fq c_y_result(c.y.get_value().lo); EXPECT_EQ(c_x_result, expected.x); EXPECT_EQ(c_y_result, expected.y); } - std::cout << "composer gates = " << composer.get_num_gates() << std::endl; - auto prover = composer.create_prover(); - std::cout << "creating verifier " << std::endl; - auto verifier = composer.create_verifier(); - std::cout << "creating proof " << std::endl; - waffle::plonk_proof proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); + + EXPECT_VERIFICATION(composer); } static void test_triple_mul() @@ -294,96 +246,39 @@ template class stdlib_biggroup : public testing::Test { auto composer = Composer("../srs_db/ignition/"); size_t num_repetitions = 1; for (size_t i = 0; i < num_repetitions; ++i) { - g1::affine_element input_a(g1::element::random_element()); - g1::affine_element input_b(g1::element::random_element()); - g1::affine_element input_c(g1::element::random_element()); + affine_element input_a(element::random_element()); + affine_element input_b(element::random_element()); + affine_element input_c(element::random_element()); fr scalar_a(fr::random_element()); fr scalar_b(fr::random_element()); fr scalar_c(fr::random_element()); if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) { - scalar_a -= fr(1); // make a have skew + scalar_a -= fr(1); // skew bit is 1 } if ((uint256_t(scalar_b).get_bit(0) & 1) == 0) { - scalar_b += fr(1); // make b not have skew + scalar_b += fr(1); // skew bit is 0 } - g1_bigfr_ct P_a = convert_inputs_bigfr(&composer, input_a); - bigfr_ct x_a = convert_inputs_bigfr(&composer, scalar_a); - g1_bigfr_ct P_b = convert_inputs_bigfr(&composer, input_b); - bigfr_ct x_b = convert_inputs_bigfr(&composer, scalar_b); - g1_bigfr_ct P_c = convert_inputs_bigfr(&composer, input_c); - bigfr_ct x_c = convert_inputs_bigfr(&composer, scalar_c); - - g1_bigfr_ct c = g1_bigfr_ct::batch_mul({ P_a, P_b, P_c }, { x_a, x_b, x_c }); - g1::element input_e = (g1::element(input_a) * scalar_a); - g1::element input_f = (g1::element(input_b) * scalar_b); - g1::element input_g = (g1::element(input_c) * scalar_c); - - g1::affine_element expected(input_e + input_f + input_g); + element_ct P_a = element_ct::from_witness(&composer, input_a); + scalar_ct x_a = scalar_ct::from_witness(&composer, scalar_a); + element_ct P_b = element_ct::from_witness(&composer, input_b); + scalar_ct x_b = scalar_ct::from_witness(&composer, scalar_b); + element_ct P_c = element_ct::from_witness(&composer, input_c); + scalar_ct x_c = scalar_ct::from_witness(&composer, scalar_c); + + element_ct c = element_ct::batch_mul({ P_a, P_b, P_c }, { x_a, x_b, x_c }); + element input_e = (element(input_a) * scalar_a); + element input_f = (element(input_b) * scalar_b); + element input_g = (element(input_c) * scalar_c); + + affine_element expected(input_e + input_f + input_g); fq c_x_result(c.x.get_value().lo); fq c_y_result(c.y.get_value().lo); EXPECT_EQ(c_x_result, expected.x); EXPECT_EQ(c_y_result, expected.y); } - std::cout << "composer gates = " << composer.get_num_gates() << std::endl; - auto prover = composer.create_prover(); - std::cout << "creating verifier " << std::endl; - auto verifier = composer.create_verifier(); - std::cout << "creating proof " << std::endl; - waffle::plonk_proof proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); - } - static void test_quad_mul_bigfr() - { - auto composer = Composer("../srs_db/ignition/"); - size_t num_repetitions = 1; - for (size_t i = 0; i < num_repetitions; ++i) { - g1::affine_element input_a(g1::element::random_element()); - g1::affine_element input_b(g1::element::random_element()); - g1::affine_element input_c(g1::element::random_element()); - g1::affine_element input_d(g1::element::random_element()); - fr scalar_a(fr::random_element()); - fr scalar_b(fr::random_element()); - fr scalar_c(fr::random_element()); - fr scalar_d(fr::random_element()); - if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) { - scalar_a -= fr(1); // make a have skew - } - if ((uint256_t(scalar_b).get_bit(0) & 1) == 0) { - scalar_b += fr(1); // make b not have skew - } - g1_bigfr_ct P_a = convert_inputs_bigfr(&composer, input_a); - bigfr_ct x_a = convert_inputs_bigfr(&composer, scalar_a); - g1_bigfr_ct P_b = convert_inputs_bigfr(&composer, input_b); - bigfr_ct x_b = convert_inputs_bigfr(&composer, scalar_b); - g1_bigfr_ct P_c = convert_inputs_bigfr(&composer, input_c); - bigfr_ct x_c = convert_inputs_bigfr(&composer, scalar_c); - g1_bigfr_ct P_d = convert_inputs_bigfr(&composer, input_d); - bigfr_ct x_d = convert_inputs_bigfr(&composer, scalar_d); - - g1_bigfr_ct c = g1_bigfr_ct::batch_mul({ P_a, P_b, P_c, P_d }, { x_a, x_b, x_c, x_d }); - g1::element input_e = (g1::element(input_a) * scalar_a); - g1::element input_f = (g1::element(input_b) * scalar_b); - g1::element input_g = (g1::element(input_c) * scalar_c); - g1::element input_h = (g1::element(input_d) * scalar_d); - - g1::affine_element expected(input_e + input_f + input_g + input_h); - fq c_x_result(c.x.get_value().lo); - fq c_y_result(c.y.get_value().lo); - - EXPECT_EQ(c_x_result, expected.x); - EXPECT_EQ(c_y_result, expected.y); - } - std::cout << "composer gates = " << composer.get_num_gates() << std::endl; - auto prover = composer.create_prover(); - std::cout << "creating verifier " << std::endl; - auto verifier = composer.create_verifier(); - std::cout << "creating proof " << std::endl; - waffle::plonk_proof proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); + EXPECT_VERIFICATION(composer); } static void test_quad_mul() @@ -391,50 +286,44 @@ template class stdlib_biggroup : public testing::Test { auto composer = Composer("../srs_db/ignition/"); size_t num_repetitions = 1; for (size_t i = 0; i < num_repetitions; ++i) { - g1::affine_element input_a(g1::element::random_element()); - g1::affine_element input_b(g1::element::random_element()); - g1::affine_element input_c(g1::element::random_element()); - g1::affine_element input_d(g1::element::random_element()); + affine_element input_a(element::random_element()); + affine_element input_b(element::random_element()); + affine_element input_c(element::random_element()); + affine_element input_d(element::random_element()); fr scalar_a(fr::random_element()); fr scalar_b(fr::random_element()); fr scalar_c(fr::random_element()); fr scalar_d(fr::random_element()); if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) { - scalar_a -= fr(1); // make a have skew + scalar_a -= fr(1); // skew bit is 1 } if ((uint256_t(scalar_b).get_bit(0) & 1) == 0) { - scalar_b += fr(1); // make b not have skew + scalar_b += fr(1); // skew bit is 0 } - g1_ct P_a = convert_inputs(&composer, input_a); - fr_ct x_a = witness_ct(&composer, scalar_a); - g1_ct P_b = convert_inputs(&composer, input_b); - fr_ct x_b = witness_ct(&composer, scalar_b); - g1_ct P_c = convert_inputs(&composer, input_c); - fr_ct x_c = witness_ct(&composer, scalar_c); - g1_ct P_d = convert_inputs(&composer, input_d); - fr_ct x_d = witness_ct(&composer, scalar_d); - - g1_ct c = g1_ct::batch_mul({ P_a, P_b, P_c, P_d }, { x_a, x_b, x_c, x_d }); - g1::element input_e = (g1::element(input_a) * scalar_a); - g1::element input_f = (g1::element(input_b) * scalar_b); - g1::element input_g = (g1::element(input_c) * scalar_c); - g1::element input_h = (g1::element(input_d) * scalar_d); - - g1::affine_element expected(input_e + input_f + input_g + input_h); + element_ct P_a = element_ct::from_witness(&composer, input_a); + scalar_ct x_a = scalar_ct::from_witness(&composer, scalar_a); + element_ct P_b = element_ct::from_witness(&composer, input_b); + scalar_ct x_b = scalar_ct::from_witness(&composer, scalar_b); + element_ct P_c = element_ct::from_witness(&composer, input_c); + scalar_ct x_c = scalar_ct::from_witness(&composer, scalar_c); + element_ct P_d = element_ct::from_witness(&composer, input_d); + scalar_ct x_d = scalar_ct::from_witness(&composer, scalar_d); + + element_ct c = element_ct::batch_mul({ P_a, P_b, P_c, P_d }, { x_a, x_b, x_c, x_d }); + element input_e = (element(input_a) * scalar_a); + element input_f = (element(input_b) * scalar_b); + element input_g = (element(input_c) * scalar_c); + element input_h = (element(input_d) * scalar_d); + + affine_element expected(input_e + input_f + input_g + input_h); fq c_x_result(c.x.get_value().lo); fq c_y_result(c.y.get_value().lo); EXPECT_EQ(c_x_result, expected.x); EXPECT_EQ(c_y_result, expected.y); } - std::cout << "composer gates = " << composer.get_num_gates() << std::endl; - auto prover = composer.create_prover(); - std::cout << "creating verifier " << std::endl; - auto verifier = composer.create_verifier(); - std::cout << "creating proof " << std::endl; - waffle::plonk_proof proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); + + EXPECT_VERIFICATION(composer); } static void test_one() @@ -444,83 +333,48 @@ template class stdlib_biggroup : public testing::Test { for (size_t i = 0; i < num_repetitions; ++i) { fr scalar_a(fr::random_element()); if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) { - scalar_a -= fr(1); // make a have skew + scalar_a -= fr(1); // skew bit is 1 } - g1_bigfr_ct P_a = g1_bigfr_ct::one(&composer); - bigfr_ct x_a = convert_inputs_bigfr(&composer, scalar_a); - g1_bigfr_ct c = P_a * x_a; - g1::affine_element expected(g1::one * scalar_a); + element_ct P_a = element_ct::one(&composer); + scalar_ct x_a = scalar_ct::from_witness(&composer, scalar_a); + element_ct c = P_a * x_a; + affine_element expected(g1::one * scalar_a); fq c_x_result(c.x.get_value().lo); fq c_y_result(c.y.get_value().lo); EXPECT_EQ(c_x_result, expected.x); EXPECT_EQ(c_y_result, expected.y); } - std::cout << "composer gates = " << composer.get_num_gates() << std::endl; - auto prover = composer.create_prover(); - std::cout << "creating verifier " << std::endl; - auto verifier = composer.create_verifier(); - std::cout << "creating proof " << std::endl; - waffle::plonk_proof proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); - } - - /** TODO (#LARGE_MODULUS_AFFINE_POINT_COMPRESSION): Rewrite this test after designing point compression for p>2^255 - static void test_one_secp256r1() - { - auto composer = Composer("../srs_db/ignition/"); - size_t num_repetitions = 1; - for (size_t i = 0; i < num_repetitions; ++i) { - typename secp256r1::fr scalar_a(secp256r1::fr::random_element()); - if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) { - scalar_a -= secp256r1::fr(1); // make a have skew - } - typename secp256r1_ct::g1_bigfr_ct P_a = secp256r1_ct::g1_bigfr_ct::one(&composer); - typename secp256r1_ct::bigfr_ct x_a = convert_inputs_bigfr_secp256r1(&composer, scalar_a); - typename secp256r1_ct::g1_bigfr_ct c = P_a * x_a; - secp256r1::g1::affine_element expected(secp256r1::g1::one * scalar_a); - secp256r1::fq c_x_result(c.x.get_value().lo); - secp256r1::fq c_y_result(c.y.get_value().lo); - - EXPECT_EQ(c_x_result, expected.x); - EXPECT_EQ(c_y_result, expected.y); - } - std::cout << "composer gates = " << composer.get_num_gates() << std::endl; - auto prover = composer.create_prover(); - std::cout << "creating verifier " << std::endl; - auto verifier = composer.create_verifier(); - std::cout << "creating proof " << std::endl; - waffle::plonk_proof proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); - }**/ + + EXPECT_VERIFICATION(composer); + } static void test_batch_mul() { const size_t num_points = 5; auto composer = Composer("../srs_db/ignition/"); - std::vector points; + std::vector points; std::vector scalars; for (size_t i = 0; i < num_points; ++i) { - points.push_back(g1::affine_element(g1::element::random_element())); + points.push_back(affine_element(element::random_element())); scalars.push_back(fr::random_element()); } - std::vector circuit_points; - std::vector circuit_scalars; + std::vector circuit_points; + std::vector circuit_scalars; for (size_t i = 0; i < num_points; ++i) { - circuit_points.push_back(convert_inputs(&composer, points[i])); - circuit_scalars.push_back(witness_ct(&composer, scalars[i])); + circuit_points.push_back(element_ct::from_witness(&composer, points[i])); + circuit_scalars.push_back(scalar_ct::from_witness(&composer, scalars[i])); } - g1_ct result_point = g1_ct::batch_mul(circuit_points, circuit_scalars); + element_ct result_point = element_ct::batch_mul(circuit_points, circuit_scalars); - g1::element expected_point = g1::one; + element expected_point = g1::one; expected_point.self_set_infinity(); for (size_t i = 0; i < num_points; ++i) { - expected_point += (g1::element(points[i]) * scalars[i]); + expected_point += (element(points[i]) * scalars[i]); } + expected_point = expected_point.normalize(); fq result_x(result_point.x.get_value().lo); fq result_y(result_point.y.get_value().lo); @@ -528,43 +382,156 @@ template class stdlib_biggroup : public testing::Test { EXPECT_EQ(result_x, expected_point.x); EXPECT_EQ(result_y, expected_point.y); - std::cout << "composer gates = " << composer.get_num_gates() << std::endl; - auto prover = composer.create_prover(); - std::cout << "creating verifier " << std::endl; - auto verifier = composer.create_verifier(); - std::cout << "creating proof " << std::endl; - waffle::plonk_proof proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); + EXPECT_VERIFICATION(composer); + } + + static void test_chain_add() + { + Composer composer = Composer(); + size_t num_repetitions = 10; + for (size_t i = 0; i < num_repetitions; ++i) { + affine_element input_a(element::random_element()); + affine_element input_b(element::random_element()); + affine_element input_c(element::random_element()); + + element_ct a = element_ct::from_witness(&composer, input_a); + element_ct b = element_ct::from_witness(&composer, input_b); + element_ct c = element_ct::from_witness(&composer, input_c); + + auto acc = element_ct::chain_add_start(a, b); + auto acc_out = element_ct::chain_add(c, acc); + + auto lambda_prev = (input_b.y - input_a.y) / (input_b.x - input_a.x); + auto x3_prev = lambda_prev * lambda_prev - input_b.x - input_a.x; + auto y3_prev = lambda_prev * (input_a.x - x3_prev) - input_a.y; + auto lambda = (y3_prev - input_c.y) / (x3_prev - input_c.x); + auto x3 = lambda * lambda - x3_prev - input_c.x; + + uint256_t x3_u256 = acc_out.x3_prev.get_value().lo; + uint256_t lambda_u256 = acc_out.lambda_prev.get_value().lo; + + fq x3_result(x3_u256); + fq lambda_result(lambda_u256); + + EXPECT_EQ(x3_result, x3); + EXPECT_EQ(lambda_result, lambda); + } + + EXPECT_VERIFICATION(composer); + } + + static void test_double_montgomery_ladder() + { + Composer composer = Composer(); + size_t num_repetitions = 10; + for (size_t i = 0; i < num_repetitions; ++i) { + affine_element acc_small(element::random_element()); + element_ct acc_big = element_ct::from_witness(&composer, acc_small); + + affine_element add_1_small_0(element::random_element()); + element_ct add_1_big_0 = element_ct::from_witness(&composer, add_1_small_0); + affine_element add_2_small_0(element::random_element()); + element_ct add_2_big_0 = element_ct::from_witness(&composer, add_2_small_0); + + affine_element add_1_small_1(element::random_element()); + element_ct add_1_big_1 = element_ct::from_witness(&composer, add_1_small_1); + affine_element add_2_small_1(element::random_element()); + element_ct add_2_big_1 = element_ct::from_witness(&composer, add_2_small_1); + + typename element_ct::chain_add_accumulator add_1 = element_ct::chain_add_start(add_1_big_0, add_1_big_1); + typename element_ct::chain_add_accumulator add_2 = element_ct::chain_add_start(add_2_big_0, add_2_big_1); + acc_big.double_montgomery_ladder(add_1, add_2); + } + + EXPECT_VERIFICATION(composer); + } + + static void test_compute_naf() + { + Composer composer = Composer(); + size_t num_repetitions(32); + for (size_t i = 0; i < num_repetitions; i++) { + fr scalar_val = fr::random_element(); + scalar_ct scalar = scalar_ct::from_witness(&composer, scalar_val); + auto naf = element_ct::compute_naf(scalar); + // scalar = -naf[254] + \sum_{i=0}^{253}(1-2*naf[i]) 2^{253-i} + fr reconstructed_val(0); + for (size_t i = 0; i < 254; i++) { + reconstructed_val += (fr(1) - fr(2) * fr(naf[i].witness_bool)) * fr(uint256_t(1) << (253 - i)); + }; + reconstructed_val -= fr(naf[254].witness_bool); + EXPECT_EQ(scalar_val, reconstructed_val); + } + EXPECT_VERIFICATION(composer); + } + + static void test_compute_wnaf() + { + Composer composer = Composer(); + + fr scalar_val = fr::random_element(); + scalar_ct scalar = scalar_ct::from_witness(&composer, scalar_val); + element_ct::compute_wnaf(scalar); + + EXPECT_VERIFICATION(composer); + } + + static void test_wnaf_batch_mul() + { + Composer composer = Composer("../srs_db/ignition"); + size_t num_repetitions = 1; + for (size_t i = 0; i < num_repetitions; ++i) { + affine_element input(element::random_element()); + fr scalar(fr::random_element()); + if ((uint256_t(scalar).get_bit(0) & 1) == 1) { + scalar -= fr(1); // make sure to add skew + } + element_ct P = element_ct::from_witness(&composer, input); + scalar_ct x = scalar_ct::from_witness(&composer, scalar); + + std::cerr << "gates before mul " << composer.get_num_gates() << std::endl; + element_ct c = element_ct::wnaf_batch_mul({ P }, { x }); + std::cerr << "composer aftr mul " << composer.get_num_gates() << std::endl; + affine_element c_expected(element(input) * scalar); + + fq c_x_result(c.x.get_value().lo); + fq c_y_result(c.y.get_value().lo); + + EXPECT_EQ(c_x_result, c_expected.x); + EXPECT_EQ(c_y_result, c_expected.y); + } + + EXPECT_VERIFICATION(composer); } static void test_batch_mul_short_scalars() { const size_t num_points = 11; auto composer = Composer("../srs_db/ignition/"); - std::vector points; + std::vector points; std::vector scalars; for (size_t i = 0; i < num_points; ++i) { - points.push_back(g1::affine_element(g1::element::random_element())); + points.push_back(affine_element(element::random_element())); uint256_t scalar_raw = fr::random_element(); scalar_raw.data[2] = 0ULL; scalar_raw.data[3] = 0ULL; scalars.push_back(fr(scalar_raw)); } - std::vector circuit_points; - std::vector circuit_scalars; + std::vector circuit_points; + std::vector circuit_scalars; for (size_t i = 0; i < num_points; ++i) { - circuit_points.push_back(convert_inputs(&composer, points[i])); - circuit_scalars.push_back(witness_ct(&composer, scalars[i])); + circuit_points.push_back(element_ct::from_witness(&composer, points[i])); + circuit_scalars.push_back(scalar_ct::from_witness(&composer, scalars[i])); } - g1_ct result_point = g1_ct::batch_mul(circuit_points, circuit_scalars, 128); + element_ct result_point = element_ct::batch_mul(circuit_points, circuit_scalars, 128); - g1::element expected_point = g1::one; + element expected_point = g1::one; expected_point.self_set_infinity(); for (size_t i = 0; i < num_points; ++i) { - expected_point += (g1::element(points[i]) * scalars[i]); + expected_point += (element(points[i]) * scalars[i]); } + expected_point = expected_point.normalize(); fq result_x(result_point.x.get_value().lo); fq result_y(result_point.y.get_value().lo); @@ -572,61 +539,139 @@ template class stdlib_biggroup : public testing::Test { EXPECT_EQ(result_x, expected_point.x); EXPECT_EQ(result_y, expected_point.y); - std::cout << "composer gates = " << composer.get_num_gates() << std::endl; - auto prover = composer.create_prover(); - std::cout << "creating verifier " << std::endl; - auto verifier = composer.create_verifier(); - std::cout << "creating proof " << std::endl; - waffle::plonk_proof proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); + EXPECT_VERIFICATION(composer); } - static void test_mixed_batch_mul() + static void test_wnaf_batch_mul_128_bit() { - const size_t num_big_points = 10; - const size_t num_small_points = 11; + Composer composer = Composer(); + size_t num_repetitions = 1; + for (size_t i = 0; i < num_repetitions; ++i) { + affine_element input(element::random_element()); + uint256_t scalar_u256(0, 0, 0, 0); + scalar_u256.data[0] = engine.get_random_uint64(); + scalar_u256.data[1] = engine.get_random_uint64(); + fr scalar(scalar_u256); + if ((uint256_t(scalar).get_bit(0) & 1) == 1) { + scalar -= fr(1); // make sure to add skew + } + element_ct P = element_ct::from_witness(&composer, input); + scalar_ct x = scalar_ct::from_witness(&composer, scalar); + + std::cerr << "gates before mul " << composer.get_num_gates() << std::endl; + // Note: need >136 bits to complete this when working over bigfield + element_ct c = element_ct::template wnaf_batch_mul<128>({ P }, { x }); + std::cerr << "composer aftr mul " << composer.get_num_gates() << std::endl; + affine_element c_expected(element(input) * scalar); + + fq c_x_result(c.x.get_value().lo); + fq c_y_result(c.y.get_value().lo); + + EXPECT_EQ(c_x_result, c_expected.x); + EXPECT_EQ(c_y_result, c_expected.y); + } + + EXPECT_VERIFICATION(composer); + } + + static void test_wnaf_batch_4() + { + Composer composer = Composer(); + size_t num_repetitions = 1; + for (size_t i = 0; i < num_repetitions; ++i) { + const auto get_128_bit_scalar = []() { + uint256_t scalar_u256(0, 0, 0, 0); + scalar_u256.data[0] = engine.get_random_uint64(); + scalar_u256.data[1] = engine.get_random_uint64(); + fr scalar(scalar_u256); + return scalar; + }; + affine_element input1(element::random_element()); + affine_element input2(element::random_element()); + affine_element input3(element::random_element()); + affine_element input4(element::random_element()); + + element_ct P1 = element_ct::from_witness(&composer, input1); + element_ct P2 = element_ct::from_witness(&composer, input2); + element_ct P3 = element_ct::from_witness(&composer, input3); + element_ct P4 = element_ct::from_witness(&composer, input4); + + fr scalar1 = get_128_bit_scalar(); + fr scalar2 = get_128_bit_scalar(); + fr scalar3 = get_128_bit_scalar(); + fr scalar4 = get_128_bit_scalar(); + scalar_ct x1 = scalar_ct::from_witness(&composer, scalar1); + scalar_ct x2 = scalar_ct::from_witness(&composer, scalar2); + scalar_ct x3 = scalar_ct::from_witness(&composer, scalar3); + scalar_ct x4 = scalar_ct::from_witness(&composer, scalar4); + + std::cerr << "gates before mul " << composer.get_num_gates() << std::endl; + element_ct c = element_ct::batch_mul({ P1, P2, P3, P4 }, { x1, x2, x3, x4 }, 128); + std::cerr << "composer aftr mul " << composer.get_num_gates() << std::endl; + + element out = input1 * scalar1; + out += (input2 * scalar2); + out += (input3 * scalar3); + out += (input4 * scalar4); + affine_element c_expected(out); + + fq c_x_result(c.x.get_value().lo); + fq c_y_result(c.y.get_value().lo); + + EXPECT_EQ(c_x_result, c_expected.x); + EXPECT_EQ(c_y_result, c_expected.y); + } + + EXPECT_VERIFICATION(composer); + } + + static void test_bn254_endo_batch_mul() + { + const size_t num_big_points = 2; + const size_t num_small_points = 1; auto composer = Composer("../srs_db/ignition/"); - std::vector big_points; + std::vector big_points; std::vector big_scalars; - std::vector small_points; + std::vector small_points; std::vector small_scalars; for (size_t i = 0; i < num_big_points; ++i) { - big_points.push_back(g1::affine_element(g1::element::random_element())); + big_points.push_back(affine_element(element::random_element())); big_scalars.push_back(fr::random_element()); } for (size_t i = 0; i < num_small_points; ++i) { - small_points.push_back(g1::affine_element(g1::element::random_element())); + small_points.push_back(affine_element(element::random_element())); uint256_t scalar_raw = fr::random_element(); scalar_raw.data[2] = 0ULL; scalar_raw.data[3] = 0ULL; small_scalars.push_back(fr(scalar_raw)); } - std::vector big_circuit_points; - std::vector big_circuit_scalars; - std::vector small_circuit_points; - std::vector small_circuit_scalars; + std::vector big_circuit_points; + std::vector big_circuit_scalars; + std::vector small_circuit_points; + std::vector small_circuit_scalars; for (size_t i = 0; i < num_big_points; ++i) { - big_circuit_points.push_back(convert_inputs(&composer, big_points[i])); - big_circuit_scalars.push_back(witness_ct(&composer, big_scalars[i])); + big_circuit_points.push_back(element_ct::from_witness(&composer, big_points[i])); + big_circuit_scalars.push_back(scalar_ct::from_witness(&composer, big_scalars[i])); } for (size_t i = 0; i < num_small_points; ++i) { - small_circuit_points.push_back(convert_inputs(&composer, small_points[i])); - small_circuit_scalars.push_back(witness_ct(&composer, small_scalars[i])); + small_circuit_points.push_back(element_ct::from_witness(&composer, small_points[i])); + small_circuit_scalars.push_back(scalar_ct::from_witness(&composer, small_scalars[i])); } - g1_ct result_point = g1_ct::mixed_batch_mul( + + element_ct result_point = element_ct::bn254_endo_batch_mul( big_circuit_points, big_circuit_scalars, small_circuit_points, small_circuit_scalars, 128); - g1::element expected_point = g1::one; + element expected_point = g1::one; expected_point.self_set_infinity(); for (size_t i = 0; i < num_big_points; ++i) { - expected_point += (g1::element(big_points[i]) * big_scalars[i]); + expected_point += (element(big_points[i]) * big_scalars[i]); } for (size_t i = 0; i < num_small_points; ++i) { - expected_point += (g1::element(small_points[i]) * small_scalars[i]); + expected_point += (element(small_points[i]) * small_scalars[i]); } + expected_point = expected_point.normalize(); fq result_x(result_point.x.get_value().lo); fq result_y(result_point.y.get_value().lo); @@ -634,368 +679,329 @@ template class stdlib_biggroup : public testing::Test { EXPECT_EQ(result_x, expected_point.x); EXPECT_EQ(result_y, expected_point.y); - std::cout << "composer gates = " << composer.get_num_gates() << std::endl; - auto prover = composer.create_prover(); - std::cout << "creating verifier " << std::endl; - auto verifier = composer.create_verifier(); - std::cout << "creating proof " << std::endl; - waffle::plonk_proof proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); + EXPECT_VERIFICATION(composer); + } + + static void test_mixed_mul_bn254_endo() + { + Composer composer = Composer("../srs_db/ignition"); + size_t num_repetitions = 1; + + const auto get_small_scalar = []() { + fr t1 = fr::random_element(); + t1 = t1.from_montgomery_form(); + t1.data[2] = 0; + t1.data[3] = 0; + return t1.to_montgomery_form(); + }; + for (size_t i = 0; i < num_repetitions; ++i) { + std::vector small_points(25); + std::vector big_points(5); + std::vector double_points(11); + std::vector small_scalars(25); + std::vector big_scalars(5); + std::vector double_scalars(11); + + std::vector small_points_w(25); + std::vector big_points_w(5); + std::vector double_points_w(11); + std::vector small_scalars_w(25); + std::vector big_scalars_w(5); + std::vector double_scalars_w(11); + + for (size_t i = 0; i < 25; ++i) { + small_points_w[i] = affine_element(element::random_element()); + small_scalars_w[i] = get_small_scalar(); + small_points[i] = element_ct::from_witness(&composer, small_points_w[i]); + small_scalars[i] = scalar_ct::from_witness(&composer, small_scalars_w[i]); + } + for (size_t i = 0; i < 5; ++i) { + big_points_w[i] = affine_element(element::random_element()); + big_scalars_w[i] = fr::random_element(); + big_points[i] = element_ct::from_witness(&composer, big_points_w[i]); + big_scalars[i] = scalar_ct::from_witness(&composer, big_scalars_w[i]); + } + for (size_t i = 0; i < 11; ++i) { + double_points_w[i] = affine_element(element::random_element()); + double_scalars_w[i] = get_small_scalar(); + double_points[i] = element_ct::from_witness(&composer, double_points_w[i]); + double_scalars[i] = scalar_ct::from_witness(&composer, double_scalars_w[i]); + } + + fr omega = get_small_scalar(); + + const auto double_opening_result = element_ct::batch_mul(double_points, double_scalars, 128); + small_points.push_back(double_opening_result); + small_scalars.push_back(scalar_ct::from_witness(&composer, omega)); + + auto opening_result = + element_ct::bn254_endo_batch_mul(big_points, big_scalars, small_points, small_scalars, 128); + + opening_result = opening_result + double_opening_result; + opening_result = opening_result.normalize(); + + element expected = g1::one; + expected.self_set_infinity(); + for (size_t i = 0; i < 11; ++i) { + expected += (double_points_w[i] * double_scalars_w[i]); + } + expected *= (omega + 1); + for (size_t i = 0; i < 25; ++i) { + expected += (small_points_w[i] * small_scalars_w[i]); + } + for (size_t i = 0; i < 5; ++i) { + expected += (big_points_w[i] * big_scalars_w[i]); + } + expected = expected.normalize(); + + fq result_x(opening_result.x.get_value().lo); + fq result_y(opening_result.y.get_value().lo); + + EXPECT_EQ(result_x, expected.x); + EXPECT_EQ(result_y, expected.y); + } + + EXPECT_VERIFICATION(composer); + }; + + static void test_wnaf_secp256k1() + { + Composer composer = Composer(); + size_t num_repetitions = 1; + for (size_t i = 0; i < num_repetitions; ++i) { + fr scalar_a(fr::random_element()); + if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) { + scalar_a -= fr(1); // skew bit is 1 + } + scalar_ct x_a = scalar_ct::from_witness(&composer, scalar_a); + element_ct::template compute_secp256k1_endo_wnaf<4, 0, 3>(x_a); + element_ct::template compute_secp256k1_endo_wnaf<4, 1, 2>(x_a); + element_ct::template compute_secp256k1_endo_wnaf<4, 2, 1>(x_a); + element_ct::template compute_secp256k1_endo_wnaf<4, 3, 0>(x_a); + } + + EXPECT_VERIFICATION(composer); + } + + static void test_wnaf_8bit_secp256k1() + { + Composer composer = Composer(); + size_t num_repetitions = 1; + for (size_t i = 0; i < num_repetitions; ++i) { + fr scalar_a(fr::random_element()); + if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) { + scalar_a -= fr(1); // skew bit is 1 + } + scalar_ct x_a = scalar_ct::from_witness(&composer, scalar_a); + element_ct::template compute_secp256k1_endo_wnaf<8, 0, 3>(x_a); + element_ct::template compute_secp256k1_endo_wnaf<8, 1, 2>(x_a); + element_ct::template compute_secp256k1_endo_wnaf<8, 2, 1>(x_a); + element_ct::template compute_secp256k1_endo_wnaf<8, 3, 0>(x_a); + } + + EXPECT_VERIFICATION(composer); + } + + static void test_ecdsa_mul_secp256k1() + { + Composer composer = Composer(); + size_t num_repetitions = 1; + for (size_t i = 0; i < num_repetitions; ++i) { + fr scalar_a(fr::random_element()); + fr scalar_b(fr::random_element()); + fr scalar_c(fr::random_element()); + if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) { + scalar_a -= fr(1); // skew bit is 1 + } + element_ct P_a = element_ct::from_witness(&composer, g1::one * scalar_c); + scalar_ct u1 = scalar_ct::from_witness(&composer, scalar_a); + scalar_ct u2 = scalar_ct::from_witness(&composer, scalar_b); + + fr alo; + fr ahi; + fr blo; + fr bhi; + + fr::split_into_endomorphism_scalars(scalar_a.from_montgomery_form(), alo, ahi); + fr::split_into_endomorphism_scalars(scalar_b.from_montgomery_form(), blo, bhi); + + auto output = element_ct::secp256k1_ecdsa_mul(P_a, u1, u2); + + auto expected = affine_element(g1::one * (scalar_c * scalar_b) + g1::one * scalar_a); + EXPECT_EQ(output.x.get_value().lo, uint256_t(expected.x)); + EXPECT_EQ(output.y.get_value().lo, uint256_t(expected.y)); + } + + EXPECT_VERIFICATION(composer); } }; -// Set types for which our typed tests will be built. -typedef testing::Types - ComposerTypes; -// Define test suite -TYPED_TEST_SUITE(stdlib_biggroup, ComposerTypes); +enum UseBigfield { No, Yes }; +typedef testing::Types, UseBigfield::No>, + TestType, UseBigfield::No>, + TestType, UseBigfield::No>, + TestType, UseBigfield::Yes>, + TestType, UseBigfield::Yes>, + TestType, UseBigfield::Yes>, + TestType, UseBigfield::Yes>, + TestType, UseBigfield::Yes>> + TestTypes; + +TYPED_TEST_SUITE(stdlib_biggroup, TestTypes); TYPED_TEST(stdlib_biggroup, add) { TestFixture::test_add(); } - TYPED_TEST(stdlib_biggroup, sub) { TestFixture::test_sub(); } - TYPED_TEST(stdlib_biggroup, dbl) { TestFixture::test_dbl(); } - TYPED_TEST(stdlib_biggroup, montgomery_ladder) { TestFixture::test_montgomery_ladder(); } - HEAVY_TYPED_TEST(stdlib_biggroup, mul) { TestFixture::test_mul(); } - HEAVY_TYPED_TEST(stdlib_biggroup, twin_mul) { TestFixture::test_twin_mul(); } - HEAVY_TYPED_TEST(stdlib_biggroup, triple_mul) { TestFixture::test_triple_mul(); } - -HEAVY_TYPED_TEST(stdlib_biggroup, quad_mul_bigfr) -{ - TestFixture::test_quad_mul_bigfr(); -} - HEAVY_TYPED_TEST(stdlib_biggroup, quad_mul) { TestFixture::test_quad_mul(); } - HEAVY_TYPED_TEST(stdlib_biggroup, one) { TestFixture::test_one(); } +HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul) +{ + TestFixture::test_batch_mul(); +} +HEAVY_TYPED_TEST(stdlib_biggroup, chain_add) +{ + + TestFixture::test_chain_add(); +} +HEAVY_TYPED_TEST(stdlib_biggroup, double_montgomery_ladder) +{ + + TestFixture::test_double_montgomery_ladder(); +} -/** TODO (#LARGE_MODULUS_AFFINE_POINT_COMPRESSION): Rewrite this test after designing point compression for p>2^255 -HEAVY_TYPED_TEST(stdlib_biggroup, one_secp256r1) +HEAVY_TYPED_TEST(stdlib_biggroup, compute_naf) { - TestFixture::test_one_secp256r1(); + // ULTRATODO: make this work for secp curves + if constexpr (TypeParam::Curve::type == waffle::CurveType::BN254) { + size_t num_repetitions = 1; + for (size_t i = 0; i < num_repetitions; i++) { + TestFixture::test_compute_naf(); + } + } else { + GTEST_SKIP(); + } } -**/ -HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul) +/* These tests only work for UltraComposer */ +HEAVY_TYPED_TEST(stdlib_biggroup, wnaf_batch_mul) { - TestFixture::test_batch_mul(); + if constexpr (TypeParam::Curve::Composer::type == waffle::ComposerType::PLOOKUP) { + TestFixture::test_compute_wnaf(); + } else { + GTEST_SKIP(); + } } +/* the following test was only developed as a test of UltraComposer. It fails for Turbo and Standard in the case where + Fr is a bigfield. */ +HEAVY_TYPED_TEST(stdlib_biggroup, compute_wnaf) +{ + if constexpr (TypeParam::Curve::Composer::type != waffle::UltraComposer::type && TypeParam::use_bigfield) { + GTEST_SKIP(); + } else { + TestFixture::test_compute_wnaf(); + } +} + +/* batch_mul with specified value of max_num_bits does not work for a biggroup formed over a big scalar field. + We skip such cases in the next group of tests. */ HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_short_scalars) { - TestFixture::test_batch_mul_short_scalars(); + if constexpr (TypeParam::use_bigfield) { + GTEST_SKIP(); + } else { + TestFixture::test_batch_mul_short_scalars(); + } +} +HEAVY_TYPED_TEST(stdlib_biggroup, wnaf_batch_mul_128_bit) +{ + if constexpr (TypeParam::use_bigfield) { + GTEST_SKIP(); + } else { + TestFixture::test_wnaf_batch_mul_128_bit(); + } +} +HEAVY_TYPED_TEST(stdlib_biggroup, wnaf_batch_4) +{ + if constexpr (TypeParam::use_bigfield) { + GTEST_SKIP(); + } else { + TestFixture::test_wnaf_batch_4(); + } } -HEAVY_TYPED_TEST(stdlib_biggroup, mixed_batch_mul) +/* The following tests are specific to BN254 and don't work when Fr is a bigfield */ +HEAVY_TYPED_TEST(stdlib_biggroup, bn254_endo_batch_mul) { - TestFixture::test_mixed_batch_mul(); + if constexpr (TypeParam::Curve::type == waffle::CurveType::BN254 && !TypeParam::use_bigfield) { + TestFixture::test_bn254_endo_batch_mul(); + } else { + GTEST_SKIP(); + } +} +HEAVY_TYPED_TEST(stdlib_biggroup, mixed_mul_bn254_endo) +{ + if constexpr (TypeParam::Curve::type == waffle::CurveType::BN254 && !TypeParam::use_bigfield) { + TestFixture::test_mixed_mul_bn254_endo(); + } else { + GTEST_SKIP(); + } } -// // REMNANTS OF TESTS OF PLOOKUP - -// Use of the following namespaces is deprecated, having been replaced -// in the above by appropriate use of the methods defined in -// stdlib/primitives/curves/bn254.hpp and stdlib/primitives/curves/sepc256r1.hpp. -// Notes on the replacement: -// - As defined below, plonk::stdlib::bn254 has Fr given by a bigfield, -// while plonk::stdlib::alt_bn254 has Fr given by a field_t. -// Therefore, to update what's below to use -// typedef stdlib::bn254 bn254 -// as defined in our class stdlib_biggroup, one should make the change -// - stdlib::bn254::fr ~> bigfr_ct -// - stdlib::alt_bn254::fr ~> fr_ct -// - stdlib::bn254::g1 ~> g1_bigfr_ct -// - stdlib::alt_bn254::g1 ~> g1_ct -// along with other, more obvious changes. -// -// - Among the tests using Plookup, only test_mul was uncommented at the time -// of the refactor of this document to use TYPED_TESTS's. - -// namespace plonk { -// namespace stdlib { -// namespace bn254 { -// typedef typename plonk::stdlib::bigfield fq; -// // Q:why not use regular fr? -// typedef typename plonk::stdlib::bigfield fr; -// typedef typename plonk::stdlib::element g1; - -// typedef typename plonk::stdlib::bigfield -// plfq; typedef typename plonk::stdlib::bigfield plfr; typedef -// typename plonk::stdlib::element plg1; - -// } // namespace bn254 -// namespace alt_bn254 { -// typedef typename plonk::stdlib::bigfield fq; -// typedef typename plonk::stdlib::field_t fr; -// typedef typename plonk::stdlib::element g1; - -// typedef typename plonk::stdlib::bigfield -// plfq; typedef typename plonk::stdlib::field_t plfr; typedef typename -// plonk::stdlib::element plg1; } // namespace alt_bn254 -// namespace secp256r { -// typedef typename plonk::stdlib::bigfield fq; -// typedef typename plonk::stdlib::bigfield fr; -// typedef typename plonk::stdlib::element g1; - -// } // namespace secp256r - -// } // namespace stdlib -// } // namespace plonk -// typedef stdlib::bool_t bool_t; -// typedef stdlib::field_t field_t; -// typedef stdlib::witness_t witness_t; -// typedef stdlib::public_witness_t public_witness_t; - -// typedef stdlib::bool_t boolpl_t; -// typedef stdlib::field_t fieldpl_t; -// typedef stdlib::witness_t witnesspl_t; -// typedef stdlib::public_witness_t public_witnesspl_t; - -// stdlib::bn254::plg1 convert_inputs(waffle::PlookupComposer* ctx, const g1::affine_element& input) -// { -// uint256_t x_u256(input.x); -// uint256_t y_u256(input.y); - -// stdlib::bn254::plfq x(witnesspl_t(ctx, fr(x_u256.slice(0, stdlib::bn254::plfq::NUM_LIMB_BITS * -// 2))), -// witnesspl_t(ctx, -// fr(x_u256.slice(stdlib::bn254::plfq::NUM_LIMB_BITS * 2, -// stdlib::bn254::plfq::NUM_LIMB_BITS * 4)))); -// stdlib::bn254::plfq y(witnesspl_t(ctx, fr(y_u256.slice(0, stdlib::bn254::plfq::NUM_LIMB_BITS * -// 2))), -// witnesspl_t(ctx, -// fr(y_u256.slice(stdlib::bn254::plfq::NUM_LIMB_BITS * 2, -// stdlib::bn254::plfq::NUM_LIMB_BITS * 4)))); - -// return stdlib::bn254::plg1(x, y); -// } - -// stdlib::alt_bn254::plg1 convert_inputs_alt_bn254(waffle::PlookupComposer* ctx, -// const g1::affine_element& input) -// { -// uint256_t x_u256(input.x); -// uint256_t y_u256(input.y); - -// stdlib::alt_bn254::plfq x( -// witnesspl_t(ctx, fr(x_u256.slice(0, stdlib::alt_bn254::plfq::NUM_LIMB_BITS * 2))), -// witnesspl_t(ctx, -// fr(x_u256.slice(stdlib::alt_bn254::plfq::NUM_LIMB_BITS * 2, -// stdlib::alt_bn254::plfq::NUM_LIMB_BITS * 4)))); -// stdlib::alt_bn254::plfq y( -// witnesspl_t(ctx, fr(y_u256.slice(0, stdlib::alt_bn254::plfq::NUM_LIMB_BITS * 2))), -// witnesspl_t(ctx, -// fr(y_u256.slice(stdlib::alt_bn254::plfq::NUM_LIMB_BITS * 2, -// stdlib::alt_bn254::plfq::NUM_LIMB_BITS * 4)))); - -// return stdlib::alt_bn254::plg1(x, y); -// } - -// stdlib::bn254::plfr convert_inputs(waffle::PlookupComposer* ctx, const fr& scalar) -// { -// uint256_t scalar_u256(scalar); - -// stdlib::bn254::plfr x( -// witnesspl_t(ctx, fr(scalar_u256.slice(0, stdlib::bn254::plfq::NUM_LIMB_BITS * 2))), -// witnesspl_t(ctx, -// fr(scalar_u256.slice(stdlib::bn254::plfq::NUM_LIMB_BITS * 2, -// stdlib::bn254::plfq::NUM_LIMB_BITS * 4)))); - -// return x; -// } - -// HEAVY_TEST(stdlib_biggroup_plookup, test_mul) -// { -// waffle::PlookupComposer composer = waffle::PlookupComposer(); -// size_t num_repetitions = 1; -// for (size_t i = 0; i < num_repetitions; ++i) { -// g1::affine_element input(g1::element::random_element()); -// fr scalar(fr::random_element()); -// if ((scalar.from_montgomery_form().get_bit(0) & 1) == 1) { -// scalar -= fr(1); // make sure to add skew -// } -// stdlib::alt_bn254::plg1 P = convert_inputs_alt_bn254(&composer, input); -// stdlib::alt_bn254::plfr x = witnesspl_t(&composer, scalar); - -// std::cout << "gates before mul " << composer.get_num_gates() << std::endl; -// stdlib::alt_bn254::plg1 c = P * x; -// std::cout << "composer aftr mul " << composer.get_num_gates() << std::endl; -// g1::affine_element c_expected(g1::element(input) * scalar); - -// fq c_x_result(c.x.get_value().lo); -// fq c_y_result(c.y.get_value().lo); - -// EXPECT_EQ(c_x_result, c_expected.x); -// EXPECT_EQ(c_y_result, c_expected.y); -// } -// std::cout << "composer gates = " << composer.get_num_gates() << std::endl; -// composer.process_range_lists(); -// waffle::PlookupProver prover = composer.create_prover(); -// std::cout << "creating verifier " << std::endl; -// waffle::PlookupVerifier verifier = composer.create_verifier(); -// std::cout << "creating proof " << std::endl; -// waffle::plonk_proof proof = prover.construct_proof(); -// bool proof_result = verifier.verify_proof(proof); -// EXPECT_EQ(proof_result, true); -// } - -// // // The remaining tests were commented out. - -// // TEST(stdlib_biggroup_plookup, test_sub) -// // { -// // waffle::PlookupComposer composer = waffle::PlookupComposer(); -// // size_t num_repetitions = 10; -// // for (size_t i = 0; i < num_repetitions; ++i) { -// // g1::affine_element input_a(g1::element::random_element()); -// // g1::affine_element input_b(g1::element::random_element()); - -// // stdlib::bn254::g1 a = convert_inputs(&composer, input_a); -// // stdlib::bn254::g1 b = convert_inputs(&composer, input_b); - -// // stdlib::bn254::g1 c = a - b; - -// // g1::affine_element c_expected(g1::element(input_a) - -// // g1::element(input_b)); - -// // uint256_t c_x_u256 = c.x.get_value().lo; -// // uint256_t c_y_u256 = c.y.get_value().lo; - -// // fq c_x_result(c_x_u256); -// // fq c_y_result(c_y_u256); - -// // EXPECT_EQ(c_x_result, c_expected.x); -// // EXPECT_EQ(c_y_result, c_expected.y); -// // } -// // waffle::PlookupProver prover = composer.create_prover(); -// // waffle::PlookupVerifier verifier = composer.create_verifier(); -// // waffle::plonk_proof proof = prover.construct_proof(); -// // bool proof_result = verifier.verify_proof(proof); -// // EXPECT_EQ(proof_result, true); -// // } - -// // TEST(stdlib_biggroup_plookup, test_dbl) -// // { -// // waffle::PlookupComposer composer = waffle::PlookupComposer(); -// // size_t num_repetitions = 10; -// // for (size_t i = 0; i < num_repetitions; ++i) { -// // g1::affine_element input_a(g1::element::random_element()); - -// // stdlib::bn254::g1 a = convert_inputs(&composer, input_a); - -// // stdlib::bn254::g1 c = a.dbl(); - -// // g1::affine_element c_expected(g1::element(input_a).dbl()); - -// // uint256_t c_x_u256 = c.x.get_value().lo; -// // uint256_t c_y_u256 = c.y.get_value().lo; - -// // fq c_x_result(c_x_u256); -// // fq c_y_result(c_y_u256); - -// // EXPECT_EQ(c_x_result, c_expected.x); -// // EXPECT_EQ(c_y_result, c_expected.y); -// // } -// // waffle::PlookupProver prover = composer.create_prover(); -// // waffle::PlookupVerifier verifier = composer.create_verifier(); -// // waffle::plonk_proof proof = prover.construct_proof(); -// // bool proof_result = verifier.verify_proof(proof); -// // EXPECT_EQ(proof_result, true); -// // } - -// // TEST(stdlib_biggroup_plookup, test_montgomery_ladder) -// // { -// // waffle::PlookupComposer composer = waffle::PlookupComposer(); -// // size_t num_repetitions = 1; -// // for (size_t i = 0; i < num_repetitions; ++i) { -// // g1::affine_element input_a(g1::element::random_element()); -// // g1::affine_element input_b(g1::element::random_element()); - -// // stdlib::bn254::g1 a = convert_inputs(&composer, input_a); -// // stdlib::bn254::g1 b = convert_inputs(&composer, input_b); - -// // stdlib::bn254::g1 c = a.montgomery_ladder(b); - -// // g1::affine_element c_expected(g1::element(input_a).dbl() + -// // g1::element(input_b)); - -// // uint256_t c_x_u256 = c.x.get_value().lo; -// // uint256_t c_y_u256 = c.y.get_value().lo; - -// // fq c_x_result(c_x_u256); -// // fq c_y_result(c_y_u256); - -// // EXPECT_EQ(c_x_result, c_expected.x); -// // EXPECT_EQ(c_y_result, c_expected.y); -// // } -// // waffle::PlookupProver prover = composer.create_prover(); -// // waffle::PlookupVerifier verifier = composer.create_verifier(); -// // waffle::plonk_proof proof = prover.construct_proof(); -// // bool proof_result = verifier.verify_proof(proof); -// // EXPECT_EQ(proof_result, true); -// // } - -// // HEAVY_TEST(stdlib_biggroup_plookup, test_mul) -// // { -// // waffle::PlookupComposer composer = waffle::PlookupComposer(); -// // size_t num_repetitions = 1; -// // for (size_t i = 0; i < num_repetitions; ++i) { -// // g1::affine_element input(g1::element::random_element()); -// // fr scalar(fr::random_element()); -// // if ((scalar.from_montgomery_form().get_bit(0) & 1) == 1) { -// // scalar -= fr(1); // make sure to add skew -// // } -// // stdlib::bn254::g1 P = convert_inputs(&composer, input); -// // stdlib::bn254::fr x = convert_inputs(&composer, scalar); - -// // stdlib::bn254::g1 c = P * x; -// // g1::affine_element c_expected(g1::element(input) * scalar); - -// // fq c_x_result(c.x.get_value().lo); -// // barretenberg::fq c_y_result(c.y.get_value().lo); - -// // EXPECT_EQ(c_x_result, c_expected.x); -// // EXPECT_EQ(c_y_result, c_expected.y); -// // } -// // std::cout << "composer gates = " << composer.get_num_gates() << std::endl; -// // waffle::PlookupProver prover = composer.create_prover(); -// // std::cout << "creating verifier " << std::endl; -// // waffle::PlookupVerifier verifier = composer.create_verifier(); -// // std::cout << "creating proof " << std::endl; -// // waffle::plonk_proof proof = prover.construct_proof(); -// // bool proof_result = verifier.verify_proof(proof); -// // EXPECT_EQ(proof_result, true); -// // } \ No newline at end of file +/* The following tests are specific to SECP256k1 */ +HEAVY_TYPED_TEST(stdlib_biggroup, wnaf_secp256k1) +{ + if constexpr (TypeParam::Curve::type == waffle::CurveType::SECP256K1) { + TestFixture::test_wnaf_secp256k1(); + } else { + GTEST_SKIP(); + } +} +HEAVY_TYPED_TEST(stdlib_biggroup, wnaf_8bit_secp256k1) +{ + if constexpr (TypeParam::Curve::type == waffle::CurveType::SECP256K1) { + TestFixture::test_wnaf_8bit_secp256k1(); + } else { + GTEST_SKIP(); + } +} +HEAVY_TYPED_TEST(stdlib_biggroup, ecdsa_mul_secp256k1) +{ + if constexpr (TypeParam::Curve::type == waffle::CurveType::SECP256K1) { + TestFixture::test_ecdsa_mul_secp256k1(); + } else { + GTEST_SKIP(); + } +} +} // namespace test_stdlib_biggroup \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_batch_mul.hpp b/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_batch_mul.hpp new file mode 100644 index 0000000000..72c6f3fb04 --- /dev/null +++ b/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_batch_mul.hpp @@ -0,0 +1,64 @@ +#pragma once + +namespace plonk { +namespace stdlib { + +/** + * only works for Plookup (otherwise falls back on batch_mul)! Multiscalar multiplication that utilizes 4-bit wNAF + * lookup tables is more efficient than points-as-linear-combinations lookup tables, if the number of points is 3 or + * fewer + */ +template +template +element element::wnaf_batch_mul(const std::vector& points, + const std::vector& scalars) +{ + constexpr size_t WNAF_SIZE = 4; + ASSERT(points.size() == scalars.size()); + if constexpr (C::type != waffle::ComposerType::PLOOKUP) { + return batch_mul(points, scalars, max_num_bits); + } + + std::vector> point_tables; + for (const auto& point : points) { + point_tables.emplace_back(four_bit_table_plookup<>(point)); + } + + std::vector>> wnaf_entries; + for (const auto& scalar : scalars) { + wnaf_entries.emplace_back(compute_wnaf(scalar)); + } + + constexpr size_t num_bits = (max_num_bits == 0) ? (Fr::modulus.get_msb() + 1) : (max_num_bits); + constexpr size_t num_rounds = ((num_bits + WNAF_SIZE - 1) / WNAF_SIZE); + const auto offset_generators = compute_offset_generators(num_rounds * 4 - 3); + + element accumulator = offset_generators.first + point_tables[0][wnaf_entries[0][0]]; + for (size_t i = 1; i < points.size(); ++i) { + accumulator += point_tables[i][wnaf_entries[i][0]]; + } + + for (size_t i = 1; i < num_rounds; ++i) { + accumulator = accumulator.dbl(); + accumulator = accumulator.dbl(); + + element to_add = point_tables[0][wnaf_entries[0][i]]; + for (size_t j = 1; j < points.size(); ++j) { + to_add += point_tables[j][wnaf_entries[j][i]]; + } + // accumulator = accumulator.dbl(); + // accumulator = accumulator.montgomery_ladder(to_add); + accumulator = accumulator.double_into_montgomery_ladder(to_add); + } + + for (size_t i = 0; i < points.size(); ++i) { + element skew = accumulator - points[i]; + Fq out_x = accumulator.x.conditional_select(skew.x, bool_t(wnaf_entries[i][num_rounds])); + Fq out_y = accumulator.y.conditional_select(skew.y, bool_t(wnaf_entries[i][num_rounds])); + accumulator = element(out_x, out_y); + } + accumulator -= offset_generators.second; + return accumulator; +} +} // namespace stdlib +} // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_bn254.hpp b/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_bn254.hpp new file mode 100644 index 0000000000..466b0c2914 --- /dev/null +++ b/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_bn254.hpp @@ -0,0 +1,425 @@ +#pragma once +/** + * Special case function for performing BN254 group operations + * + * TODO: we should try to genericize this, but this method is super fiddly and we need it to be efficient! + * + * We use a special case algorithm to split bn254 scalar multipliers into endomorphism scalars + * + **/ +namespace plonk { +namespace stdlib { + +/** + * Perform a multi-scalar multiplication over the BN254 curve + * + * The inputs are: + * + * `big_scalars/big_points` : 254-bit scalar multipliers (hardcoded to be 4 at the moment) + * `small_scalars/small_points` : 128-bit scalar multipliers + * `generator_scalar` : a 254-bit scalar multiplier over the bn254 generator point + * + **/ +template +template +element element::bn254_endo_batch_mul_with_generator( + const std::vector& big_points, + const std::vector& big_scalars, + const std::vector& small_points, + const std::vector& small_scalars, + const Fr& generator_scalar, + const size_t max_num_small_bits) +{ + C* ctx = nullptr; + for (auto element : big_points) { + if (element.get_context()) { + ctx = element.get_context(); + break; + } + } + if constexpr (C::type != waffle::ComposerType::PLOOKUP) { + // MERGENOTE: these four lines don't have an equivalent in d-b-p + std::vector modified_big_points = big_points; + std::vector modified_big_scalars = big_scalars; + modified_big_points.emplace_back(element::one(ctx)); + modified_big_scalars.emplace_back(generator_scalar); + return bn254_endo_batch_mul( + modified_big_points, modified_big_scalars, small_points, small_scalars, max_num_small_bits); + } else { + constexpr size_t NUM_BIG_POINTS = 4; + // TODO: handle situation where big points size is not 4 :/ + + auto big_table_pair = + create_endo_pair_quad_lookup_table({ big_points[0], big_points[1], big_points[2], big_points[3] }); + auto& big_table = big_table_pair.first; + auto& endo_table = big_table_pair.second; + batch_lookup_table small_table(small_points); + std::vector>> big_naf_entries; + std::vector>> endo_naf_entries; + std::vector>> small_naf_entries; + + const auto split_into_endomorphism_scalars = [ctx](const Fr& scalar) { + barretenberg::fr k = scalar.get_value(); + barretenberg::fr k1(0); + barretenberg::fr k2(0); + barretenberg::fr::split_into_endomorphism_scalars(k.from_montgomery_form(), k1, k2); + Fr scalar_k1 = Fr::from_witness(ctx, k1.to_montgomery_form()); + Fr scalar_k2 = Fr::from_witness(ctx, k2.to_montgomery_form()); + barretenberg::fr beta = barretenberg::fr::cube_root_of_unity(); + scalar.assert_equal(scalar_k1 - scalar_k2 * beta); + return std::make_pair((Fr)scalar_k1, (Fr)scalar_k2); + }; + + for (size_t i = 0; i < NUM_BIG_POINTS; ++i) { + const auto [scalar_k1, scalar_k2] = split_into_endomorphism_scalars(big_scalars[i]); + big_naf_entries.emplace_back(compute_naf(scalar_k1, max_num_small_bits)); + endo_naf_entries.emplace_back(compute_naf(scalar_k2, max_num_small_bits)); + } + + const auto [generator_k1, generator_k2] = split_into_endomorphism_scalars(generator_scalar); + const std::vector> generator_wnaf = compute_wnaf<128, 8>(generator_k1); + const std::vector> generator_endo_wnaf = compute_wnaf<128, 8>(generator_k2); + const auto generator_table = + element::eight_bit_fixed_base_table<>(element::eight_bit_fixed_base_table<>::CurveType::BN254, false); + const auto generator_endo_table = + element::eight_bit_fixed_base_table<>(element::eight_bit_fixed_base_table<>::CurveType::BN254, true); + + for (size_t i = 0; i < small_points.size(); ++i) { + small_naf_entries.emplace_back(compute_naf(small_scalars[i], max_num_small_bits)); + } + + const size_t num_rounds = max_num_small_bits; + + const auto offset_generators = compute_offset_generators(num_rounds); + + auto init_point = element::chain_add(offset_generators.first, small_table.get_chain_initial_entry()); + init_point = element::chain_add(endo_table[0], init_point); + init_point = element::chain_add(big_table[0], init_point); + + element accumulator = element::chain_add_end(init_point); + + const auto get_point_to_add = [&](size_t naf_index) { + std::vector> small_nafs; + std::vector> big_nafs; + std::vector> endo_nafs; + for (size_t i = 0; i < small_points.size(); ++i) { + small_nafs.emplace_back(small_naf_entries[i][naf_index]); + } + for (size_t i = 0; i < NUM_BIG_POINTS; ++i) { + big_nafs.emplace_back(big_naf_entries[i][naf_index]); + endo_nafs.emplace_back(endo_naf_entries[i][naf_index]); + } + + auto to_add = small_table.get_chain_add_accumulator(small_nafs); + to_add = element::chain_add(big_table.get({ big_nafs[0], big_nafs[1], big_nafs[2], big_nafs[3] }), to_add); + to_add = + element::chain_add(endo_table.get({ endo_nafs[0], endo_nafs[1], endo_nafs[2], endo_nafs[3] }), to_add); + return to_add; + }; + + for (size_t i = 1; i < num_rounds / 2; ++i) { + + auto add_1 = get_point_to_add(i * 2 - 1); + auto add_2 = get_point_to_add(i * 2); + + // TODO update this to work if num_bits is odd + if ((i * 2) % 8 == 0) { + add_1 = element::chain_add(generator_table[generator_wnaf[(i * 2 - 8) / 8]], add_1); + add_1 = element::chain_add(generator_endo_table[generator_endo_wnaf[(i * 2 - 8) / 8]], add_1); + } + if (!add_1.is_element) { + accumulator = accumulator.double_montgomery_ladder(add_1, add_2); + } else { + accumulator = accumulator.double_montgomery_ladder(element(add_1.x3_prev, add_1.y3_prev), + element(add_2.x3_prev, add_2.y3_prev)); + } + } + + if ((num_rounds & 0x01ULL) == 0x00ULL) { + auto add_1 = get_point_to_add(num_rounds - 1); + add_1 = element::chain_add(generator_table[generator_wnaf[generator_wnaf.size() - 2]], add_1); + add_1 = element::chain_add(generator_endo_table[generator_endo_wnaf[generator_wnaf.size() - 2]], add_1); + if (add_1.is_element) { + element temp(add_1.x3_prev, add_1.y3_prev); + accumulator = accumulator.montgomery_ladder(temp); + } else { + accumulator = accumulator.montgomery_ladder(add_1); + } + } + + for (size_t i = 0; i < small_points.size(); ++i) { + element skew = accumulator - small_points[i]; + Fq out_x = accumulator.x.conditional_select(skew.x, small_naf_entries[i][num_rounds]); + Fq out_y = accumulator.y.conditional_select(skew.y, small_naf_entries[i][num_rounds]); + accumulator = element(out_x, out_y); + } + + uint256_t beta_val = barretenberg::field::cube_root_of_unity(); + Fq beta(barretenberg::fr(beta_val.slice(0, 136)), barretenberg::fr(beta_val.slice(136, 256)), false); + + for (size_t i = 0; i < NUM_BIG_POINTS; ++i) { + element skew_point = big_points[i]; + skew_point.x *= beta; + element skew = accumulator + skew_point; + Fq out_x = accumulator.x.conditional_select(skew.x, endo_naf_entries[i][num_rounds]); + Fq out_y = accumulator.y.conditional_select(skew.y, endo_naf_entries[i][num_rounds]); + accumulator = element(out_x, out_y); + } + { + element skew = accumulator - generator_table[128]; + Fq out_x = accumulator.x.conditional_select(skew.x, bool_t(generator_wnaf[generator_wnaf.size() - 1])); + Fq out_y = accumulator.y.conditional_select(skew.y, bool_t(generator_wnaf[generator_wnaf.size() - 1])); + accumulator = element(out_x, out_y); + } + { + element skew = accumulator - generator_endo_table[128]; + Fq out_x = + accumulator.x.conditional_select(skew.x, bool_t(generator_endo_wnaf[generator_wnaf.size() - 1])); + Fq out_y = + accumulator.y.conditional_select(skew.y, bool_t(generator_endo_wnaf[generator_wnaf.size() - 1])); + accumulator = element(out_x, out_y); + } + + for (size_t i = 0; i < NUM_BIG_POINTS; ++i) { + element skew = accumulator - big_points[i]; + Fq out_x = accumulator.x.conditional_select(skew.x, big_naf_entries[i][num_rounds]); + Fq out_y = accumulator.y.conditional_select(skew.y, big_naf_entries[i][num_rounds]); + accumulator = element(out_x, out_y); + } + accumulator = accumulator - offset_generators.second; + + return accumulator; + } +} + +/** + * A batch multiplication method for the BN254 curve. This method is only available if Fr == field_t + * + * big_points : group elements we will multiply by full 254-bit scalar multipliers + * big_scalars : 254-bit scalar multipliers. We want to compute (\sum big_scalars[i] * big_points[i]) + * small_points : group elements we will multiply by short scalar mutipliers whose max value will be (1 << + *max_num_small_bits) small_scalars : short scalar mutipliers whose max value will be (1 << max_num_small_bits) + * max_num_small_bits : MINIMUM value must be 128 bits + * (we will be splitting `big_scalars` into two 128-bit scalars, we assume all scalars after this transformation are 128 + *bits) + **/ +template +template +element element::bn254_endo_batch_mul(const std::vector& big_points, + const std::vector& big_scalars, + const std::vector& small_points, + const std::vector& small_scalars, + const size_t max_num_small_bits) +{ + ASSERT(max_num_small_bits >= 128); + const size_t num_big_points = big_points.size(); + const size_t num_small_points = small_points.size(); + C* ctx = nullptr; + for (auto element : big_points) { + if (element.get_context()) { + ctx = element.get_context(); + break; + } + } + + std::vector points; + std::vector scalars; + std::vector endo_points; + std::vector endo_scalars; + + /** + * Split big scalars into short 128-bit scalars. + * + * For `big_scalars` we use the BN254 curve endomorphism to split the scalar into two short 128-bit scalars. + * i.e. for scalar multiplier `k` we derive 128-bit values `k1, k2` where: + * k = k1 - k2 * \lambda + * (\lambda is the cube root of unity modulo the group order of the BN254 curve) + * + * This ensures ALL our scalar multipliers can now be treated as 128-bit scalars, + * which halves the number of iterations of our main "double and add" loop! + */ + barretenberg::fr lambda = barretenberg::fr::cube_root_of_unity(); + barretenberg::fq beta = barretenberg::fq::cube_root_of_unity(); + for (size_t i = 0; i < num_big_points; ++i) { + Fr scalar = big_scalars[i]; + // Q: is it a problem if wraps? get_value is 512 bits + // A: it can't wrap, this method only compiles if the Fr type is a field_t type + + // Split k into short scalars (scalar_k1, scalar_k2) using bn254 endomorphism. + barretenberg::fr k = uint256_t(scalar.get_value()); + barretenberg::fr k1(0); + barretenberg::fr k2(0); + barretenberg::fr::split_into_endomorphism_scalars(k.from_montgomery_form(), k1, k2); + Fr scalar_k1 = Fr::from_witness(ctx, k1.to_montgomery_form()); + Fr scalar_k2 = Fr::from_witness(ctx, k2.to_montgomery_form()); + + // Add copy constraint that validates k1 = scalar_k1 - scalar_k2 * \lambda + scalar.assert_equal(scalar_k1 - scalar_k2 * lambda); + scalars.push_back(scalar_k1); + endo_scalars.push_back(scalar_k2); + element point = big_points[i]; + points.push_back(point); + + // negate the point that maps to the endo scalar `scalar_k2` + // instead of computing scalar_k1 * [P] - scalar_k2 * [P], we compute scalar_k1 * [P] + scalar_k2 * [-P] + point.y = -point.y; + point.x = point.x * Fq(ctx, uint256_t(beta)); + point.y.self_reduce(); + endo_points.push_back(point); + } + for (size_t i = 0; i < num_small_points; ++i) { + points.push_back(small_points[i]); + scalars.push_back(small_scalars[i]); + } + std::copy(endo_points.begin(), endo_points.end(), std::back_inserter(points)); + std::copy(endo_scalars.begin(), endo_scalars.end(), std::back_inserter(scalars)); + + ASSERT(big_scalars.size() == num_big_points); + ASSERT(small_scalars.size() == num_small_points); + + /** + * Compute batch_lookup_table + * + * batch_lookup_table implements a lookup table for a vector of points. + * + * For TurboPlonk, we subdivide `batch_lookup_table` into a set of 3-bit lookup tables, + * (using 2-bit and 1-bit tables if points.size() is not a multiple of 8) + * + * We index the lookup table using a vector of NAF values for each point + * + * e.g. for points P_1, .., P_N and naf values s_1, ..., s_n (where S_i = +1 or -1), + * the lookup table will compute: + * + * \sum_{i=0}^n (s_i ? -P_i : P_i) + **/ + batch_lookup_table point_table(points); + + /** + * Compute scalar multiplier NAFs + * + * A Non Adjacent Form is a representation of an integer where each 'bit' is either +1 OR -1, i.e. each bit entry is + *non-zero. This is VERY useful for biggroup operations, as this removes the need to conditionally add points + *depending on whether the scalar mul bit is +1 or 0 (instead we multiply the y-coordinate by the NAF value, which + *is cheaper) + * + * The vector `naf_entries` tracks the `naf` set for each point, where each `naf` set is a vector of bools + * if `naf[i][j] = 0` this represents a NAF value of -1 + * if `naf[i][j] = 1` this represents a NAF value of +1 + **/ + const size_t num_rounds = max_num_small_bits; + const size_t num_points = points.size(); + std::vector>> naf_entries; + for (size_t i = 0; i < num_points; ++i) { + naf_entries.emplace_back(compute_naf(scalars[i], max_num_small_bits)); + } + + /** + * Initialize accumulator point with an offset generator. See `compute_offset_generators` for detailed explanation + **/ + const auto offset_generators = compute_offset_generators(num_rounds); + + /** + * Get the initial entry of our point table. This is the same as point_table.get_accumulator for the most + *significant NAF entry. HOWEVER, we know the most significant NAF value is +1 because our scalar muls are positive. + * `get_initial_entry` handles this special case as it's cheaper than `point_table.get_accumulator` + **/ + element accumulator = offset_generators.first + point_table.get_initial_entry(); + + /** + * Main "double and add" loop + * + * Each loop iteration traverses TWO bits of our scalar multiplier. Algorithm performs following: + * + * 1. Extract NAF value for bit `2*i - 1` for each scalar multiplier and store in `nafs` vector. + * 2. Use `nafs` vector to derive the point that we need (`add_1`) to add into our accumulator. + * 3. Repeat the above 2 steps but for bit `2 * i` (`add_2`) + * 4. Compute `accumulator = 4 * accumulator + 2 * add_1 + add_2` using `double_montgomery_ladder` method + * + * The purpose of the above is to minimize the number of required range checks (vs a simple double and add algo). + * + * When computing two iterations of the montgomery ladder algorithm, we can neglect computing the y-coordinate of + *the 1st ladder output. See `double_montgomery_ladder` for more details. + **/ + for (size_t i = 1; i < num_rounds / 2; ++i) { + // `nafs` tracks the naf value for each point for the current round + std::vector> nafs; + for (size_t j = 0; j < points.size(); ++j) { + nafs.emplace_back(naf_entries[j][i * 2 - 1]); + } + + /** + * Get `chain_add_accumulator`. + * + * Recovering a point from our point table requires group additions iff the table is >3 bits. + * We can chain repeated add operations together without computing the y-coordinate of intermediate addition + *outputs. + * + * This is represented using the `chain_add_accumulator` type. See the type declaration for more details + * + * (this is cheaper than regular additions iff point_table.get_accumulator require 2 or more point additions. + * Cost is the same as `point_table.get_accumulator` if 1 or 0 point additions are required) + **/ + element::chain_add_accumulator add_1 = point_table.get_chain_add_accumulator(nafs); + for (size_t j = 0; j < points.size(); ++j) { + nafs[j] = (naf_entries[j][i * 2]); + } + element::chain_add_accumulator add_2 = point_table.get_chain_add_accumulator(nafs); + + // Perform the double montgomery ladder. We need to convert our chain_add_accumulator types into regular + // elements if the accumuator does not contain a y-coordinate + if (!add_1.is_element) { + accumulator = accumulator.double_montgomery_ladder(add_1, add_2); + } else { + accumulator = accumulator.double_montgomery_ladder(element(add_1.x3_prev, add_1.y3_prev), + element(add_2.x3_prev, add_2.y3_prev)); + } + } + + // we need to iterate 1 more time if the number of rounds is even + if ((num_rounds & 0x01ULL) == 0x00ULL) { + std::vector> nafs; + for (size_t j = 0; j < points.size(); ++j) { + nafs.emplace_back(naf_entries[j][num_rounds - 1]); + } + element::chain_add_accumulator add_1 = point_table.get_chain_add_accumulator(nafs); + if (add_1.is_element) { + element temp(add_1.x3_prev, add_1.y3_prev); + accumulator = accumulator.montgomery_ladder(temp); + } else { + accumulator = accumulator.montgomery_ladder(add_1); + } + } + + /** + * Handle skew factors. + * + * We represent scalar multipliers via Non Adjacent Form values (NAF). + * In a NAF, each bit value is either -1 or +1. + * We use this representation to avoid having to conditionally add points + * (i.e. every bit we iterate over will result in either a point addition or subtraction, + * instead of conditionally adding a point into an accumulator, + * we conditionally negate the point's y-coordinate and *always* add it into the accumulator) + * + * However! The problem here is that we can only represent odd integers with a NAF. + * For even integers we add +1 to the integer and set that multiplier's `skew` value to `true`. + * + * We record a scalar multiplier's skew value at the end of their NAF values + *(`naf_entries[point_index][num_rounds]`) + * + * If the skew is true, we must subtract the original point from the accumulator. + **/ + for (size_t i = 0; i < num_points; ++i) { + element skew = accumulator - points[i]; + Fq out_x = accumulator.x.conditional_select(skew.x, naf_entries[i][num_rounds]); + Fq out_y = accumulator.y.conditional_select(skew.y, naf_entries[i][num_rounds]); + accumulator = element(out_x, out_y); + } + + // Remove the offset generator point! + accumulator = accumulator - offset_generators.second; + + // Return our scalar mul output + return accumulator; +} +} // namespace stdlib +} // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_impl.hpp b/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_impl.hpp index 893882e566..2a6e7be925 100644 --- a/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_impl.hpp +++ b/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_impl.hpp @@ -1,5 +1,16 @@ #pragma once +#include +#include +#include + +#include "../composers/composers.hpp" + +#include "../bit_array/bit_array.hpp" +// #include "../field/field.hpp" + +using namespace barretenberg; + namespace plonk { namespace stdlib { @@ -66,20 +77,22 @@ element element::operator-(const element& other) con const Fq lambda = Fq::div_without_denominator_check({ other.y, y }, (other.x - x)); const Fq x_3 = lambda.sqradd({ -other.x, -x }); const Fq y_3 = lambda.madd(x_3 - x, { -y }); + return element(x_3, y_3); } - template element element::dbl() const { - Fq T0 = x.sqr(); - Fq T1 = T0 + T0 + T0; + Fq two_x = x + x; if constexpr (G::has_a) { Fq a(get_context(), uint256_t(G::curve_a)); - T1 = T1 + a; + Fq neg_lambda = Fq::msub_div({ x }, { (two_x + x) }, (y + y), { a }); + Fq x_3 = neg_lambda.sqradd({ -(two_x) }); + Fq y_3 = neg_lambda.madd(x_3 - x, { -y }); + return element(x_3, y_3); } - Fq lambda = T1 / (y + y); - Fq x_3 = lambda.sqradd({ -x, -x }); - Fq y_3 = lambda.madd(x - x_3, { -y }); + Fq neg_lambda = Fq::msub_div({ x }, { (two_x + x) }, (y + y), {}); + Fq x_3 = neg_lambda.sqradd({ -(two_x) }); + Fq y_3 = neg_lambda.madd(x_3 - x, { -y }); return element(x_3, y_3); } @@ -95,6 +108,29 @@ template element element * * `chain_add` requires 1 less non-native field reduction than a regular add operation. **/ + +/** + * begin a chain of additions + * input points p1 p2 + * output accumulator = x3_prev (output x coordinate), x1_prev, y1_prev (p1), lambda_prev (y2 - y1) / (x2 - x1) + **/ +template +typename element::chain_add_accumulator element::chain_add_start(const element& p1, + const element& p2) +{ + chain_add_accumulator output; + output.x1_prev = p1.x; + output.y1_prev = p1.y; + + p1.x.assert_is_not_equal(p2.x); + const Fq lambda = Fq::div_without_denominator_check({ p2.y, -p1.y }, (p2.x - p1.x)); + + const Fq x3 = lambda.sqradd({ -p2.x, -p1.x }); + output.x3_prev = x3; + output.lambda_prev = lambda; + return output; +} + template typename element::chain_add_accumulator element::chain_add(const element& p1, const chain_add_accumulator& acc) @@ -136,28 +172,6 @@ typename element::chain_add_accumulator element::cha return output; } -/** - * begin a chain of additions - * input points p1 p2 - * output accumulator = x3_prev (output x coordinate), x1_prev, y1_prev (p1), lambda_prev (y2 - y1) / (x2 - x1) - **/ -template -typename element::chain_add_accumulator element::chain_add_start(const element& p1, - const element& p2) -{ - chain_add_accumulator output; - output.x1_prev = p1.x; - output.y1_prev = p1.y; - - p1.x.assert_is_not_equal(p2.x); - const Fq lambda = Fq::div_without_denominator_check({ p2.y, -p1.y }, (p2.x - p1.x)); - - const Fq x3 = lambda.sqradd({ -p2.x, -p1.x }); - output.x3_prev = x3; - output.lambda_prev = lambda; - return output; -} - /** * End an addition chain. Produces a full output group element with a y-coordinate **/ @@ -173,7 +187,6 @@ element element::chain_add_end(const chain_add_accum Fq y3 = lambda.madd((acc.x1_prev - x3), { -acc.y1_prev }); return element(x3, y3); } - /** * Compute one round of a Montgomery ladder: i.e. compute 2 * (*this) + other * Compute D = A + B + A, where A = `this` and B = `other` @@ -194,6 +207,27 @@ element element::chain_add_end(const chain_add_accum * * Requires 5 non-native field reductions. Doubling and adding would require 6 **/ + +// ################################# +// ### SCALAR MULTIPLICATION METHODS +// ################################# +/** + * Compute D = A + B + A, where A = `this` and B = `other` + * + * We can skip computing the y-coordinate of C = A + B: + * + * To compute D = A + C, A=(x_1,y_1), we need the gradient of our two coordinates, specifically: + * + * + * y_3 - y_1 lambda_1 * (x_1 - x_3) - 2 * y_1 2 * y_1 + * lambda_2 = __________ = ________________________________ = -\lambda_1 - _________ + * x_3 - x_1 x_3 - x_1 x_3 - x_1 + * + * We don't need y_3 to compute this. We can then compute D.x and D.y as usual: + * + * D.x = lambda_2 * lambda_2 - (C.x + A.x) + * D.y = lambda_2 * (A.x - D.x) - A.y + **/ template element element::montgomery_ladder(const element& other) const { @@ -202,7 +236,7 @@ element element::montgomery_ladder(const element& ot const Fq x_3 = lambda_1.sqradd({ -other.x, -x }); - const Fq minus_lambda_2 = lambda_1 + (y + y) / (x_3 - x); + const Fq minus_lambda_2 = lambda_1 + Fq::div_without_denominator_check({ y + y }, (x_3 - x)); const Fq x_4 = minus_lambda_2.sqradd({ -x, -x_3 }); @@ -328,9 +362,8 @@ element element::double_montgomery_ladder(const chai throw_or_abort("An accumulator expected"); } add1.x3_prev.assert_is_not_equal(x); - typename Fq::cached_product cache; Fq lambda_1 = Fq::msub_div( - { add1.lambda_prev }, { (add1.x1_prev - add1.x3_prev) }, (x - add1.x3_prev), { -add1.y1_prev, -y }, cache); + { add1.lambda_prev }, { (add1.x1_prev - add1.x3_prev) }, (x - add1.x3_prev), { -add1.y1_prev, -y }); const Fq x_3 = lambda_1.sqradd({ -add1.x3_prev, -x }); @@ -347,17 +380,14 @@ element element::double_montgomery_ladder(const chai const Fq x_sub_x4 = x - x_4; const Fq x4_sub_add2x = x_4 - add2.x; - typename Fq::cached_product minus_lambda_2_mul_x_sub_4_cache; - const Fq lambda_3 = Fq::msub_div( - { minus_lambda_2 }, { (x_sub_x4) }, (x4_sub_add2x), { y, add2.y }, minus_lambda_2_mul_x_sub_4_cache); + const Fq lambda_3 = Fq::msub_div({ minus_lambda_2 }, { (x_sub_x4) }, (x4_sub_add2x), { y, add2.y }); x_4.assert_is_not_equal(add2.x); const Fq x_5 = lambda_3.sqradd({ -x_4, -add2.x }); const Fq x5_sub_x4 = x_5 - x_4; - const Fq half_minus_lambda_4_minus_lambda_3 = - Fq::msub_div({ minus_lambda_2 }, { x_sub_x4 }, (x5_sub_x4), { y }, minus_lambda_2_mul_x_sub_4_cache); + const Fq half_minus_lambda_4_minus_lambda_3 = Fq::msub_div({ minus_lambda_2 }, { x_sub_x4 }, (x5_sub_x4), { y }); const Fq minus_lambda_4_minus_lambda_3 = half_minus_lambda_4_minus_lambda_3 + half_minus_lambda_4_minus_lambda_3; const Fq minus_lambda_4 = minus_lambda_4_minus_lambda_3 + lambda_3; @@ -365,8 +395,7 @@ element element::double_montgomery_ladder(const chai const Fq x6_sub_x4 = x_6 - x_4; - const Fq y_6 = - Fq::dual_madd(minus_lambda_4, (x6_sub_x4), minus_lambda_2, x_sub_x4, { y }, minus_lambda_2_mul_x_sub_4_cache); + const Fq y_6 = Fq::dual_madd(minus_lambda_4, (x6_sub_x4), minus_lambda_2, x_sub_x4, { y }); return element(x_6, y_6); } @@ -425,7 +454,6 @@ element element::double_montgomery_ladder(const chai return element(x_6, y_6); } - /** * If we chain two iterations of the montgomery ladder together, we can squeeze out a non-native field reduction **/ @@ -433,41 +461,35 @@ template element element::double_into_montgomery_ladder(const element& add1) const { const Fq two_x = x + x; - typename Fq::cached_product temp; Fq x_1; Fq minus_lambda_dbl; if constexpr (G::has_a) { Fq a(get_context(), uint256_t(G::curve_a)); - minus_lambda_dbl = Fq::msub_div({ x }, { (two_x + x) }, (y + y), { a }, temp); + minus_lambda_dbl = Fq::msub_div({ x }, { (two_x + x) }, (y + y), { a }); x_1 = minus_lambda_dbl.sqradd({ -(two_x) }); } else { - minus_lambda_dbl = Fq::msub_div({ x }, { (two_x + x) }, (y + y), {}, temp); + minus_lambda_dbl = Fq::msub_div({ x }, { (two_x + x) }, (y + y), {}); x_1 = minus_lambda_dbl.sqradd({ -(two_x) }); } add1.x.assert_is_not_equal(x_1); const Fq x_minus_x_1 = x - x_1; - typename Fq::cached_product cache; - const Fq lambda_1 = Fq::msub_div({ minus_lambda_dbl }, { x_minus_x_1 }, (x_1 - add1.x), { add1.y, y }, cache); + const Fq lambda_1 = Fq::msub_div({ minus_lambda_dbl }, { x_minus_x_1 }, (x_1 - add1.x), { add1.y, y }); const Fq x_3 = lambda_1.sqradd({ -add1.x, -x_1 }); const Fq half_minus_lambda_2_minus_lambda_1 = - Fq::msub_div({ minus_lambda_dbl }, { x_minus_x_1 }, (x_3 - x_1), { y }, cache); + Fq::msub_div({ minus_lambda_dbl }, { x_minus_x_1 }, (x_3 - x_1), { y }); const Fq minus_lambda_2_minus_lambda_1 = half_minus_lambda_2_minus_lambda_1 + half_minus_lambda_2_minus_lambda_1; const Fq minus_lambda_2 = minus_lambda_2_minus_lambda_1 + lambda_1; const Fq x_4 = minus_lambda_2.sqradd({ -x_1, -x_3 }); - const Fq y_4 = Fq::dual_madd(minus_lambda_2, (x_4 - x_1), minus_lambda_dbl, x_minus_x_1, { y }, cache); + const Fq y_4 = Fq::dual_madd(minus_lambda_2, (x_4 - x_1), minus_lambda_dbl, x_minus_x_1, { y }); return element(x_4, y_4); } -// ################################# -// ### SCALAR MULTIPLICATION METHODS -// ################################# - /** * compute_offset_generators! Let's explain what an offset generator is... * @@ -518,322 +540,11 @@ std::pair, element> element::c return std::make_pair(offset_generator_start, offset_generator_end); } -/** - * Compute the Non Adjacent Form representation of a scalar multiplier. - * - * This method works for both native field elements (e.g. Fr = field_t) AND bigfield elements (e.g. Fr = bigfield) - **/ -template -std::vector> element::compute_naf(const Fr& scalar, const size_t max_num_bits) -{ - - static_assert((Fr::modulus.get_msb() + 1) / 2 < barretenberg::fr::modulus.get_msb()); - C* ctx = scalar.context; - uint512_t scalar_multiplier_512 = uint512_t(uint256_t(scalar.get_value()) % Fr::modulus); - uint256_t scalar_multiplier = scalar_multiplier_512.lo; - - const size_t num_rounds = (max_num_bits == 0) ? Fr::modulus.get_msb() + 1 : max_num_bits; - std::vector> naf_entries(num_rounds + 1); - - // if boolean is false => do NOT flip y - // if boolean is true => DO flip y - // first entry is skew. i.e. do we subtract one from the final result or not - if (scalar_multiplier.get_bit(0) == false) { - // add skew - naf_entries[num_rounds] = bool_t(witness_t(ctx, true)); - scalar_multiplier += uint256_t(1); - } else { - naf_entries[num_rounds] = bool_t(witness_t(ctx, false)); - } - for (size_t i = 0; i < num_rounds - 1; ++i) { - bool next_entry = scalar_multiplier.get_bit(i + 1); - // if the next entry is false, we need to flip the sign of the current entry. i.e. make negative - if (next_entry == false) { - naf_entries[num_rounds - i - 1] = bool_t(witness_t(ctx, true)); // flip sign - } else { - naf_entries[num_rounds - i - 1] = bool_t(witness_t(ctx, false)); // don't flip! - } - } - naf_entries[0] = bool_t(ctx, false); // most significant entry is always true - - // validate correctness of NAF - if constexpr (!Fr::is_composite) { - Fr accumulator(ctx, uint256_t(0)); - const size_t num_even_rounds = (num_rounds >> 1) << 1; - for (size_t i = 0; i < num_even_rounds; i += 2) { - accumulator = accumulator + accumulator; - accumulator = accumulator + accumulator; - Fr hi = Fr(1) - (static_cast(naf_entries[i]) * Fr(2)); - Fr lo = Fr(1) - (static_cast(naf_entries[i + 1]) * Fr(2)); - accumulator = accumulator.add_two(hi + hi, lo); - } - if ((num_rounds & 1UL) == 1UL) { - accumulator = accumulator + accumulator; - - accumulator = accumulator + Fr(1) - (static_cast(naf_entries[num_rounds - 1]) * Fr(2)); - } - accumulator -= field_t(naf_entries[num_rounds]); - accumulator.assert_equal(scalar); - } else { - const auto reconstruct_half_naf = [](bool_t* nafs, const size_t half_round_length) { - // Q: need constraint to start from zero? - field_t negative_accumulator(0); - field_t positive_accumulator(0); - for (size_t i = 0; i < half_round_length; ++i) { - negative_accumulator = negative_accumulator + negative_accumulator + field_t(nafs[i]); - positive_accumulator = - positive_accumulator + positive_accumulator + field_t(1) - field_t(nafs[i]); - } - return std::make_pair(positive_accumulator, negative_accumulator); - }; - const size_t midpoint = num_rounds - Fr::NUM_LIMB_BITS * 2; - auto hi_accumulators = reconstruct_half_naf(&naf_entries[0], midpoint); - auto lo_accumulators = reconstruct_half_naf(&naf_entries[midpoint], num_rounds - midpoint); - - lo_accumulators.second = lo_accumulators.second + field_t(naf_entries[num_rounds]); - - Fr reconstructed_positive = Fr(lo_accumulators.first, hi_accumulators.first); - Fr reconstructed_negative = Fr(lo_accumulators.second, hi_accumulators.second); - Fr accumulator = reconstructed_positive - reconstructed_negative; - accumulator.assert_equal(scalar); - } - return naf_entries; -} - -/** - * A batch multiplication method for the BN254 curve. This method is only available if Fr == field_t - * - * big_points : group elements we will multiply by full 254-bit scalar multipliers - * big_scalars : 254-bit scalar multipliers. We want to compute (\sum big_scalars[i] * big_points[i]) - * small_points : group elements we will multiply by short scalar mutipliers whose max value will be (1 << - *max_num_small_bits) small_scalars : short scalar mutipliers whose max value will be (1 << max_num_small_bits) - * max_num_small_bits : MINIMUM value must be 128 bits - * (we will be splitting `big_scalars` into two 128-bit scalars, we assume all scalars after this transformation are 128 - *bits) - **/ -template -template -element element::bn254_endo_batch_mul(const std::vector& big_points, - const std::vector& big_scalars, - const std::vector& small_points, - const std::vector& small_scalars, - const size_t max_num_small_bits) -{ - ASSERT(max_num_small_bits >= 128); - const size_t num_big_points = big_points.size(); - const size_t num_small_points = small_points.size(); - C* ctx = nullptr; - for (auto element : big_points) { - if (element.get_context()) { - ctx = element.get_context(); - break; - } - } - - std::vector points; - std::vector scalars; - std::vector endo_points; - std::vector endo_scalars; - - /** - * Split big scalars into short 128-bit scalars. - * - * For `big_scalars` we use the BN254 curve endomorphism to split the scalar into two short 128-bit scalars. - * i.e. for scalar multiplier `k` we derive 128-bit values `k1, k2` where: - * k = k1 - k2 * \beta - * (\beta is the cube root of unity modulo the group order of the BN254 curve) - * - * This ensures ALL our scalar multipliers can now be treated as 128-bit scalars, - * which halves the number of iterations of our main "double and add" loop! - */ - for (size_t i = 0; i < num_big_points; ++i) { - Fr scalar = big_scalars[i]; - // Q: is it a problem if wraps? get_value is 512 bits - // A: it can't wrap, this method only compiles if the Fr type is a field_t type - - // Split k into short scalars (scalar_k1, scalar_k2) using bn254 endomorphism. - barretenberg::fr k = uint256_t(scalar.get_value()); - barretenberg::fr k1(0); - barretenberg::fr k2(0); - barretenberg::fr::split_into_endomorphism_scalars(k.from_montgomery_form(), k1, k2); - Fr scalar_k1 = witness_t(ctx, k1.to_montgomery_form()); - Fr scalar_k2 = witness_t(ctx, k2.to_montgomery_form()); - - // Add copy constraint that validates k1 = scalar_k1 - scalar_k2 * \beta - scalar.assert_equal(scalar_k1 - scalar_k2 * barretenberg::fr::beta()); - scalars.push_back(scalar_k1); - endo_scalars.push_back(scalar_k2); - element point = big_points[i]; - points.push_back(point); - - // negate the point that maps to the endo scalar `scalar_k2` - // instead of computing scalar_k1 * [P] - scalar_k2 * [P], we compute scalar_k1 * [P] + scalar_k2 * [-P] - point.y = -point.y; - point.x = point.x * Fq(ctx, uint256_t(barretenberg::fq::beta())); - point.y.self_reduce(); - endo_points.push_back(point); - } - for (size_t i = 0; i < num_small_points; ++i) { - points.push_back(small_points[i]); - scalars.push_back(small_scalars[i]); - } - std::copy(endo_points.begin(), endo_points.end(), std::back_inserter(points)); - std::copy(endo_scalars.begin(), endo_scalars.end(), std::back_inserter(scalars)); - - ASSERT(big_scalars.size() == num_big_points); - ASSERT(small_scalars.size() == num_small_points); - - /** - * Compute batch_lookup_table - * - * batch_lookup_table implements a lookup table for a vector of points. - * - * For TurboPlonk, we subdivide `batch_lookup_table` into a set of 3-bit lookup tables, - * (using 2-bit and 1-bit tables if points.size() is not a multiple of 8) - * - * We index the lookup table using a vector of NAF values for each point - * - * e.g. for points P_1, .., P_N and naf values s_1, ..., s_n (where S_i = +1 or -1), - * the lookup table will compute: - * - * \sum_{i=0}^n (s_i ? -P_i : P_i) - **/ - batch_lookup_table point_table(points); - - /** - * Compute scalar multiplier NAFs - * - * A Non Adjacent Form is a representation of an integer where each 'bit' is either +1 OR -1, i.e. each bit entry is - *non-zero. This is VERY useful for biggroup operations, as this removes the need to conditionally add points - *depending on whether the scalar mul bit is +1 or 0 (instead we multiply the y-coordinate by the NAF value, which - *is cheaper) - * - * The vector `naf_entries` tracks the `naf` set for each point, where each `naf` set is a vector of bools - * if `naf[i][j] = 0` this represents a NAF value of -1 - * if `naf[i][j] = 1` this represents a NAF value of +1 - **/ - const size_t num_rounds = max_num_small_bits; - const size_t num_points = points.size(); - std::vector>> naf_entries; - for (size_t i = 0; i < num_points; ++i) { - naf_entries.emplace_back(compute_naf(scalars[i], max_num_small_bits)); - } - - /** - * Initialize accumulator point with an offset generator. See `compute_offset_generators` for detailed explanation - **/ - const auto offset_generators = compute_offset_generators(num_rounds); - element accumulator = offset_generators.first; - - /** - * Get the initial entry of our point table. This is the same as point_table.get_accumulator for the most - *significant NAF entry. HOWEVER, we know the most significant NAF value is +1 because our scalar muls are positive. - * `get_initial_entry` handles this special case as it's cheaper than `point_table.get_accumulator` - **/ - accumulator = accumulator + point_table.get_initial_entry(); - - /** - * Main "double and add" loop - * - * Each loop iteration traverses TWO bits of our scalar multiplier. Algorithm performs following: - * - * 1. Extract NAF value for bit `2*i - 1` for each scalar multiplier and store in `nafs` vector. - * 2. Use `nafs` vector to derive the point that we need (`add_1`) to add into our accumulator. - * 3. Repeat the above 2 steps but for bit `2 * i` (`add_2`) - * 4. Compute `accumulator = 4 * accumulator + 2 * add_1 + add_2` using `double_montgomery_ladder` method - * - * The purpose of the above is to minimize the number of required range checks (vs a simple double and add algo). - * - * When computing two iterations of the montgomery ladder algorithm, we can neglect computing the y-coordinate of - *the 1st ladder output. See `double_montgomery_ladder` for more details. - **/ - for (size_t i = 1; i < num_rounds / 2; ++i) { - // `nafs` tracks the naf value for each point for the current round - std::vector> nafs; - for (size_t j = 0; j < points.size(); ++j) { - nafs.emplace_back(naf_entries[j][i * 2 - 1]); - } - - /** - * Get `chain_add_accumulator`. - * - * Recovering a point from our point table requires group additions iff the table is >3 bits. - * We can chain repeated add operations together without computing the y-coordinate of intermediate addition - *outputs. - * - * This is represented using the `chain_add_accumulator` type. See the type declaration for more details - * - * (this is cheaper than regular additions iff point_table.get_accumulator require 2 or more point additions. - * Cost is the same as `point_table.get_accumulator` if 1 or 0 point additions are required) - **/ - element::chain_add_accumulator add_1 = point_table.get_chain_add_accumulator(nafs); - for (size_t j = 0; j < points.size(); ++j) { - nafs[j] = (naf_entries[j][i * 2]); - } - element::chain_add_accumulator add_2 = point_table.get_chain_add_accumulator(nafs); - - // Perform the double montgomery ladder. We need to convert our chain_add_accumulator types into regular - // elements if the accumuator does not contain a y-coordinate - if (!add_1.is_element) { - accumulator = accumulator.double_montgomery_ladder(add_1, add_2); - } else { - accumulator = accumulator.double_montgomery_ladder(element(add_1.x3_prev, add_1.y3_prev), - element(add_2.x3_prev, add_2.y3_prev)); - } - } - - // we need to iterate 1 more time if the number of rounds is even - if ((num_rounds & 0x01ULL) == 0x00ULL) { - std::vector> nafs; - for (size_t j = 0; j < points.size(); ++j) { - nafs.emplace_back(naf_entries[j][num_rounds - 1]); - } - element::chain_add_accumulator add_1 = point_table.get_chain_add_accumulator(nafs); - if (add_1.is_element) { - element temp(add_1.x3_prev, add_1.y3_prev); - accumulator = accumulator.montgomery_ladder(temp); - } else { - accumulator = accumulator.montgomery_ladder(add_1); - } - } - - /** - * Handle skew factors. - * - * We represent scalar multipliers via Non Adjacent Form values (NAF). - * In a NAF, each bit value is either -1 or +1. - * We use this representation to avoid having to conditionally add points - * (i.e. every bit we iterate over will result in either a point addition or subtraction, - * instead of conditionally adding a point into an accumulator, - * we conditionally negate the point's y-coordinate and *always* add it into the accumulator) - * - * However! The problem here is that we can only represent odd integers with a NAF. - * For even integers we add +1 to the integer and set that multiplier's `skew` value to `true`. - * - * We record a scalar multiplier's skew value at the end of their NAF values - *(`naf_entries[point_index][num_rounds]`) - * - * If the skew is true, we must subtract the original point from the accumulator. - **/ - for (size_t i = 0; i < num_points; ++i) { - element skew = accumulator - points[i]; - Fq out_x = accumulator.x.conditional_select(skew.x, naf_entries[i][num_rounds]); - Fq out_y = accumulator.y.conditional_select(skew.y, naf_entries[i][num_rounds]); - accumulator = element(out_x, out_y); - } - - // Remove the offset generator point! - accumulator = accumulator - offset_generators.second; - - // Return our scalar mul output - return accumulator; -} - /** * Generic batch multiplication that works for all elliptic curve types. * * Implementation is identical to `bn254_endo_batch_mul` but WITHOUT the endomorphism transforms OR support for short - *scalars See `bn254_endo_batch_mul` for description of algorithm + * scalars See `bn254_endo_batch_mul` for description of algorithm **/ template element element::batch_mul(const std::vector& points, @@ -895,104 +606,6 @@ element element::batch_mul(const std::vector -element element::mixed_batch_mul(const std::vector& big_points, - const std::vector& big_scalars, - const std::vector& small_points, - const std::vector& small_scalars, - const size_t max_num_small_bits) -{ - if constexpr (!G::USE_ENDOMORPHISM || Fr::is_composite) { - std::vector points(big_points.begin(), big_points.end()); - std::copy(small_points.begin(), small_points.end(), std::back_inserter(big_points)); - std::vector scalars(big_scalars.begin(), big_scalars.end()); - std::copy(small_scalars.begin(), small_scalars.end(), std::back_inserter(big_scalars)); - return batch_mul(points, scalars); - } - const size_t num_big_points = big_points.size(); - const size_t num_small_points = small_points.size(); - C* ctx = nullptr; - for (auto element : big_points) { - if (element.get_context()) { - ctx = element.get_context(); - break; - } - } - - std::vector points; - std::vector scalars; - std::vector endo_points; - std::vector endo_scalars; - for (size_t i = 0; i < num_big_points; ++i) { - Fr scalar = big_scalars[i]; - // Q:is it a problem if wraps? get_value is 512 bits - barretenberg::fr k = scalar.get_value(); - barretenberg::fr k1(0); - barretenberg::fr k2(0); - barretenberg::fr::split_into_endomorphism_scalars(k.from_montgomery_form(), k1, k2); - Fr scalar_k1 = witness_t( - ctx, k1.to_montgomery_form()); // Q:seems we are assuming barret fr and template Fr are same field - Fr scalar_k2 = witness_t(ctx, k2.to_montgomery_form()); - scalar.assert_equal(scalar_k1 - scalar_k2 * barretenberg::fr::beta(), - "biggroup endormorphism scalar split fail?"); - scalars.push_back(scalar_k1); - endo_scalars.push_back(scalar_k2); - element point = big_points[i]; - points.push_back(point); - point.y = -point.y; - point.x = point.x * Fq(ctx, uint256_t(barretenberg::fq::beta())); - point.y.self_reduce(); - endo_points.push_back(point); - } - for (size_t i = 0; i < num_small_points; ++i) { - points.push_back(small_points[i]); - scalars.push_back(small_scalars[i]); - } - std::copy(endo_points.begin(), endo_points.end(), std::back_inserter(points)); - std::copy(endo_scalars.begin(), endo_scalars.end(), std::back_inserter(scalars)); - - ASSERT(big_scalars.size() == num_big_points); - ASSERT(small_scalars.size() == num_small_points); - - batch_lookup_table point_table(points); - - const size_t num_rounds = max_num_small_bits; - const size_t num_points = points.size(); - std::vector>> naf_entries; - for (size_t i = 0; i < num_points; ++i) { - naf_entries.emplace_back(compute_naf(scalars[i], max_num_small_bits)); - } - const auto offset_generators = compute_offset_generators(num_rounds); - - element accumulator = offset_generators.first + point_table.get_initial_entry(); - - for (size_t i = 1; i < num_rounds; ++i) { - std::vector> nafs; - for (size_t j = 0; j < points.size(); ++j) { - nafs.emplace_back(naf_entries[j][i]); - } - - element to_add = point_table.get(nafs); - - accumulator = accumulator.montgomery_ladder(to_add); - } - for (size_t i = 0; i < num_points; ++i) { - element skew = accumulator - points[i]; - Fq out_x = accumulator.x.conditional_select(skew.x, naf_entries[i][num_rounds]); - Fq out_y = accumulator.y.conditional_select(skew.y, naf_entries[i][num_rounds]); - accumulator = element(out_x, out_y); - } - - accumulator = accumulator - offset_generators.second; - return accumulator; -} - /** * Implements scalar multiplication. * @@ -1046,6 +659,5 @@ element element::operator*(const Fr& scalar) const return element(out_x, out_y) - element(offset_generators.second); } - } // namespace stdlib } // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_nafs.hpp b/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_nafs.hpp new file mode 100644 index 0000000000..c6c38cf045 --- /dev/null +++ b/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_nafs.hpp @@ -0,0 +1,500 @@ +#pragma once +#include + +namespace plonk { +namespace stdlib { + +/** + * Split a secp256k1 Fr element into two 129 bit scalars `klo, khi`, where `scalar = klo + \lambda * khi mod n` + * where `\lambda` is the cube root of unity mod n, and `n` is the secp256k1 Fr modulus + * + * We return the wnaf representation of the two 129-bit scalars + * + * The wnaf representation includes `positive_skew` and `negative_skew` components, + * because for both `klo, khi` EITHER `k < 2^{129}` OR `-k mod n < 2^{129}`. + * If we have to negate the short scalar, the wnaf skew component flips sign. + * + * Outline of algorithm: + * + * We will use our wnaf elements to index a ROM table. ROM index values act like regular array indices, + * i.e. start at 0, increase by 1 per index. + * We need the wnaf format to follow the same structure. + * + * The mapping from wnaf value to lookup table point is as follows (example is 4-bit WNAF): + * + * | wnaf witness value | wnaf real value | point representation | + * |--------------------|-----------------|----------------------| + * | 0 | -15 | -15.[P] | + * | 1 | -13 | -13.[P] | + * | 2 | -11 | -11.[P] | + * | 3 | -9 | -9.[P] | + * | 4 | -7 | -7.[P] | + * | 5 | -5 | -5.[P] | + * | 6 | -3 | -3.[P] | + * | 7 | -1 | -1.[P] | + * | 8 | 1 | 1.[P] | + * | 9 | 3 | 3.[P] | + * | 10 | 5 | 5.[P] | + * | 11 | 7 | 7.[P] | + * | 12 | 9 | 9.[P] | + * | 13 | 11 | 11.[P] | + * | 14 | 13 | 13.[P] | + * | 15 | 15 | 15.[P] | + * |--------------------|-----------------|----------------------| + * + * The transformation between the wnaf witness value `w` and the wnaf real value `v` is, for an `s`-bit window: + * + * s + * v = 2.w - (2 - 1) + * + * To reconstruct the 129-bit scalar multiplier `x` from wnaf values `w` (starting with most significant slice): + * + * m + * ___ + * \ / s \ s.(m - i - 1) + * x = positive_skew - negative_skew + | | 2.w - (2 - 1) | . 2 + * /___ \ i / + * i=0 + * + * N.B. `m` = number of rounds = (129 + s - 1) / s + * + * We can split the RHS into positive and negative components that are strictly positive: + * + * m + * ___ + * \ / \ s.(m - i - 1) + * x_pos = positive_skew + | |2.w | . 2 + * /___ \ i/ + * i=0 + * + * m + * ___ + * \ / s \ s.(m - i - 1) + * x_neg = negative_skew + | |(2 - 1)| . 2 + * /___ \ / + * i=0 + * + * By independently constructing `x_pos`, `x_neg`, we ensure we never underflow the native circuit modulus + * + * To reconstruct our wnaf components into a scalar, we perform the following (for each 129-bit slice klo, khi): + * + * 1. Compute the wnaf entries and range constrain each entry to be < 2^s + * 2. Construct `x_pos` + * 3. Construct `x_neg` + * 4. Cast `x_pos, x_neg` into two Fr elements and compute `Fr reconstructed = Fr(x_pos) - Fr(x_neg)` + * + * This ensures that the only negation in performed in the Fr representation, removing the risk of underflow errors + * + * Once `klo, khi` have been reconstructed as Fr elements, we validate the following: + * + * 1. `scalar == Fr(klo) - Fr(khi) * Fr(\lambda) + * + * Finally, we return the wnaf representations of klo, khi including the skew + **/ +template +template +typename element::secp256k1_wnaf_pair element::compute_secp256k1_endo_wnaf(const Fr& scalar) +{ + /** + * The staggered offset describes the number of bits we want to remove from the input scalar before computing our + * wnaf slices. This is to enable us to make repeated calls to the montgomery ladder algo when computing a + * multi-scalar multiplication e.g. Consider an example with 2 points (A, B), using a 2-bit WNAF The typical + * approach would be to perfomr a double-and-add algorithm, adding points into an accumulator ACC: + * + * ACC = ACC.dbl() + * ACC = ACC.dbl() + * ACC = ACC.add(A) + * ACC = ACC.add(B) + * + * However, if the A and B WNAFs are offset by 1 bit each, we can perform the following: + * + * ACC = ACC.dbl() + * ACC = ACC.add(A) + * ACC = ACC.dbl() + * ACC = ACC.add(B) + * + * which we can reduce to: + * + * ACC = ACC.montgomery_ladder(A) + * ACC = ACC.montgomery_ladder(B) + * + * This is more efficient than the non-staggered approach as we save 1 non-native field multiplication when we + * replace a DBL, ADD subroutine with a call to the montgomery ladder + */ + C* ctx = scalar.context; + + constexpr size_t num_bits = 129; + + const auto compute_single_wnaf = + [ctx](const secp256k1::fr& k, const auto stagger, const bool is_negative, const bool is_lo = false) { + constexpr size_t num_rounds = ((num_bits + wnaf_size - 1) / wnaf_size); + const uint64_t stagger_mask = (1ULL << stagger) - 1; + const uint64_t stagger_scalar = k.data[0] & stagger_mask; + + uint64_t wnaf_values[num_rounds] = { 0 }; + bool skew_without_stagger; + uint256_t k_u256{ k.data[0], k.data[1], k.data[2], k.data[3] }; + k_u256 = k_u256 >> stagger; + if (is_lo) { + barretenberg::wnaf::fixed_wnaf( + &k_u256.data[0], &wnaf_values[0], skew_without_stagger, 0); + } else { + barretenberg::wnaf::fixed_wnaf( + &k_u256.data[0], &wnaf_values[0], skew_without_stagger, 0); + } + const size_t num_rounds_adjusted = ((num_bits + wnaf_size - 1 - stagger) / wnaf_size); + + const auto compute_staggered_wnaf_fragment = + [](const uint64_t fragment_u64, const uint64_t stagger, bool is_negative, bool wnaf_skew) { + if (stagger == 0) { + return std::make_pair((uint64_t)0, (bool)wnaf_skew); + } + int fragment = static_cast(fragment_u64); + + if (is_negative) { + fragment = -fragment; + } + if (!is_negative && wnaf_skew) { + fragment -= (1 << stagger); + } else if (is_negative && wnaf_skew) { + fragment += (1 << stagger); + } + bool output_skew = (fragment_u64 % 2) == 0; + if (!is_negative && output_skew) { + fragment += 1; + } else if (is_negative && output_skew) { + fragment -= 1; + } + + uint64_t output_fragment; + if (fragment < 0) { + output_fragment = static_cast((int)((1ULL << (wnaf_size - 1))) + (fragment / 2 - 1)); + } else { + output_fragment = static_cast((1ULL << (wnaf_size - 1)) - 1ULL + + (uint64_t)((uint64_t)fragment / 2 + 1)); + } + + return std::make_pair((uint64_t)output_fragment, (bool)output_skew); + }; + + const auto [first_fragment, skew] = + compute_staggered_wnaf_fragment(stagger_scalar, stagger, is_negative, skew_without_stagger); + + constexpr uint64_t wnaf_window_size = (1ULL << (wnaf_size - 1)); + const auto get_wnaf_wires = [ctx](uint64_t* wnaf_values, bool is_negative, size_t rounds) { + std::vector> wnaf_entries; + for (size_t i = 0; i < rounds; ++i) { + bool predicate = bool((wnaf_values[i] >> 31U) & 1U); + uint64_t offset_entry; + if ((!predicate && !is_negative) || (predicate && is_negative)) { + offset_entry = wnaf_window_size + (wnaf_values[i] & 0xffffff); + } else { + offset_entry = wnaf_window_size - 1 - (wnaf_values[i] & 0xffffff); + } + field_t entry(witness_t(ctx, offset_entry)); + + // TODO: Do these need to be range constrained? we use these witnesses + // to index a size-16 ROM lookup table, which performs an implicit range constraint + entry.create_range_constraint(wnaf_size); + wnaf_entries.emplace_back(entry); + } + return wnaf_entries; + }; + + std::vector> wnaf = get_wnaf_wires(&wnaf_values[0], is_negative, num_rounds_adjusted); + field_t negative_skew = witness_t(ctx, is_negative ? 0 : skew); + field_t positive_skew = witness_t(ctx, is_negative ? skew : 0); + negative_skew.create_range_constraint(1); + positive_skew.create_range_constraint(1); + (negative_skew + positive_skew).create_range_constraint(1); + + const auto reconstruct_bigfield_from_wnaf = [ctx](const std::vector>& wnaf, + const field_t& positive_skew, + const field_t& stagger_fragment, + const size_t stagger, + const size_t rounds) { + std::vector> accumulator; + for (size_t i = 0; i < rounds; ++i) { + field_t entry = wnaf[rounds - 1 - i]; + entry *= 2; + entry *= static_cast>(uint256_t(1) << (i * wnaf_size)); + accumulator.emplace_back(entry); + } + field_t sum = field_t::accumulate(accumulator); + sum = sum * field_t(barretenberg::fr(1ULL << stagger)); + sum += (stagger_fragment * 2); + sum += positive_skew; + sum = sum.normalize(); + // TODO: improve efficiency by creating a constructor that does NOT require us to range constrain + // limbs (we already know (sum < 2^{130})) + Fr reconstructed = Fr(sum, field_t::from_witness_index(ctx, ctx->zero_idx), false); + return reconstructed; + }; + + field_t stagger_fragment = witness_t(ctx, first_fragment); + Fr wnaf_sum = + reconstruct_bigfield_from_wnaf(wnaf, positive_skew, stagger_fragment, stagger, num_rounds_adjusted); + + uint256_t negative_constant_wnaf_offset(0); + + for (size_t i = 0; i < num_rounds_adjusted; ++i) { + negative_constant_wnaf_offset += + uint256_t(wnaf_window_size * 2 - 1) * (uint256_t(1) << (i * wnaf_size)); + } + negative_constant_wnaf_offset = negative_constant_wnaf_offset << stagger; + if (stagger > 0) { + negative_constant_wnaf_offset += ((1ULL << wnaf_size) - 1ULL); // FROM STAGGER FRAMGENT + } + field_t skew_offset = + (negative_skew + field_t(barretenberg::fr(negative_constant_wnaf_offset))).normalize(); + + // TODO: improve efficiency by removing range constraint on lo_offset and hi_offset (we already know are + // boolean) + Fr offset = Fr(skew_offset, field_t(ctx, barretenberg::fr(0)), false); + + Fr reconstructed = wnaf_sum - offset; + + secp256k1_wnaf wnaf_out{ .wnaf = wnaf, + .positive_skew = positive_skew, + .negative_skew = negative_skew, + .least_significant_wnaf_fragment = stagger_fragment, + .has_wnaf_fragment = (stagger > 0) }; + + return std::make_pair((Fr)reconstructed, (secp256k1_wnaf)wnaf_out); + }; + + secp256k1::fr k(scalar.get_value().lo); + secp256k1::fr klo(0); + secp256k1::fr khi(0); + bool klo_negative = false; + bool khi_negative = false; + secp256k1::fr::split_into_endomorphism_scalars(k.from_montgomery_form(), klo, khi); + + /* AUDITNOTE: it has been observed in testing that klo_negative is always false. + On the other hand, khi_negative is sometimes true (e.g., in test_wnaf_secp256k1, take + scalar_a = 0x3e3e7e9628094ee8942358f6daa1130790f5165d55705d83dad745c85f36807a). So it may be + that this block is not needed. I could not quickly determine why this might be the case, + so I leave it to the auditor to check whether the following if block is needed. */ + if (klo.uint256_t_no_montgomery_conversion().get_msb() > 129) { + klo_negative = true; + klo = -klo; + } + if (khi.uint256_t_no_montgomery_conversion().get_msb() > 129) { + khi_negative = true; + khi = -khi; + } + + const auto [klo_reconstructed, klo_out] = compute_single_wnaf(klo, lo_stagger, klo_negative, true); + const auto [khi_reconstructed, khi_out] = compute_single_wnaf(khi, hi_stagger, khi_negative, false); + + uint256_t minus_lambda_val(-secp256k1::fr::cube_root_of_unity()); + Fr minus_lambda( + barretenberg::fr(minus_lambda_val.slice(0, 136)), barretenberg::fr(minus_lambda_val.slice(136, 256)), false); + + Fr reconstructed_scalar = khi_reconstructed.madd(minus_lambda, { klo_reconstructed }); + + if (reconstructed_scalar.get_value() != scalar.get_value()) { + std::cerr << "biggroup_nafs: secp256k1 reconstructed wnaf does not match input! " << reconstructed_scalar + << " vs " << scalar << std::endl; + } + scalar.binary_basis_limbs[0].element.assert_equal(reconstructed_scalar.binary_basis_limbs[0].element); + scalar.binary_basis_limbs[1].element.assert_equal(reconstructed_scalar.binary_basis_limbs[1].element); + scalar.binary_basis_limbs[2].element.assert_equal(reconstructed_scalar.binary_basis_limbs[2].element); + scalar.binary_basis_limbs[3].element.assert_equal(reconstructed_scalar.binary_basis_limbs[3].element); + scalar.prime_basis_limb.assert_equal(reconstructed_scalar.prime_basis_limb); + + return { .klo = klo_out, .khi = khi_out }; +} + +template +template +std::vector> element::compute_wnaf(const Fr& scalar) +{ + C* ctx = scalar.context; + uint512_t scalar_multiplier_512 = uint512_t(uint256_t(scalar.get_value()) % Fr::modulus); + uint256_t scalar_multiplier = scalar_multiplier_512.lo; + + constexpr size_t num_bits = (max_num_bits == 0) ? (Fr::modulus.get_msb() + 1) : (max_num_bits); + constexpr size_t num_rounds = ((num_bits + WNAF_SIZE - 1) / WNAF_SIZE); + + uint64_t wnaf_values[num_rounds] = { 0 }; + bool skew = false; + barretenberg::wnaf::fixed_wnaf(&scalar_multiplier.data[0], &wnaf_values[0], skew, 0); + + std::vector> wnaf_entries; + for (size_t i = 0; i < num_rounds; ++i) { + bool predicate = bool((wnaf_values[i] >> 31U) & 1U); + uint64_t offset_entry; + if (!predicate) { + offset_entry = (1ULL << (WNAF_SIZE - 1)) + (wnaf_values[i] & 0xffffff); + } else { + offset_entry = (1ULL << (WNAF_SIZE - 1)) - 1 - (wnaf_values[i] & 0xffffff); + } + field_t entry(witness_t(ctx, offset_entry)); + + entry.create_range_constraint(WNAF_SIZE); + wnaf_entries.emplace_back(entry); + } + + // add skew + wnaf_entries.emplace_back(witness_t(ctx, skew)); + wnaf_entries[wnaf_entries.size() - 1].create_range_constraint(1); + + // TODO: VALIDATE SUM DOES NOT OVERFLOW P + + // validate correctness of wNAF + if constexpr (!Fr::is_composite) { + std::vector accumulators; + for (size_t i = 0; i < num_rounds; ++i) { + Fr entry = wnaf_entries[wnaf_entries.size() - 2 - i]; + entry *= 2; + // entry -= 15; + entry *= static_cast(uint256_t(1) << (i * WNAF_SIZE)); + accumulators.emplace_back(entry); + } + accumulators.emplace_back(wnaf_entries[wnaf_entries.size() - 1] * -1); + uint256_t negative_offset(0); + for (size_t i = 0; i < num_rounds; ++i) { + negative_offset += uint256_t((1ULL << WNAF_SIZE) - 1) * (uint256_t(1) << (i * WNAF_SIZE)); + } + accumulators.emplace_back(-Fr(negative_offset)); + Fr accumulator_result = Fr::accumulate(accumulators); + scalar.assert_equal(accumulator_result); + } else { + // If Fr is a non-native field element, we can't just accumulate the wnaf entries into a single value, + // as we could overflow the circuit modulus + // + // We add the first 34 wnaf entries into a 'low' 138-bit accumulator (138 = 2 68 bit limbs) + // We add the remaining wnaf entries into a 'high' accumulator + // We can then directly construct a Fr element from the accumulators. + // However we cannot underflow our accumulators, and our wnafs represent negative and positive values + // The raw value of each wnaf value is contained in the range [0, 15], however these values represent integers + // [-15, -13, -11, ..., 13, 15] + // + // To map from the raw value to the actual value, we must compute `value * 2 - 15` + // However, we do not subtract off the -15 term when constructing our low and high accumulators (instead just + // multiplying by 2) This ensures the low accumulator will not underflow + // + // Once we hvae reconstructed an Fr element out of our accumulators, + // we ALSO construct an Fr element from the constant offset terms we left out + // We then subtract off the constant term and call `Fr::assert_is_in_field` to reduce the value modulo + // Fr::modulus + const auto reconstruct_half_wnaf = [](field_t* wnafs, const size_t half_round_length) { + std::vector> half_accumulators; + for (size_t i = 0; i < half_round_length; ++i) { + field_t entry = wnafs[half_round_length - 1 - i]; + entry *= 2; + entry *= static_cast>(uint256_t(1) << (i * 4)); + half_accumulators.emplace_back(entry); + } + return field_t::accumulate(half_accumulators); + }; + const size_t midpoint = num_rounds - (Fr::NUM_LIMB_BITS * 2) / WNAF_SIZE; + auto hi_accumulators = reconstruct_half_wnaf(&wnaf_entries[0], midpoint); + auto lo_accumulators = reconstruct_half_wnaf(&wnaf_entries[midpoint], num_rounds - midpoint); + + uint256_t negative_lo(0); + uint256_t negative_hi(0); + for (size_t i = 0; i < midpoint; ++i) { + negative_hi += uint256_t(15) * (uint256_t(1) << (i * 4)); + } + for (size_t i = 0; i < (num_rounds - midpoint); ++i) { + negative_lo += uint256_t(15) * (uint256_t(1) << (i * 4)); + } + + field_t lo_offset = + (wnaf_entries[wnaf_entries.size() - 1] + field_t(barretenberg::fr(negative_lo))).normalize(); + Fr offset = Fr(lo_offset, field_t(barretenberg::fr(negative_hi)), true); + Fr reconstructed = Fr(lo_accumulators, hi_accumulators, true); + reconstructed = reconstructed - offset; + reconstructed.assert_is_in_field(); + reconstructed.assert_equal(scalar); + } + return wnaf_entries; +} + +template +std::vector> element::compute_naf(const Fr& scalar, const size_t max_num_bits) +{ + C* ctx = scalar.context; + uint512_t scalar_multiplier_512 = uint512_t(uint256_t(scalar.get_value()) % Fr::modulus); + uint256_t scalar_multiplier = scalar_multiplier_512.lo; + + const size_t num_rounds = (max_num_bits == 0) ? Fr::modulus.get_msb() + 1 : max_num_bits; + std::vector> naf_entries(num_rounds + 1); + + // if boolean is false => do NOT flip y + // if boolean is true => DO flip y + // first entry is skew. i.e. do we subtract one from the final result or not + if (scalar_multiplier.get_bit(0) == false) { + // add skew + naf_entries[num_rounds] = bool_t(witness_t(ctx, true)); + scalar_multiplier += uint256_t(1); + } else { + naf_entries[num_rounds] = bool_t(witness_t(ctx, false)); + } + for (size_t i = 0; i < num_rounds - 1; ++i) { + bool next_entry = scalar_multiplier.get_bit(i + 1); + // if the next entry is false, we need to flip the sign of the current entry. i.e. make negative + // This is a VERY hacky workaround to ensure that UltraComposer will apply a basic + // range constraint per bool, and not a full 1-bit range gate + if (next_entry == false) { + bool_t bit(ctx, true); + bit.context = ctx; + bit.witness_index = witness_t(ctx, true).witness_index; // flip sign + bit.witness_bool = true; + ctx->create_range_constraint( + bit.witness_index, 1, "biggroup_nafs: compute_naf extracted too many bits in non-next_entry case"); + naf_entries[num_rounds - i - 1] = bit; + } else { + bool_t bit(ctx, false); + bit.witness_index = witness_t(ctx, false).witness_index; // don't flip sign + bit.witness_bool = false; + ctx->create_range_constraint( + bit.witness_index, 1, "biggroup_nafs: compute_naf extracted too many bits in next_entry case"); + naf_entries[num_rounds - i - 1] = bit; + } + } + naf_entries[0] = bool_t(ctx, false); // most significant entry is always true + + // validate correctness of NAF + if constexpr (!Fr::is_composite) { + std::vector accumulators; + for (size_t i = 0; i < num_rounds; ++i) { + // bit = 1 - 2 * naf + Fr entry(naf_entries[naf_entries.size() - 2 - i]); + entry *= -2; + entry += 1; + entry *= static_cast(uint256_t(1) << (i)); + accumulators.emplace_back(entry); + } + accumulators.emplace_back(Fr(naf_entries[naf_entries.size() - 1]) * -1); // skew + Fr accumulator_result = Fr::accumulate(accumulators); + scalar.assert_equal(accumulator_result); + } else { + const auto reconstruct_half_naf = [](bool_t* nafs, const size_t half_round_length) { + // Q: need constraint to start from zero? + field_t negative_accumulator(0); + field_t positive_accumulator(0); + for (size_t i = 0; i < half_round_length; ++i) { + negative_accumulator = negative_accumulator + negative_accumulator + field_t(nafs[i]); + positive_accumulator = + positive_accumulator + positive_accumulator + field_t(1) - field_t(nafs[i]); + } + return std::make_pair(positive_accumulator, negative_accumulator); + }; + const size_t midpoint = num_rounds - Fr::NUM_LIMB_BITS * 2; + auto hi_accumulators = reconstruct_half_naf(&naf_entries[0], midpoint); + auto lo_accumulators = reconstruct_half_naf(&naf_entries[midpoint], num_rounds - midpoint); + + lo_accumulators.second = lo_accumulators.second + field_t(naf_entries[num_rounds]); + + Fr reconstructed_positive = Fr(lo_accumulators.first, hi_accumulators.first); + Fr reconstructed_negative = Fr(lo_accumulators.second, hi_accumulators.second); + Fr accumulator = reconstructed_positive - reconstructed_negative; + accumulator.assert_equal(scalar); + } + return naf_entries; +} +} // namespace stdlib +} // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_secp256k1.hpp b/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_secp256k1.hpp new file mode 100644 index 0000000000..07fc29b37b --- /dev/null +++ b/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_secp256k1.hpp @@ -0,0 +1,141 @@ +#pragma once +/** + * Special case function for performing secp256k1 ecdsa signature verification group operations + * + * TODO: we should try to genericize this, but this method is super fiddly and we need it to be efficient! + * + **/ +namespace plonk { +namespace stdlib { + +template +template +element element::secp256k1_ecdsa_mul(const element& pubkey, const Fr& u1, const Fr& u2) +{ + if constexpr (C::type != waffle::ComposerType::PLOOKUP) { + C* ctx = pubkey.get_context(); + return batch_mul({ element::one(ctx), pubkey }, { u1, u2 }); + } + /** + * Compute `out = u1.[1] + u2.[pubkey] + * + * Split scalar `u1` into 129-bit short scalars `u1_lo, u1_hi`, where `u1 = u1_lo * \lambda u1_hi` + * (\lambda is the cube root of unity modulo the secp256k1 group order) + * + * Covert `u1_lo` and `u1_hi` into an 8-bit sliding window NAF. Our base point is the G1 generator. + * We have a precomputed size-256 plookup table of the generator point, multiplied by all possible wNAF values + * + * We also split scalar `u2` using the secp256k1 endomorphism. Convert short scalars into 4-bit sliding window NAFs. + * We will store the lookup table of all possible base-point wNAF states in a ROM table + * (it's variable-base scalar multiplication in a SNARK with a lookup table! ho ho ho) + * + * The wNAFs `u1_lo_wnaf, u1_hi_wnaf, u2_lo_wnaf, u2_hi_wnaf` are each offset by 1 bit relative to each other. + * i.e. we right-shift `u2_hi` by 1 bit before computing its wNAF + * we right-shift `u1_lo` by 2 bits + * we right-shift `u1_hi` by 3 bits + * we do not shift `u2_lo` + * + * We do this to ensure that we are never adding more than 1 point into our accumulator when performing our + * double-and-add scalar multiplication. It is more efficient to use the montgomery ladder algorithm, + * compared against doubling an accumulator and adding points into it. + * + * The bits removed by the right-shifts are stored in the wnaf's respective `least_significant_wnaf_fragment` member + * variable + */ + const auto [u1_lo_wnaf, u1_hi_wnaf] = compute_secp256k1_endo_wnaf<8, 2, 3>(u1); + const auto [u2_lo_wnaf, u2_hi_wnaf] = compute_secp256k1_endo_wnaf<4, 0, 1>(u2); + + /** + * Construct our 4-bit variable-base and 8-bit fixed base lookup tables + **/ + auto P1 = element::one(pubkey.get_context()); + auto P2 = pubkey; + const auto P1_table = + element::eight_bit_fixed_base_table<>(element::eight_bit_fixed_base_table<>::CurveType::SECP256K1, false); + const auto endoP1_table = + element::eight_bit_fixed_base_table<>(element::eight_bit_fixed_base_table<>::CurveType::SECP256K1, true); + const auto [P2_table, endoP2_table] = create_endo_pair_four_bit_table_plookup(P2); + + // Initialize our accumulator + auto accumulator = P2_table[u2_lo_wnaf.wnaf[0]]; + + /** + * main double-and-add loop + * + * Acc = Acc + Acc + * Acc = Acc + Acc + * Acc = Acc + u2_hi_wnaf.[endoP2] + Acc + * Acc = Acc + u2_lo_wnaf.[P2] + Acc + * Acc = Acc + u1_hi_wnaf.[endoP1] + Acc + * Acc = Acc + u1_lo_wnaf.[P1] + Acc + * Acc = Acc + u2_hi_wnaf.[endoP2] + Acc + * Acc = Acc + u2_lo_wnaf.[P2] + Acc + * + * We add u2 points into the accumulator twice per 'round' as we only have a 4-bit lookup table + * (vs the 8-bit table for u1) + * + * At the conclusion of this loop, we will need to add a final contribution from `u2_hi, u1_lo, u1_hi`. + * This is because we offset our wNAFs to take advantage of the montgomery ladder, but this means we + * have doubled our accumulator AFTER adding our final wnaf contributions from u2_hi, u1_lo and u1_hi + **/ + for (size_t i = 0; i < 16; ++i) { + accumulator = accumulator.dbl(); + accumulator = accumulator.dbl(); + + // u2_hi_wnaf.wnaf[2 * i] is a field_t element (as are the other wnafs). + // See `stdlib/memory/rom_table.hpp` for how indirect array accesses are implemented in UltraPlonk + const auto& add_1 = endoP2_table[u2_hi_wnaf.wnaf[2 * i]]; + const auto& add_2 = P2_table[u2_lo_wnaf.wnaf[2 * i + 1]]; + accumulator = accumulator.double_montgomery_ladder(add_1, add_2); + + const auto& add_3 = endoP1_table[u1_hi_wnaf.wnaf[i]]; + const auto& add_4 = P1_table[u1_lo_wnaf.wnaf[i]]; + accumulator = accumulator.double_montgomery_ladder(add_3, add_4); + + const auto& add_5 = endoP2_table[u2_hi_wnaf.wnaf[2 * i + 1]]; + const auto& add_6 = P2_table[u2_lo_wnaf.wnaf[2 * i + 2]]; + accumulator = accumulator.double_montgomery_ladder(add_5, add_6); + } + + /** + * Add the final contributions from `u2_hi, u1_lo, u1_hi` + **/ + const auto& add_1 = endoP1_table[u1_hi_wnaf.least_significant_wnaf_fragment]; + const auto& add_2 = endoP2_table[u2_hi_wnaf.least_significant_wnaf_fragment]; + const auto& add_3 = P1_table[u1_lo_wnaf.least_significant_wnaf_fragment]; + accumulator = element::chain_add_end( + element::chain_add(add_3, element::chain_add(add_2, element::chain_add_start(accumulator, add_1)))); + + /** + * Handle wNAF skew. + * + * scalars represented via the non-adjacent form can only be odd. If our scalars are even, we must either + * add or subtract the relevant base point into the accumulator + **/ + // TODO REMOVE BOOL CASTS, VALUES HAVE ALREADY BEEN RANGE CONSTRAINED + const auto conditional_add = [](const element& accumulator, + const element& base_point, + const field_t& positive_skew, + const field_t& negative_skew) { + const bool_t positive_skew_bool(positive_skew); + const bool_t negative_skew_bool(negative_skew); + auto to_add = base_point; + to_add.y = to_add.y.conditional_negate(negative_skew_bool); + element result = accumulator + to_add; + + // when computing the wNAF we have already validated that positive_skew and negative_skew cannot both be true + bool_t skew_combined = positive_skew_bool ^ negative_skew_bool; + result.x = accumulator.x.conditional_select(result.x, skew_combined); + result.y = accumulator.y.conditional_select(result.y, skew_combined); + return result; + }; + + accumulator = conditional_add(accumulator, P1, u1_lo_wnaf.positive_skew, u1_lo_wnaf.negative_skew); + accumulator = conditional_add(accumulator, endoP1_table[128], u1_hi_wnaf.positive_skew, u1_hi_wnaf.negative_skew); + accumulator = conditional_add(accumulator, P2, u2_lo_wnaf.positive_skew, u2_lo_wnaf.negative_skew); + accumulator = conditional_add(accumulator, endoP2_table[8], u2_hi_wnaf.positive_skew, u2_hi_wnaf.negative_skew); + + return accumulator; +} +} // namespace stdlib +} // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_tables.hpp b/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_tables.hpp new file mode 100644 index 0000000000..e10ffdabc5 --- /dev/null +++ b/cpp/src/aztec/stdlib/primitives/biggroup/biggroup_tables.hpp @@ -0,0 +1,586 @@ +#pragma once +namespace plonk { +namespace stdlib { + +template +template +std::array, 5> element::create_group_element_rom_tables( + const std::array& rom_data) +{ + + std::vector, 2>> x_lo_limbs; + std::vector, 2>> x_hi_limbs; + std::vector, 2>> y_lo_limbs; + std::vector, 2>> y_hi_limbs; + std::vector, 2>> prime_limbs; + + for (size_t i = 0; i < num_elements; ++i) { + x_lo_limbs.emplace_back(std::array, 2>{ rom_data[i].x.binary_basis_limbs[0].element, + rom_data[i].x.binary_basis_limbs[1].element }); + x_hi_limbs.emplace_back(std::array, 2>{ rom_data[i].x.binary_basis_limbs[2].element, + rom_data[i].x.binary_basis_limbs[3].element }); + y_lo_limbs.emplace_back(std::array, 2>{ rom_data[i].y.binary_basis_limbs[0].element, + rom_data[i].y.binary_basis_limbs[1].element }); + y_hi_limbs.emplace_back(std::array, 2>{ rom_data[i].y.binary_basis_limbs[2].element, + rom_data[i].y.binary_basis_limbs[3].element }); + prime_limbs.emplace_back( + std::array, 2>{ rom_data[i].x.prime_basis_limb, rom_data[i].y.prime_basis_limb }); + } + std::array, 5> output_tables; + output_tables[0] = twin_rom_table(x_lo_limbs); + output_tables[1] = twin_rom_table(x_hi_limbs); + output_tables[2] = twin_rom_table(y_lo_limbs); + output_tables[3] = twin_rom_table(y_hi_limbs); + output_tables[4] = twin_rom_table(prime_limbs); + return output_tables; +} + +template +template +element element::read_group_element_rom_tables( + const std::array, 5>& tables, const field_t& index) +{ + const auto xlo = tables[0][index]; + const auto xhi = tables[1][index]; + const auto ylo = tables[2][index]; + 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]); + const auto output = element(x_fq, y_fq); + return output; +} + +template +template +element::four_bit_table_plookup::four_bit_table_plookup(const element& input) +{ + element d2 = input.dbl(); + + element_table[8] = input; + for (size_t i = 9; i < 16; ++i) { + element_table[i] = element_table[i - 1] + d2; + } + for (size_t i = 0; i < 8; ++i) { + element_table[i] = (-element_table[15 - i]).reduce(); + } + + coordinates = create_group_element_rom_tables<16>(element_table); +} + +template +template +element element::four_bit_table_plookup::operator[](const field_t& index) const +{ + return read_group_element_rom_tables<16>(coordinates, index); +} + +template +template +element element::eight_bit_fixed_base_table::operator[](const field_t& index) const +{ + const auto get_plookup_tags = [this]() { + switch (curve_type) { + case CurveType::SECP256K1: { + return std::array{ + use_endomorphism ? MultiTableId::SECP256K1_XLO_ENDO : MultiTableId::SECP256K1_XLO, + use_endomorphism ? MultiTableId::SECP256K1_XHI_ENDO : MultiTableId::SECP256K1_XHI, + MultiTableId::SECP256K1_YLO, + MultiTableId::SECP256K1_YHI, + use_endomorphism ? MultiTableId::SECP256K1_XYPRIME_ENDO : MultiTableId::SECP256K1_XYPRIME, + }; + } + case CurveType::BN254: { + return std::array{ + use_endomorphism ? MultiTableId::BN254_XLO_ENDO : MultiTableId::BN254_XLO, + use_endomorphism ? MultiTableId::BN254_XHI_ENDO : MultiTableId::BN254_XHI, + MultiTableId::BN254_YLO, + MultiTableId::BN254_YHI, + use_endomorphism ? MultiTableId::BN254_XYPRIME_ENDO : MultiTableId::BN254_XYPRIME, + }; + } + default: { + return std::array{ + use_endomorphism ? MultiTableId::BN254_XLO_ENDO : MultiTableId::BN254_XLO, + use_endomorphism ? MultiTableId::BN254_XHI_ENDO : MultiTableId::BN254_XHI, + MultiTableId::BN254_YLO, + MultiTableId::BN254_YHI, + use_endomorphism ? MultiTableId::BN254_XYPRIME_ENDO : MultiTableId::BN254_XYPRIME, + }; + } + } + }; + + const auto tags = get_plookup_tags(); + + const auto xlo = plookup_read::read_pair_from_table(tags[0], index); + const auto xhi = plookup_read::read_pair_from_table(tags[1], index); + const auto ylo = plookup_read::read_pair_from_table(tags[2], index); + 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); + + if (use_endomorphism) { + y = -y; + } + + return element(x, y); +} + +template +template +element element::eight_bit_fixed_base_table::operator[](const size_t index) const +{ + return operator[](field_t(index)); +} + +/** + * lookup_table_plookup + **/ +template +template +element::lookup_table_plookup::lookup_table_plookup(const std::array& inputs) +{ + if constexpr (length == 2) { + element_table[0] = inputs[1] + inputs[0]; + element_table[1] = inputs[1] - inputs[0]; + } else if constexpr (length == 3) { + element R0 = inputs[1] + inputs[0]; + element R1 = inputs[1] - inputs[0]; + element_table[0] = inputs[2] + R0; // C + B + A + element_table[1] = inputs[2] + R1; // C + B - A + element_table[2] = inputs[2] - R1; // C - B + A + element_table[3] = inputs[2] - R0; // C - B - A + } else if constexpr (length == 4) { + element T0 = inputs[1] + inputs[0]; + element T1 = inputs[1] - inputs[0]; + element T2 = inputs[3] + inputs[2]; + element T3 = inputs[3] - inputs[2]; + + element_table[0] = T2 + T0; // D + C + B + A + element_table[1] = T2 + T1; // D + C + B - A + element_table[2] = T2 - T1; // D + C - B + A + element_table[3] = T2 - T0; // D + C - B - A + element_table[4] = T3 + T0; // D - C + B + A + element_table[5] = T3 + T1; // D - C + B - A + element_table[6] = T3 - T1; // D - C - B + A + element_table[7] = T3 - T0; // D - C - B - A + } else if constexpr (length == 5) { + element A0 = inputs[1] + inputs[0]; // B + A + element A1 = inputs[1] - inputs[0]; // B - A + + element T2 = inputs[3] + inputs[2]; // D + C + element T3 = inputs[3] - inputs[2]; // D - C + + element E0 = inputs[4] + T2; // E + D + C // 0 0 0 + element E1 = inputs[4] + T3; // E + D - C // 0 0 1 + element E2 = inputs[4] - T3; // E - D + C // 0 1 0 + element E3 = inputs[4] - T2; // E - D - C // 0 1 1 + + element_table[0] = E0 + A0; // E + D + C + B + A // 0 0 0 0 0 + element_table[1] = E0 + A1; // E + D + C + B - A // 0 0 0 0 1 + element_table[2] = E0 - A1; // E + D + C - B + A // 0 0 0 1 0 + element_table[3] = E0 - A0; // E + D + C - B - A // 0 0 0 1 1 + element_table[4] = E1 + A0; // E + D - C + B + A // 0 0 1 0 0 + element_table[5] = E1 + A1; // E + D - C + B - A // 0 0 1 0 1 + element_table[6] = E1 - A1; // E + D - C - B + A // 0 0 1 1 0 + element_table[7] = E1 - A0; // E + D - C - B - A // 0 0 1 1 1 + element_table[8] = E2 + A0; // E - D + C + B + A // 0 1 0 0 0 + element_table[9] = E2 + A1; // E - D + C + B - A // 0 1 0 0 1 + element_table[10] = E2 - A1; // E - D + C - B + A // 0 1 0 1 0 + element_table[11] = E2 - A0; // E - D - C - B - A // 0 1 0 1 1 + element_table[12] = E3 + A0; // E - D - C + B + A // 0 1 1 0 0 + element_table[13] = E3 + A1; // E - D - C + B - A // 0 1 1 0 1 + element_table[14] = E3 - A1; // E - D - C - B + A // 0 1 1 1 0 + element_table[15] = E3 - A0; // E - D - C - B - A // 0 1 1 1 1 + } else if constexpr (length == 6) { + // 44 adds! Only use this if it saves us adding another table to a multi-scalar-multiplication + element A0 = inputs[1] + inputs[0]; // B + A + element A1 = inputs[1] - inputs[0]; // B - A + element E0 = inputs[4] + inputs[3]; // E + D + element E1 = inputs[4] - inputs[3]; // E - D + + element C0 = inputs[2] + A0; // C + B + A + element C1 = inputs[2] + A1; // C + B - A + element C2 = inputs[2] - A1; // C - B + A + element C3 = inputs[2] - A0; // C - B - A + + element F0 = inputs[5] + E0; // F + E + D + element F1 = inputs[5] + E1; // F + E - D + element F2 = inputs[5] - E1; // F - E + D + element F3 = inputs[5] - E0; // F - E - E + + element_table[0] = F0 + C0; + element_table[1] = F0 + C1; + element_table[2] = F0 + C2; + element_table[3] = F0 + C3; + element_table[4] = F0 - C3; + element_table[5] = F0 - C2; + element_table[6] = F0 - C1; + element_table[7] = F0 - C0; + + element_table[8] = F1 + C0; + element_table[9] = F1 + C1; + element_table[10] = F1 + C2; + element_table[11] = F1 + C3; + element_table[12] = F1 - C3; + element_table[13] = F1 - C2; + element_table[14] = F1 - C1; + element_table[15] = F1 - C0; + + element_table[16] = F2 + C0; + element_table[17] = F2 + C1; + element_table[18] = F2 + C2; + element_table[19] = F2 + C3; + element_table[20] = F2 - C3; + element_table[21] = F2 - C2; + element_table[22] = F2 - C1; + element_table[23] = F2 - C0; + + element_table[24] = F3 + C0; + element_table[25] = F3 + C1; + element_table[26] = F3 + C2; + element_table[27] = F3 + C3; + element_table[28] = F3 - C3; + element_table[29] = F3 - C2; + element_table[30] = F3 - C1; + element_table[31] = F3 - C0; + } else if constexpr (length == 7) { + // 82 adds! This one is not worth using... + + element A0 = inputs[1] + inputs[0]; // B + A + element A1 = inputs[1] - inputs[0]; // B - A + + element D0 = inputs[3] + inputs[2]; // D + C + element D1 = inputs[3] - inputs[2]; // D - C + + element E0 = D0 + A0; // D + C + B + A + element E1 = D0 + A1; // D + C + B - A + element E2 = D0 - A1; // D + C - B + A + element E3 = D0 - A0; // D + C - B - A + element E4 = D1 + A0; // D - C + B + A + element E5 = D1 + A1; // D - C + B - A + element E6 = D1 - A1; // D - C - B + A + element E7 = D1 - A0; // D - C - B - A + + element F0 = inputs[5] + inputs[4]; // F + E + element F1 = inputs[5] - inputs[4]; // F - E + + element G0 = inputs[6] + F0; // G + F + E + element G1 = inputs[6] + F1; // G + F - E + element G2 = inputs[6] - F1; // G - F + E + element G3 = inputs[6] - F0; // G - F - E + + element_table[0] = G0 + E0; + element_table[1] = G0 + E1; + element_table[2] = G0 + E2; + element_table[3] = G0 + E3; + element_table[4] = G0 + E4; + element_table[5] = G0 + E5; + element_table[6] = G0 + E6; + element_table[7] = G0 + E7; + element_table[8] = G0 - E7; + element_table[9] = G0 - E6; + element_table[10] = G0 - E5; + element_table[11] = G0 - E4; + element_table[12] = G0 - E3; + element_table[13] = G0 - E2; + element_table[14] = G0 - E1; + element_table[15] = G0 - E0; + element_table[16] = G1 + E0; + element_table[17] = G1 + E1; + element_table[18] = G1 + E2; + element_table[19] = G1 + E3; + element_table[20] = G1 + E4; + element_table[21] = G1 + E5; + element_table[22] = G1 + E6; + element_table[23] = G1 + E7; + element_table[24] = G1 - E7; + element_table[25] = G1 - E6; + element_table[26] = G1 - E5; + element_table[27] = G1 - E4; + element_table[28] = G1 - E3; + element_table[29] = G1 - E2; + element_table[30] = G1 - E1; + element_table[31] = G1 - E0; + element_table[32] = G2 + E0; + element_table[33] = G2 + E1; + element_table[34] = G2 + E2; + element_table[35] = G2 + E3; + element_table[36] = G2 + E4; + element_table[37] = G2 + E5; + element_table[38] = G2 + E6; + element_table[39] = G2 + E7; + element_table[40] = G2 - E7; + element_table[41] = G2 - E6; + element_table[42] = G2 - E5; + element_table[43] = G2 - E4; + element_table[44] = G2 - E3; + element_table[45] = G2 - E2; + element_table[46] = G2 - E1; + element_table[47] = G2 - E0; + element_table[48] = G3 + E0; + element_table[49] = G3 + E1; + element_table[50] = G3 + E2; + element_table[51] = G3 + E3; + element_table[52] = G3 + E4; + element_table[53] = G3 + E5; + element_table[54] = G3 + E6; + element_table[55] = G3 + E7; + element_table[56] = G3 - E7; + element_table[57] = G3 - E6; + element_table[58] = G3 - E5; + element_table[59] = G3 - E4; + element_table[60] = G3 - E3; + element_table[61] = G3 - E2; + element_table[62] = G3 - E1; + element_table[63] = G3 - E0; + } + for (size_t i = 0; i < table_size / 2; ++i) { + element_table[i + table_size / 2] = (-element_table[table_size / 2 - 1 - i]).reduce(); + } + coordinates = create_group_element_rom_tables(element_table); +} + +template +template +element element::lookup_table_plookup::get( + const std::array, length>& bits) const +{ + std::vector> accumulators; + for (size_t i = 0; i < length; ++i) { + accumulators.emplace_back(field_t(bits[i]) * (1ULL << i)); + } + field_t index = field_t::accumulate(accumulators); + return read_group_element_rom_tables(coordinates, index); +} + +/** + * lookup_table_base + **/ +template +template +element::lookup_table_base::lookup_table_base(const std::array& inputs) +{ + static_assert(length <= 4 && length >= 2); + if constexpr (length == 2) { + twin0 = inputs[1] + inputs[0]; + twin1 = inputs[1] - inputs[0]; + element_table[0] = twin0; + element_table[1] = twin1; + } else if constexpr (length == 3) { + element T0 = inputs[1] + inputs[0]; + element T1 = inputs[1] - inputs[0]; + element_table[0] = inputs[2] + T0; // C + B + A + element_table[1] = inputs[2] + T1; // C + B - A + element_table[2] = inputs[2] - T1; // C - B + A + element_table[3] = inputs[2] - T0; // C - B - A + + x_b0_table = field_t::preprocess_two_bit_table(element_table[0].x.binary_basis_limbs[0].element, + element_table[1].x.binary_basis_limbs[0].element, + element_table[2].x.binary_basis_limbs[0].element, + element_table[3].x.binary_basis_limbs[0].element); + x_b1_table = field_t::preprocess_two_bit_table(element_table[0].x.binary_basis_limbs[1].element, + element_table[1].x.binary_basis_limbs[1].element, + element_table[2].x.binary_basis_limbs[1].element, + element_table[3].x.binary_basis_limbs[1].element); + x_b2_table = field_t::preprocess_two_bit_table(element_table[0].x.binary_basis_limbs[2].element, + element_table[1].x.binary_basis_limbs[2].element, + element_table[2].x.binary_basis_limbs[2].element, + element_table[3].x.binary_basis_limbs[2].element); + x_b3_table = field_t::preprocess_two_bit_table(element_table[0].x.binary_basis_limbs[3].element, + element_table[1].x.binary_basis_limbs[3].element, + element_table[2].x.binary_basis_limbs[3].element, + element_table[3].x.binary_basis_limbs[3].element); + + y_b0_table = field_t::preprocess_two_bit_table(element_table[0].y.binary_basis_limbs[0].element, + element_table[1].y.binary_basis_limbs[0].element, + element_table[2].y.binary_basis_limbs[0].element, + element_table[3].y.binary_basis_limbs[0].element); + y_b1_table = field_t::preprocess_two_bit_table(element_table[0].y.binary_basis_limbs[1].element, + element_table[1].y.binary_basis_limbs[1].element, + element_table[2].y.binary_basis_limbs[1].element, + element_table[3].y.binary_basis_limbs[1].element); + y_b2_table = field_t::preprocess_two_bit_table(element_table[0].y.binary_basis_limbs[2].element, + element_table[1].y.binary_basis_limbs[2].element, + element_table[2].y.binary_basis_limbs[2].element, + element_table[3].y.binary_basis_limbs[2].element); + y_b3_table = field_t::preprocess_two_bit_table(element_table[0].y.binary_basis_limbs[3].element, + element_table[1].y.binary_basis_limbs[3].element, + element_table[2].y.binary_basis_limbs[3].element, + element_table[3].y.binary_basis_limbs[3].element); + } else if constexpr (length == 4) { + element T0 = inputs[1] + inputs[0]; + element T1 = inputs[1] - inputs[0]; + element T2 = inputs[3] + inputs[2]; + element T3 = inputs[3] - inputs[2]; + + element_table[0] = T2 + T0; // D + C + B + A + element_table[1] = T2 + T1; // D + C + B - A + element_table[2] = T2 - T1; // D + C - B + A + element_table[3] = T2 - T0; // D + C - B - A + element_table[4] = T3 + T0; // D - C + B + A + element_table[5] = T3 + T1; // D - C + B - A + element_table[6] = T3 - T1; // D - C - B + A + element_table[7] = T3 - T0; // D - C - B - A + + x_b0_table = field_t::preprocess_three_bit_table(element_table[0].x.binary_basis_limbs[0].element, + element_table[1].x.binary_basis_limbs[0].element, + element_table[2].x.binary_basis_limbs[0].element, + element_table[3].x.binary_basis_limbs[0].element, + element_table[4].x.binary_basis_limbs[0].element, + element_table[5].x.binary_basis_limbs[0].element, + element_table[6].x.binary_basis_limbs[0].element, + element_table[7].x.binary_basis_limbs[0].element); + x_b1_table = field_t::preprocess_three_bit_table(element_table[0].x.binary_basis_limbs[1].element, + element_table[1].x.binary_basis_limbs[1].element, + element_table[2].x.binary_basis_limbs[1].element, + element_table[3].x.binary_basis_limbs[1].element, + element_table[4].x.binary_basis_limbs[1].element, + element_table[5].x.binary_basis_limbs[1].element, + element_table[6].x.binary_basis_limbs[1].element, + element_table[7].x.binary_basis_limbs[1].element); + x_b2_table = field_t::preprocess_three_bit_table(element_table[0].x.binary_basis_limbs[2].element, + element_table[1].x.binary_basis_limbs[2].element, + element_table[2].x.binary_basis_limbs[2].element, + element_table[3].x.binary_basis_limbs[2].element, + element_table[4].x.binary_basis_limbs[2].element, + element_table[5].x.binary_basis_limbs[2].element, + element_table[6].x.binary_basis_limbs[2].element, + element_table[7].x.binary_basis_limbs[2].element); + x_b3_table = field_t::preprocess_three_bit_table(element_table[0].x.binary_basis_limbs[3].element, + element_table[1].x.binary_basis_limbs[3].element, + element_table[2].x.binary_basis_limbs[3].element, + element_table[3].x.binary_basis_limbs[3].element, + element_table[4].x.binary_basis_limbs[3].element, + element_table[5].x.binary_basis_limbs[3].element, + element_table[6].x.binary_basis_limbs[3].element, + element_table[7].x.binary_basis_limbs[3].element); + + y_b0_table = field_t::preprocess_three_bit_table(element_table[0].y.binary_basis_limbs[0].element, + element_table[1].y.binary_basis_limbs[0].element, + element_table[2].y.binary_basis_limbs[0].element, + element_table[3].y.binary_basis_limbs[0].element, + element_table[4].y.binary_basis_limbs[0].element, + element_table[5].y.binary_basis_limbs[0].element, + element_table[6].y.binary_basis_limbs[0].element, + element_table[7].y.binary_basis_limbs[0].element); + y_b1_table = field_t::preprocess_three_bit_table(element_table[0].y.binary_basis_limbs[1].element, + element_table[1].y.binary_basis_limbs[1].element, + element_table[2].y.binary_basis_limbs[1].element, + element_table[3].y.binary_basis_limbs[1].element, + element_table[4].y.binary_basis_limbs[1].element, + element_table[5].y.binary_basis_limbs[1].element, + element_table[6].y.binary_basis_limbs[1].element, + element_table[7].y.binary_basis_limbs[1].element); + y_b2_table = field_t::preprocess_three_bit_table(element_table[0].y.binary_basis_limbs[2].element, + element_table[1].y.binary_basis_limbs[2].element, + element_table[2].y.binary_basis_limbs[2].element, + element_table[3].y.binary_basis_limbs[2].element, + element_table[4].y.binary_basis_limbs[2].element, + element_table[5].y.binary_basis_limbs[2].element, + element_table[6].y.binary_basis_limbs[2].element, + element_table[7].y.binary_basis_limbs[2].element); + y_b3_table = field_t::preprocess_three_bit_table(element_table[0].y.binary_basis_limbs[3].element, + element_table[1].y.binary_basis_limbs[3].element, + element_table[2].y.binary_basis_limbs[3].element, + element_table[3].y.binary_basis_limbs[3].element, + element_table[4].y.binary_basis_limbs[3].element, + element_table[5].y.binary_basis_limbs[3].element, + element_table[6].y.binary_basis_limbs[3].element, + element_table[7].y.binary_basis_limbs[3].element); + } +} + +template +template +element element::lookup_table_base::get( + const std::array, length>& bits) const +{ + static_assert(length <= 4 && length >= 2); + + if constexpr (length == 2) { + bool_t table_selector = bits[0] ^ bits[1]; + bool_t sign_selector = bits[1]; + Fq to_add_x = twin0.x.conditional_select(twin1.x, table_selector); + Fq to_add_y = twin0.y.conditional_select(twin1.y, table_selector); + element to_add(to_add_x, to_add_y.conditional_negate(sign_selector)); + return to_add; + } else if constexpr (length == 3) { + bool_t t0 = bits[2] ^ bits[0]; + bool_t t1 = bits[2] ^ bits[1]; + + field_t x_b0 = field_t::select_from_two_bit_table(x_b0_table, t1, t0); + field_t x_b1 = field_t::select_from_two_bit_table(x_b1_table, t1, t0); + field_t x_b2 = field_t::select_from_two_bit_table(x_b2_table, t1, t0); + field_t x_b3 = field_t::select_from_two_bit_table(x_b3_table, t1, t0); + + field_t y_b0 = field_t::select_from_two_bit_table(y_b0_table, t1, t0); + field_t y_b1 = field_t::select_from_two_bit_table(y_b1_table, t1, t0); + field_t y_b2 = field_t::select_from_two_bit_table(y_b2_table, t1, t0); + field_t y_b3 = field_t::select_from_two_bit_table(y_b3_table, t1, t0); + + Fq to_add_x; + Fq to_add_y; + to_add_x.binary_basis_limbs[0] = typename Fq::Limb(x_b0, Fq::DEFAULT_MAXIMUM_LIMB); + to_add_x.binary_basis_limbs[1] = typename Fq::Limb(x_b1, Fq::DEFAULT_MAXIMUM_LIMB); + to_add_x.binary_basis_limbs[2] = typename Fq::Limb(x_b2, Fq::DEFAULT_MAXIMUM_LIMB); + to_add_x.binary_basis_limbs[3] = typename Fq::Limb(x_b3, Fq::DEFAULT_MAXIMUM_MOST_SIGNIFICANT_LIMB); + to_add_x.prime_basis_limb = to_add_x.binary_basis_limbs[0].element.add_two( + to_add_x.binary_basis_limbs[1].element * Fq::shift_1, to_add_x.binary_basis_limbs[2].element * Fq::shift_2); + to_add_x.prime_basis_limb += to_add_x.binary_basis_limbs[3].element * Fq::shift_3; + + to_add_y.binary_basis_limbs[0] = typename Fq::Limb(y_b0, Fq::DEFAULT_MAXIMUM_LIMB); + to_add_y.binary_basis_limbs[1] = typename Fq::Limb(y_b1, Fq::DEFAULT_MAXIMUM_LIMB); + to_add_y.binary_basis_limbs[2] = typename Fq::Limb(y_b2, Fq::DEFAULT_MAXIMUM_LIMB); + to_add_y.binary_basis_limbs[3] = typename Fq::Limb(y_b3, Fq::DEFAULT_MAXIMUM_MOST_SIGNIFICANT_LIMB); + to_add_y.prime_basis_limb = to_add_y.binary_basis_limbs[0].element.add_two( + to_add_y.binary_basis_limbs[1].element * Fq::shift_1, to_add_y.binary_basis_limbs[2].element * Fq::shift_2); + to_add_y.prime_basis_limb += to_add_y.binary_basis_limbs[3].element * Fq::shift_3; + element to_add(to_add_x, to_add_y.conditional_negate(bits[2])); + + return to_add; + } else if constexpr (length == 4) { + bool_t t0 = bits[3] ^ bits[0]; + bool_t t1 = bits[3] ^ bits[1]; + bool_t t2 = bits[3] ^ bits[2]; + + field_t x_b0 = field_t::select_from_three_bit_table(x_b0_table, t2, t1, t0); + field_t x_b1 = field_t::select_from_three_bit_table(x_b1_table, t2, t1, t0); + field_t x_b2 = field_t::select_from_three_bit_table(x_b2_table, t2, t1, t0); + field_t x_b3 = field_t::select_from_three_bit_table(x_b3_table, t2, t1, t0); + + field_t y_b0 = field_t::select_from_three_bit_table(y_b0_table, t2, t1, t0); + field_t y_b1 = field_t::select_from_three_bit_table(y_b1_table, t2, t1, t0); + field_t y_b2 = field_t::select_from_three_bit_table(y_b2_table, t2, t1, t0); + field_t y_b3 = field_t::select_from_three_bit_table(y_b3_table, t2, t1, t0); + + Fq to_add_x; + Fq to_add_y; + to_add_x.binary_basis_limbs[0] = typename Fq::Limb(x_b0, Fq::DEFAULT_MAXIMUM_LIMB); + to_add_x.binary_basis_limbs[1] = typename Fq::Limb(x_b1, Fq::DEFAULT_MAXIMUM_LIMB); + to_add_x.binary_basis_limbs[2] = typename Fq::Limb(x_b2, Fq::DEFAULT_MAXIMUM_LIMB); + to_add_x.binary_basis_limbs[3] = typename Fq::Limb(x_b3, Fq::DEFAULT_MAXIMUM_MOST_SIGNIFICANT_LIMB); + to_add_x.prime_basis_limb = to_add_x.binary_basis_limbs[0].element.add_two( + to_add_x.binary_basis_limbs[1].element * Fq::shift_1, to_add_x.binary_basis_limbs[2].element * Fq::shift_2); + to_add_x.prime_basis_limb += to_add_x.binary_basis_limbs[3].element * Fq::shift_3; + + to_add_y.binary_basis_limbs[0] = typename Fq::Limb(y_b0, Fq::DEFAULT_MAXIMUM_LIMB); + to_add_y.binary_basis_limbs[1] = typename Fq::Limb(y_b1, Fq::DEFAULT_MAXIMUM_LIMB); + to_add_y.binary_basis_limbs[2] = typename Fq::Limb(y_b2, Fq::DEFAULT_MAXIMUM_LIMB); + to_add_y.binary_basis_limbs[3] = typename Fq::Limb(y_b3, Fq::DEFAULT_MAXIMUM_MOST_SIGNIFICANT_LIMB); + to_add_y.prime_basis_limb = to_add_y.binary_basis_limbs[0].element.add_two( + to_add_y.binary_basis_limbs[1].element * Fq::shift_1, to_add_y.binary_basis_limbs[2].element * Fq::shift_2); + to_add_y.prime_basis_limb += to_add_y.binary_basis_limbs[3].element * Fq::shift_3; + + element to_add(to_add_x, to_add_y.conditional_negate(bits[3])); + + return to_add; + } + return element::one(bits[0].get_context()); +} +} // namespace stdlib +} // namespace plonk diff --git a/cpp/src/aztec/stdlib/primitives/byte_array/byte_array.cpp b/cpp/src/aztec/stdlib/primitives/byte_array/byte_array.cpp index a370a23cdd..656bd9c5c6 100644 --- a/cpp/src/aztec/stdlib/primitives/byte_array/byte_array.cpp +++ b/cpp/src/aztec/stdlib/primitives/byte_array/byte_array.cpp @@ -9,19 +9,21 @@ using namespace barretenberg; namespace plonk { namespace stdlib { -template -byte_array::byte_array(ComposerContext* parent_context) +// ULTRA: Further merging with + +template +byte_array::byte_array(Composer* parent_context) : context(parent_context) {} -template -byte_array::byte_array(ComposerContext* parent_context, const size_t n) +template +byte_array::byte_array(Composer* parent_context, const size_t n) : context(parent_context) - , values(std::vector>(n)) + , values(std::vector>(n)) {} -template -byte_array::byte_array(ComposerContext* parent_context, const std::string& input) +template +byte_array::byte_array(Composer* parent_context, const std::string& input) : byte_array(parent_context, std::vector(input.begin(), input.end())) {} @@ -31,15 +33,15 @@ byte_array::byte_array(ComposerContext* parent_context, const s * @warning This constructor will instantiate each byte as a circuit witness, NOT a circuit constant. * Do not use this method if the input needs to be hardcoded for a specific circuit **/ -template -byte_array::byte_array(ComposerContext* parent_context, std::vector const& input) +template +byte_array::byte_array(Composer* parent_context, std::vector const& input) : context(parent_context) , values(input.size()) { for (size_t i = 0; i < input.size(); ++i) { uint8_t c = input[i]; - field_t value(witness_t(context, (uint64_t)c)); - value.create_range_constraint(8); + field_t value(witness_t(context, (uint64_t)c)); + value.create_range_constraint(8, "byte_array: vector entry larger than 1 byte."); values[i] = value; } } @@ -84,13 +86,12 @@ byte_array::byte_array(ComposerContext* parent_context, std::ve * Case 0 corresponds to the range [1, 2^128-1]. We see that the 129th bit of y_lo exactly indicates the case. * Extracting this (and the 130th bit, which is always 0, for convenience) and adding it to v_hi, we have a uniform * constraint to apply. Namely, setting - * y_borrow := 1 - (top quad of y_lo regarded as a 130-bit integer) + * y_overlap := 1 - (top quad of y_lo regarded as a 130-bit integer) * and - * y_hi := s_hi - v_hi - y_borrow, + * y_hi := s_hi - v_hi - y_overlap, * range constrianing y_hi to 128 bits imposes validator < r. */ -template -byte_array::byte_array(const field_t& input, const size_t num_bytes) +template byte_array::byte_array(const field_t& input, const size_t num_bytes) { ASSERT(num_bytes <= 32); uint256_t value = input.get_value(); @@ -102,19 +103,19 @@ byte_array::byte_array(const field_t& input, c } } else { constexpr barretenberg::fr byte_shift(256); - field_t validator(context, 0); + field_t validator(context, 0); - field_t shifted_high_limb(context, 0); // will be set to 2^128v_hi if `i` reaches 15. + field_t shifted_high_limb(context, 0); // will be set to 2^128v_hi if `i` reaches 15. for (size_t i = 0; i < num_bytes; ++i) { barretenberg::fr byte_val = value.slice((num_bytes - i - 1) * 8, (num_bytes - i) * 8); - field_t byte = witness_t(context, byte_val); - byte.create_range_constraint(8); + field_t byte = witness_t(context, byte_val); + byte.create_range_constraint(8, "byte_array: byte extraction failed."); barretenberg::fr scaling_factor_value = byte_shift.pow(static_cast(num_bytes - 1 - i)); - field_t scaling_factor(context, scaling_factor_value); + field_t scaling_factor(context, scaling_factor_value); validator = validator + (scaling_factor * byte); values[i] = byte; if (i == 15) { - shifted_high_limb = field_t(validator); + shifted_high_limb = field_t(validator); } } validator.assert_equal(input); @@ -125,66 +126,75 @@ byte_array::byte_array(const field_t& input, c const fr s_lo = modulus_minus_one.slice(0, 128); const fr s_hi = modulus_minus_one.slice(128, 256); const fr shift = fr(uint256_t(1) << 128); - - // defining input_lo = validator - shifted_high_limb, we're checking s_lo + shift - input_lo is - // non-negative. - field_t y_lo = (-validator) + (s_lo + shift); - y_lo += shifted_high_limb; - // The range constraint imposed here already holds implicitly. We only do this to get the top quad. - const auto low_accumulators = - context->decompose_into_base4_accumulators(y_lo.normalize().witness_index, 130); - field_t y_borrow = - -(field_t::from_witness_index(context, low_accumulators[0]) - 1); - + field_t y_lo = (-validator) + (s_lo + shift); + + field_t y_overlap; + if constexpr (Composer::type == waffle::ComposerType::PLOOKUP) { + // carve out the 2 high bits from (y_lo + shifted_high_limb) and instantiate as y_overlap + const uint256_t y_lo_value = y_lo.get_value() + shifted_high_limb.get_value(); + const uint256_t y_overlap_value = y_lo_value >> 128; + y_overlap = witness_t(context, y_overlap_value); + y_overlap.create_range_constraint(2, "byte_array: y_overlap is not a quad"); + field_t y_remainder = + y_lo.add_two(shifted_high_limb, -(y_overlap * field_t(uint256_t(1ULL) << 128))); + y_remainder.create_range_constraint(128, "byte_array: y_remainder doesn't fit in 128 bits."); + y_overlap = -(y_overlap - 1); + } else { + // defining input_lo = validator - shifted_high_limb, we're checking s_lo + shift - input_lo is + // non-negative. + y_lo += shifted_high_limb; + // The range constraint imposed here already holds implicitly. We only do this to get the top quad. + const auto low_accumulators = context->decompose_into_base4_accumulators( + y_lo.normalize().witness_index, 130, "byte_array: normalized y_lo too large."); + y_overlap = -(field_t::from_witness_index(context, low_accumulators[0]) - 1); + } // define input_hi = shifted_high_limb/shift. We know input_hi is max 128 bits, and we're checking // s_hi - (input_hi + borrow) is non-negative - field_t y_hi = -(shifted_high_limb / shift) + (s_hi); - y_hi -= y_borrow; - y_hi.create_range_constraint(128); + field_t y_hi = -(shifted_high_limb / shift) + (s_hi); + y_hi -= y_overlap; + y_hi.create_range_constraint(128, "byte_array: y_hi doesn't fit in 128 bits."); } } } -template -byte_array::byte_array(const safe_uint_t& input, const size_t num_bytes) +template +byte_array::byte_array(const safe_uint_t& input, const size_t num_bytes) : byte_array(input.value, num_bytes) {} -template -byte_array::byte_array(ComposerContext* parent_context, bytes_t const& input) +template +byte_array::byte_array(Composer* parent_context, bytes_t const& input) : context(parent_context) , values(input) {} -template -byte_array::byte_array(ComposerContext* parent_context, bytes_t&& input) +template +byte_array::byte_array(Composer* parent_context, bytes_t&& input) : context(parent_context) , values(input) {} -template byte_array::byte_array(const byte_array& other) +template byte_array::byte_array(const byte_array& other) { context = other.context; std::copy(other.values.begin(), other.values.end(), std::back_inserter(values)); } -template byte_array::byte_array(byte_array&& other) +template byte_array::byte_array(byte_array&& other) { context = other.context; values = std::move(other.values); } -template -byte_array& byte_array::operator=(const byte_array& other) +template byte_array& byte_array::operator=(const byte_array& other) { context = other.context; - values = std::vector>(); + values = std::vector>(); std::copy(other.values.begin(), other.values.end(), std::back_inserter(values)); return *this; } -template -byte_array& byte_array::operator=(byte_array&& other) +template byte_array& byte_array::operator=(byte_array&& other) { context = other.context; values = std::move(other.values); @@ -196,30 +206,39 @@ byte_array& byte_array::operator=(byte_array&& * * @details The byte array is represented as a big integer, that is then converted into a field element. * The transformation is only injective if the byte array is < 32 bytes. - * Larger byte arrays can still be cast to a single field element, but the value will wrap around the circuit modulus + * Larger byte arrays can still be cast to a single field element, but the value will wrap around the circuit + *modulus **/ -template byte_array::operator field_t() const +template byte_array::operator field_t() const { const size_t bytes = values.size(); barretenberg::fr shift(256); - field_t result(context, barretenberg::fr(0)); + field_t result(context, barretenberg::fr(0)); for (size_t i = 0; i < values.size(); ++i) { - field_t temp(values[i]); + field_t temp(values[i]); barretenberg::fr scaling_factor_value = shift.pow(static_cast(bytes - 1 - i)); - field_t scaling_factor(values[i].context, scaling_factor_value); + field_t scaling_factor(values[i].context, scaling_factor_value); result = result + (scaling_factor * temp); } return result.normalize(); } -template -byte_array& byte_array::write(byte_array const& other) +template byte_array& byte_array::write(byte_array const& other) { values.insert(values.end(), other.bytes().begin(), other.bytes().end()); return *this; } -template byte_array byte_array::slice(size_t offset) const +template byte_array& byte_array::write_at(byte_array const& other, size_t index) +{ + ASSERT(index + other.values.size() <= values.size()); + for (size_t i = 0; i < other.values.size(); i++) { + values[i + index] = other.values[i]; + } + return *this; +} + +template byte_array byte_array::slice(size_t offset) const { ASSERT(offset < values.size()); return byte_array(context, bytes_t(values.begin() + (long)(offset), values.end())); @@ -229,8 +248,7 @@ template byte_array byte_array -byte_array byte_array::slice(size_t offset, size_t length) const +template byte_array byte_array::slice(size_t offset, size_t length) const { ASSERT(offset < values.size()); // it's <= cause vector constructor doesn't include end point @@ -243,7 +261,7 @@ byte_array byte_array::slice(size_t offset, si /** * @brief Reverse the bytes in the byte array **/ -template byte_array byte_array::reverse() const +template byte_array byte_array::reverse() const { bytes_t bytes(values.size()); size_t offset = bytes.size() - 1; @@ -253,7 +271,7 @@ template byte_array byte_array std::vector byte_array::get_value() const +template std::vector byte_array::get_value() const { size_t length = values.size(); size_t num = (length); @@ -264,7 +282,7 @@ template std::vector byte_array std::string byte_array::get_string() const +template std::string byte_array::get_string() const { auto v = get_value(); return std::string(v.begin(), v.end()); @@ -277,8 +295,7 @@ template std::string byte_array::get * e.g. get_bit(1) corresponds to the second bit in the last, 'least significant' byte in the array. * **/ -template -bool_t byte_array::get_bit(size_t index_reversed) const +template bool_t byte_array::get_bit(size_t index_reversed) const { const size_t index = (values.size() * 8) - index_reversed - 1; const auto slice = split_byte(index); @@ -297,19 +314,17 @@ bool_t byte_array::get_bit(size_t index_revers * * Previously we did not reverse the bit index, but we have modified the behaviour to be consistent with `get_bit` * - * The rationale behind reversing the bit index is so that we can more naturally contain integers inside byte arrays and - *perform bit manipulation + * The rationale behind reversing the bit index is so that we can more naturally contain integers inside byte arrays + *and perform bit manipulation **/ -template -void byte_array::set_bit(size_t index_reversed, bool_t const& new_bit) +template void byte_array::set_bit(size_t index_reversed, bool_t const& new_bit) { const size_t index = (values.size() * 8) - index_reversed - 1; const auto slice = split_byte(index); const size_t byte_index = index / 8UL; const size_t bit_index = 7UL - (index % 8UL); - field_t scaled_new_bit = - field_t(new_bit) * barretenberg::fr(uint256_t(1) << bit_index); + field_t scaled_new_bit = field_t(new_bit) * barretenberg::fr(uint256_t(1) << bit_index); const auto new_value = slice.low.add_two(slice.high, scaled_new_bit).normalize(); values[byte_index] = new_value; } @@ -319,8 +334,8 @@ void byte_array::set_bit(size_t index_reversed, bool_t -typename byte_array::byte_slice byte_array::split_byte(const size_t index) const +template +typename byte_array::byte_slice byte_array::split_byte(const size_t index) const { const size_t byte_index = index / 8UL; const auto byte = values[byte_index]; @@ -335,30 +350,31 @@ typename byte_array::byte_slice byte_array::sp const uint64_t high_value = (bit_index == 7) ? 0ULL : (value >> (8 - num_high_bits)); if (byte.is_constant()) { - field_t low(context, low_value); - field_t high(context, high_value); - bool_t bit(context, static_cast(bit_value)); + field_t low(context, low_value); + field_t high(context, high_value); + bool_t bit(context, static_cast(bit_value)); return { low, high, bit }; } - field_t low = witness_t(context, low_value); - field_t high = witness_t(context, high_value); - bool_t bit = witness_t(context, static_cast(bit_value)); + field_t low = witness_t(context, low_value); + field_t high = witness_t(context, high_value); + bool_t bit = witness_t(context, static_cast(bit_value)); if (num_low_bits > 0) { - low.create_range_constraint(static_cast(num_low_bits)); + low.create_range_constraint(static_cast(num_low_bits), "byte_array: low bits split off incorrectly."); } else { low.assert_equal(0); } if (num_high_bits > 0) { - high.create_range_constraint(static_cast(num_high_bits)); + high.create_range_constraint(static_cast(num_high_bits), + "byte_array: high bits split off incorrectly."); } else { high.assert_equal(0); } - field_t scaled_high = high * barretenberg::fr(uint256_t(1) << (8ULL - num_high_bits)); - field_t scaled_bit = field_t(bit) * barretenberg::fr(uint256_t(1) << bit_index); - field_t result = low.add_two(scaled_high, scaled_bit); + field_t scaled_high = high * barretenberg::fr(uint256_t(1) << (8ULL - num_high_bits)); + field_t scaled_bit = field_t(bit) * barretenberg::fr(uint256_t(1) << bit_index); + field_t result = low.add_two(scaled_high, scaled_bit); result.assert_equal(byte); return { low, scaled_high, bit }; diff --git a/cpp/src/aztec/stdlib/primitives/byte_array/byte_array.hpp b/cpp/src/aztec/stdlib/primitives/byte_array/byte_array.hpp index df20ed95df..5f8fa327a4 100644 --- a/cpp/src/aztec/stdlib/primitives/byte_array/byte_array.hpp +++ b/cpp/src/aztec/stdlib/primitives/byte_array/byte_array.hpp @@ -10,7 +10,7 @@ template class byte_array { public: typedef std::vector> bytes_t; - byte_array(ComposerContext* parent_context); + byte_array(ComposerContext* parent_context = nullptr); byte_array(ComposerContext* parent_context, size_t const n); byte_array(ComposerContext* parent_context, std::string const& input); byte_array(ComposerContext* parent_context, std::vector const& input); @@ -37,7 +37,14 @@ template class byte_array { explicit operator field_t() const; + field_t operator[](const size_t index) const + { + assert(values.size() > 0); + return values[index]; + } + byte_array& write(byte_array const& other); + byte_array& write_at(byte_array const& other, size_t index); byte_array slice(size_t offset) const; byte_array slice(size_t offset, size_t length) const; @@ -51,6 +58,18 @@ template class byte_array { void set_bit(size_t index, bool_t const& value); + void set_byte(size_t index, const field_t& byte_val) + { + ASSERT(index < values.size()); + values[index] = byte_val; + } + + void set_context(ComposerContext* ctx) + { + ASSERT(context == nullptr); + context = ctx; + } + ComposerContext* get_context() const { return context; } std::vector get_value() const; @@ -85,4 +104,4 @@ inline std::ostream& operator<<(std::ostream& os, byte_array co EXTERN_STDLIB_TYPE(byte_array); } // namespace stdlib -} // namespace plonk \ No newline at end of file +} // namespace plonk diff --git a/cpp/src/aztec/stdlib/primitives/byte_array/byte_array.test.cpp b/cpp/src/aztec/stdlib/primitives/byte_array/byte_array.test.cpp index 9f7e280f6e..0a80e7ea71 100644 --- a/cpp/src/aztec/stdlib/primitives/byte_array/byte_array.test.cpp +++ b/cpp/src/aztec/stdlib/primitives/byte_array/byte_array.test.cpp @@ -1,19 +1,21 @@ #include "byte_array.hpp" #include #include +#include +// ULTRATODO: make these typed tests namespace test_stdlib_byte_array { using namespace barretenberg; using namespace plonk; - -typedef stdlib::bool_t bool_t; -typedef stdlib::field_t field_t; -typedef stdlib::witness_t witness_t; -typedef stdlib::byte_array byte_array; +typedef waffle::TurboComposer Composer; +typedef stdlib::bool_t bool_t; +typedef stdlib::field_t field_t; +typedef stdlib::witness_t witness_t; +typedef stdlib::byte_array byte_array; TEST(stdlib_byte_array, test_reverse) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); std::vector expected = { 0x04, 0x03, 0x02, 0x01 }; byte_array arr(&composer, std::vector{ 0x01, 0x02, 0x03, 0x04 }); @@ -23,7 +25,7 @@ TEST(stdlib_byte_array, test_reverse) TEST(stdlib_byte_array, test_string_constructor) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); std::string a = "ascii"; byte_array arr(&composer, a); EXPECT_EQ(arr.get_string(), a); @@ -31,7 +33,7 @@ TEST(stdlib_byte_array, test_string_constructor) TEST(stdlib_byte_array, test_ostream_operator) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); std::string a = "\1\2\3a"; byte_array arr(&composer, a); std::ostringstream os; @@ -41,7 +43,7 @@ TEST(stdlib_byte_array, test_ostream_operator) TEST(stdlib_byte_array, test_byte_array_input_output_consistency) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); fr a_expected = fr::random_element(); fr b_expected = fr::random_element(); @@ -71,7 +73,7 @@ TEST(stdlib_byte_array, test_byte_array_input_output_consistency) TEST(stdlib_byte_array, get_bit) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); byte_array arr(&composer, std::vector{ 0x01, 0x02, 0x03, 0x04 }); EXPECT_EQ(arr.get_bit(0).get_value(), false); @@ -103,7 +105,7 @@ TEST(stdlib_byte_array, get_bit) TEST(stdlib_byte_array, set_bit) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); byte_array arr(&composer, std::vector{ 0x01, 0x02, 0x03, 0x04 }); arr.set_bit(16, bool_t(witness_t(&composer, true))); diff --git a/cpp/src/aztec/stdlib/primitives/composers/composers.hpp b/cpp/src/aztec/stdlib/primitives/composers/composers.hpp index 657e3281e1..929b748a2f 100644 --- a/cpp/src/aztec/stdlib/primitives/composers/composers.hpp +++ b/cpp/src/aztec/stdlib/primitives/composers/composers.hpp @@ -1,14 +1,27 @@ #pragma once #include #include -#include +#include #define INSTANTIATE_STDLIB_TYPE(stdlib_type) \ template class stdlib_type; \ template class stdlib_type; \ - template class stdlib_type; + template class stdlib_type; #define INSTANTIATE_STDLIB_TYPE_VA(stdlib_type, ...) \ template class stdlib_type; \ template class stdlib_type; \ - template class stdlib_type; + template class stdlib_type; + +#define INSTANTIATE_STDLIB_BASIC_TYPE(stdlib_type) \ + template class stdlib_type; \ + template class stdlib_type; + +#define INSTANTIATE_STDLIB_BASIC_TYPE_VA(stdlib_type, ...) \ + template class stdlib_type; \ + template class stdlib_type; + +#define INSTANTIATE_STDLIB_ULTRA_TYPE(stdlib_type) template class stdlib_type; + +#define INSTANTIATE_STDLIB_ULTRA_TYPE_VA(stdlib_type, ...) \ + template class stdlib_type; diff --git a/cpp/src/aztec/stdlib/primitives/composers/composers_fwd.hpp b/cpp/src/aztec/stdlib/primitives/composers/composers_fwd.hpp index 228f7a0761..6a809f8bb0 100644 --- a/cpp/src/aztec/stdlib/primitives/composers/composers_fwd.hpp +++ b/cpp/src/aztec/stdlib/primitives/composers/composers_fwd.hpp @@ -3,15 +3,28 @@ namespace waffle { class StandardComposer; class TurboComposer; -class PlookupComposer; +class UltraComposer; } // namespace waffle #define EXTERN_STDLIB_TYPE(stdlib_type) \ extern template class stdlib_type; \ extern template class stdlib_type; \ - extern template class stdlib_type; + extern template class stdlib_type; #define EXTERN_STDLIB_TYPE_VA(stdlib_type, ...) \ extern template class stdlib_type; \ extern template class stdlib_type; \ - extern template class stdlib_type; + extern template class stdlib_type; + +#define EXTERN_STDLIB_BASIC_TYPE(stdlib_type) \ + extern template class stdlib_type; \ + extern template class stdlib_type; + +#define EXTERN_STDLIB_BASIC_TYPE_VA(stdlib_type, ...) \ + extern template class stdlib_type; \ + extern template class stdlib_type; + +#define EXTERN_STDLIB_ULTRA_TYPE(stdlib_type) extern template class stdlib_type; + +#define EXTERN_STDLIB_ULTRA_TYPE_VA(stdlib_type, ...) \ + extern template class stdlib_type; diff --git a/cpp/src/aztec/stdlib/primitives/curves/bn254.hpp b/cpp/src/aztec/stdlib/primitives/curves/bn254.hpp index 3293e28230..ba7d86ba0e 100644 --- a/cpp/src/aztec/stdlib/primitives/curves/bn254.hpp +++ b/cpp/src/aztec/stdlib/primitives/curves/bn254.hpp @@ -1,5 +1,5 @@ #pragma once - +#include #include "../bigfield/bigfield.hpp" #include "../biggroup/biggroup.hpp" #include "../field/field.hpp" @@ -8,20 +8,24 @@ namespace plonk { namespace stdlib { template struct bn254 { + static constexpr waffle::CurveType type = waffle::CurveType::BN254; + + typedef barretenberg::fq fq; + typedef barretenberg::fr fr; + typedef barretenberg::g1 g1; + typedef ComposerType Composer; - typedef bigfield fq_ct; + typedef witness_t witness_ct; + typedef public_witness_t public_witness_ct; typedef field_t fr_ct; - typedef bool_t bool_ct; typedef byte_array byte_array_ct; + typedef bool_t bool_ct; typedef stdlib::uint32 uint32_ct; - typedef witness_t witness_ct; - typedef public_witness_t public_witness_ct; - typedef element g1_ct; + + typedef bigfield fq_ct; typedef bigfield bigfr_ct; - typedef element g1_bigfr_ct; - typedef barretenberg::g1 g1_base_t; - typedef barretenberg::fq fq_base_t; - typedef barretenberg::fr fr_base_t; + typedef element g1_ct; + typedef element g1_bigfr_ct; }; // namespace bn254 } // namespace stdlib diff --git a/cpp/src/aztec/stdlib/primitives/curves/secp256k1.hpp b/cpp/src/aztec/stdlib/primitives/curves/secp256k1.hpp new file mode 100644 index 0000000000..d6df25470d --- /dev/null +++ b/cpp/src/aztec/stdlib/primitives/curves/secp256k1.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "../bigfield/bigfield.hpp" +#include "../biggroup/biggroup.hpp" +#include "../field/field.hpp" + +#include + +namespace plonk { +namespace stdlib { + +template struct secp256k1 { + static constexpr waffle::CurveType type = waffle::CurveType::SECP256K1; + + typedef ::secp256k1::fq fq; + typedef ::secp256k1::fr fr; + typedef ::secp256k1::g1 g1; + + typedef ComposerType Composer; + typedef witness_t witness_ct; + typedef public_witness_t public_witness_ct; + typedef field_t fr_ct; + typedef byte_array byte_array_ct; + typedef bool_t bool_ct; + typedef stdlib::uint32 uint32_ct; + + typedef bigfield fq_ct; + typedef bigfield bigfr_ct; + typedef element g1_ct; + typedef element g1_bigfr_ct; +}; +} // namespace stdlib +} // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/curves/secp256r1.hpp b/cpp/src/aztec/stdlib/primitives/curves/secp256r1.hpp index 9e3383578d..1a0979491e 100644 --- a/cpp/src/aztec/stdlib/primitives/curves/secp256r1.hpp +++ b/cpp/src/aztec/stdlib/primitives/curves/secp256r1.hpp @@ -3,23 +3,31 @@ #include "../bigfield/bigfield.hpp" #include "../biggroup/biggroup.hpp" #include "../field/field.hpp" + #include namespace plonk { namespace stdlib { -template struct secp256r1_ct { +template struct secp256r1 { + static constexpr waffle::CurveType type = waffle::CurveType::SECP256R1; + + typedef ::secp256r1::fq fq; + typedef ::secp256r1::fr fr; + typedef ::secp256r1::g1 g1; + typedef ComposerType Composer; - typedef bigfield fq_ct; - typedef field_t fr_ct; typedef witness_t witness_ct; - typedef element g1_ct; - typedef bigfield bigfr_ct; - typedef element g1_bigfr_ct; - typedef secp256r1::g1 g1_base_t; - typedef secp256r1::fq fq_base_t; - typedef secp256r1::fr fr_base_t; + typedef public_witness_t public_witness_ct; + typedef field_t fr_ct; + typedef byte_array byte_array_ct; + typedef bool_t bool_ct; + typedef stdlib::uint32 uint32_ct; -}; // namespace secp256r1 + typedef bigfield fq_ct; + typedef bigfield bigfr_ct; + typedef element g1_ct; + typedef element g1_bigfr_ct; +}; } // namespace stdlib } // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/field/field.cpp b/cpp/src/aztec/stdlib/primitives/field/field.cpp index 1a526e3a49..0bd4df71fb 100644 --- a/cpp/src/aztec/stdlib/primitives/field/field.cpp +++ b/cpp/src/aztec/stdlib/primitives/field/field.cpp @@ -3,7 +3,6 @@ #include "../bool/bool.hpp" #include "../composers/composers.hpp" #include "../../../rollup/constants.hpp" -#include "pow.hpp" #include "../../../rollup/constants.hpp" // #pragma GCC diagnostic ignored "-Wunused-variable" @@ -62,7 +61,7 @@ field_t field_t::from_witness_index(ComposerCo return result; } -template field_t::operator bool_t() +template field_t::operator bool_t() const { if (witness_index == IS_CONSTANT) { bool_t result(context); @@ -153,19 +152,66 @@ field_t field_t::operator*(const field_t& othe ASSERT(ctx || (witness_index == IS_CONSTANT && other.witness_index == IS_CONSTANT)); if (witness_index == IS_CONSTANT && other.witness_index == IS_CONSTANT) { - // both inputs are constant - don't add a gate + // Both inputs are constant - don't add a gate. + // The value of a constant is tracked in `.additive_constant`. result.additive_constant = additive_constant * other.additive_constant; } else if (witness_index != IS_CONSTANT && other.witness_index == IS_CONSTANT) { - // one input is constant - don't add a gate, but update scaling factors + // One input is constant: don't add a gate, but update scaling factors. + + /** + * Let: + * a := this; + * b := other; + * a.v := ctx->variables[this.witness_index]; + * b.v := ctx->variables[other.witness_index]; + * .mul = .multiplicative_constant + * .add = .additive_constant + */ + + /** + * Value of this = a.v * a.mul + a.add; + * Value of other = b.add + * Value of result = a * b = a.v * [a.mul * b.add] + [a.add * b.add] + * ^ ^result.mul ^result.add + * ^result.v + */ + result.additive_constant = additive_constant * other.additive_constant; result.multiplicative_constant = multiplicative_constant * other.additive_constant; result.witness_index = witness_index; } else if (witness_index == IS_CONSTANT && other.witness_index != IS_CONSTANT) { + // One input is constant: don't add a gate, but update scaling factors. + + /** + * Value of this = a.add; + * Value of other = b.v * b.mul + b.add + * Value of result = a * b = b.v * [a.add * b.mul] + [a.add * b.add] + * ^ ^result.mul ^result.add + * ^result.v + */ + result.additive_constant = additive_constant * other.additive_constant; result.multiplicative_constant = other.multiplicative_constant * additive_constant; result.witness_index = other.witness_index; } else { - // both inputs map to circuit varaibles - create a * constraint + // Both inputs map to circuit varaibles: create a `*` constraint. + + /** + * Value of this = a.v * a.mul + a.add; + * Value of other = b.v * b.mul + b.add; + * Value of result = a * b + * = [a.v * b.v] * [a.mul * b.mul] + a.v * [a.mul * b.add] + b.v * [a.add * b.mul] + [a.ac * b.add] + * = [a.v * b.v] * [ q_m ] + a.v * [ q_l ] + b.v * [ q_r ] + [ q_c ] + * ^ ^Notice the add/mul_constants form selectors when a gate is created. + * | Only the witnesses (pointed-to by the witness_indexes) form the wires in/out of + * | the gate. + * ^This entire value is pushed to ctx->variables as a new witness. The + * implied additive & multiplicative constants of the new witness are 0 & 1 resp. + * Left wire value: a.v + * Right wire value: b.v + * Output wire value: result.v (with q_o = -1) + */ + barretenberg::fr T0; barretenberg::fr q_m; barretenberg::fr q_l; @@ -189,9 +235,14 @@ field_t field_t::operator*(const field_t& othe out += T0; out += q_c; result.witness_index = ctx->add_variable(out); - const waffle::poly_triple gate_coefficients{ - witness_index, other.witness_index, result.witness_index, q_m, q_l, q_r, barretenberg::fr::neg_one(), q_c - }; + const waffle::poly_triple gate_coefficients{ .a = witness_index, + .b = other.witness_index, + .c = result.witness_index, + .q_m = q_m, + .q_l = q_l, + .q_r = q_r, + .q_o = barretenberg::fr::neg_one(), + .q_c = q_c }; ctx->create_poly_gate(gate_coefficients); } return result; @@ -240,9 +291,14 @@ field_t field_t::divide_no_zero_check(const fi barretenberg::fr q_c = -get_value(); barretenberg::fr out_value = get_value() / other.get_value(); result.witness_index = ctx->add_variable(out_value); - const waffle::poly_triple gate_coefficients{ - result.witness_index, other.witness_index, result.witness_index, q_m, q_l, 0, 0, q_c - }; + const waffle::poly_triple gate_coefficients{ .a = result.witness_index, + .b = other.witness_index, + .c = result.witness_index, + .q_m = q_m, + .q_l = q_l, + .q_r = 0, + .q_o = 0, + .q_c = q_c }; ctx->create_poly_gate(gate_coefficients); } } else { @@ -281,13 +337,60 @@ field_t field_t::divide_no_zero_check(const fi barretenberg::fr q_o = -multiplicative_constant; barretenberg::fr q_c = -additive_constant; - const waffle::poly_triple gate_coefficients{ - result.witness_index, other.witness_index, witness_index, q_m, q_l, q_r, q_o, q_c - }; + const waffle::poly_triple gate_coefficients{ .a = result.witness_index, + .b = other.witness_index, + .c = witness_index, + .q_m = q_m, + .q_l = q_l, + .q_r = q_r, + .q_o = q_o, + .q_c = q_c }; ctx->create_poly_gate(gate_coefficients); } return result; } +/** + * @brief raise a field_t to a power of an exponent (field_t). Note that the exponent must not exceed 32 bits and is + * implicitly range constrained. + * + * @returns this ** (exponent) + */ +template +field_t field_t::pow(const field_t& exponent) const +{ + auto* ctx = get_context() ? get_context() : exponent.get_context(); + + bool exponent_constant = exponent.is_constant(); + + uint256_t exponent_value = exponent.get_value(); + std::vector> exponent_bits(32); + 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]); + 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 accumulator(ctx, 1); + field_t mul_coefficient = *this - 1; + for (size_t i = 0; i < 32; ++i) { + accumulator *= accumulator; + const auto bit = exponent_bits[i]; + accumulator *= (mul_coefficient * bit + 1); + } + accumulator = accumulator.normalize(); + return accumulator; +} /** * @returns `this * to_mul + to_add` @@ -303,13 +406,31 @@ field_t field_t::madd(const field_t& to_mul, c return ((*this) * to_mul + to_add); } - // (a * Q_a + R_a) * (b * Q_b + R_b) + (c * Q_c R_c) = result + // Let: + // a = this; + // b = to_mul; + // c = to_add; + // a.v = ctx->variables[this.witness_index]; + // b.v = ctx->variables[to_mul.witness_index]; + // c.v = ctx->variables[to_add.witness_index]; + // .mul = .multiplicative_constant; + // .add = .additive_constant. + // + // result = a * b + c + // = (a.v * a.mul + a.add) * (b.v * b.mul + b.add) + (c.v * c.mul + c.add) + // = a.v * b.v * [a.mul * b.mul] + a.v * [a.mul * b.add] + b.v * [b.mul + a.add] + c.v * [c.mul] + + // [a.add * b.add + c.add] + // = a.v * b.v * [ q_m ] + a.v * [ q_1 ] + b.v * [ q_2 ] + c.v * [ q_3 ] + [ q_c ] + barretenberg::fr q_m = multiplicative_constant * to_mul.multiplicative_constant; barretenberg::fr q_1 = multiplicative_constant * to_mul.additive_constant; barretenberg::fr q_2 = to_mul.multiplicative_constant * additive_constant; barretenberg::fr q_3 = to_add.multiplicative_constant; barretenberg::fr q_c = additive_constant * to_mul.additive_constant + to_add.additive_constant; + // Note: the value of a constant field_t is wholly tracked by the field_t's `additive_constant` member, which is + // accounted for in the above-calculated selectors (`q_`'s). Therefore no witness (`variables[witness_index]`) + // exists for constants, and so the field_t's corresponding wire value is set to `0` in the gate equation. barretenberg::fr a = witness_index == IS_CONSTANT ? barretenberg::fr(0) : ctx->get_variable(witness_index); barretenberg::fr b = to_mul.witness_index == IS_CONSTANT ? barretenberg::fr(0) : ctx->get_variable(to_mul.witness_index); @@ -322,16 +443,16 @@ field_t field_t::madd(const field_t& to_mul, c result.witness_index = ctx->add_variable(out); const waffle::mul_quad gate_coefficients{ - witness_index == IS_CONSTANT ? ctx->zero_idx : witness_index, - to_mul.witness_index == IS_CONSTANT ? ctx->zero_idx : to_mul.witness_index, - to_add.witness_index == IS_CONSTANT ? ctx->zero_idx : to_add.witness_index, - result.witness_index, - q_m, - q_1, - q_2, - q_3, - -barretenberg::fr(1), - q_c, + .a = witness_index == IS_CONSTANT ? ctx->zero_idx : witness_index, + .b = to_mul.witness_index == IS_CONSTANT ? ctx->zero_idx : to_mul.witness_index, + .c = to_add.witness_index == IS_CONSTANT ? ctx->zero_idx : to_add.witness_index, + .d = result.witness_index, + .mul_scaling = q_m, + .a_scaling = q_1, + .b_scaling = q_2, + .c_scaling = q_3, + .d_scaling = -barretenberg::fr(1), + .const_scaling = q_c, }; ctx->create_big_mul_gate(gate_coefficients); return result; @@ -363,16 +484,16 @@ field_t field_t::add_two(const field_t& add_a, result.witness_index = ctx->add_variable(out); const waffle::mul_quad gate_coefficients{ - witness_index == IS_CONSTANT ? ctx->zero_idx : witness_index, - add_a.witness_index == IS_CONSTANT ? ctx->zero_idx : add_a.witness_index, - add_b.witness_index == IS_CONSTANT ? ctx->zero_idx : add_b.witness_index, - result.witness_index, - barretenberg::fr(0), - q_1, - q_2, - q_3, - -barretenberg::fr(1), - q_c, + .a = witness_index == IS_CONSTANT ? ctx->zero_idx : witness_index, + .b = add_a.witness_index == IS_CONSTANT ? ctx->zero_idx : add_a.witness_index, + .c = add_b.witness_index == IS_CONSTANT ? ctx->zero_idx : add_b.witness_index, + .d = result.witness_index, + .mul_scaling = barretenberg::fr(0), + .a_scaling = q_1, + .b_scaling = q_2, + .c_scaling = q_3, + .d_scaling = -barretenberg::fr(1), + .const_scaling = q_c, }; ctx->create_big_mul_gate(gate_coefficients); return result; @@ -385,6 +506,10 @@ template field_t field_tvariables[this.witness_index] + // Normalised result = result.v * 1 + 0; // where result.v = this.v * this.mul + this.add + // We need a new gate to enforce that the `result` was correctly calculated from `this`. + field_t result(context); barretenberg::fr value = context->get_variable(witness_index); barretenberg::fr out; @@ -394,10 +519,19 @@ template field_t field_tadd_variable(out); result.additive_constant = barretenberg::fr::zero(); result.multiplicative_constant = barretenberg::fr::one(); - const waffle::add_triple gate_coefficients{ - witness_index, witness_index, result.witness_index, multiplicative_constant, 0, barretenberg::fr::neg_one(), - additive_constant - }; + + // Aim of new gate: this.v * this.mul + this.add == result.v + // <=> this.v * [this.mul] + result.v * [ -1] + [this.add] == 0 + // <=> this.v * this.v * [ 0 ] + this.v * [this.mul] + this.v * [ 0 ] + result.v * [ -1] + [this.add] == 0 + // <=> this.v * this.v * [q_m] + this.v * [ q_l ] + this.v * [q_r] + result.v * [q_o] + [ q_c ] == 0 + + const waffle::add_triple gate_coefficients{ .a = witness_index, + .b = witness_index, + .c = result.witness_index, + .a_scaling = multiplicative_constant, + .b_scaling = 0, + .c_scaling = barretenberg::fr::neg_one(), + .const_scaling = additive_constant }; context->create_add_gate(gate_coefficients); return result; @@ -406,8 +540,7 @@ template field_t field_t void field_t::assert_is_zero(std::string const& msg) const { if (get_value() != barretenberg::fr(0)) { - context->failed = true; - context->err = msg; + context->failure(msg); } if (witness_index == IS_CONSTANT) { @@ -415,10 +548,21 @@ template void field_t::assert_is_zer return; } + // Aim of new gate: this.v * this.mul + this.add == 0 + // I.e.: + // this.v * 0 * [ 0 ] + this.v * [this.mul] + 0 * [ 0 ] + 0 * [ 0 ] + [this.add] == 0 + // this.v * 0 * [q_m] + this.v * [ q_l ] + 0 * [q_r] + 0 * [q_o] + [ q_c ] == 0 + ComposerContext* ctx = context; const waffle::poly_triple gate_coefficients{ - witness_index, ctx->zero_idx, ctx->zero_idx, barretenberg::fr(0), - multiplicative_constant, barretenberg::fr(0), barretenberg::fr(0), additive_constant, + .a = witness_index, + .b = ctx->zero_idx, + .c = ctx->zero_idx, + .q_m = barretenberg::fr(0), + .q_l = multiplicative_constant, + .q_r = barretenberg::fr(0), + .q_o = barretenberg::fr(0), + .q_c = additive_constant, }; context->create_poly_gate(gate_coefficients); } @@ -426,33 +570,40 @@ template void field_t::assert_is_zer template void field_t::assert_is_not_zero(std::string const& msg) const { if (get_value() == barretenberg::fr(0)) { - context->failed = true; - context->err = msg; + context->failure(msg); + // We don't return; we continue with the function, for debugging purposes. } if (witness_index == IS_CONSTANT) { ASSERT(additive_constant != barretenberg::fr(0)); return; } + ComposerContext* ctx = context; if (get_value() == 0 && ctx) { - ctx->failed = true; - ctx->err = msg; + ctx->failure(msg); } + barretenberg::fr inverse_value = (get_value() == 0) ? 0 : get_value().invert(); field_t inverse(witness_t(ctx, inverse_value)); + // Aim of new gate: `this` has an inverse (hence is not zero). + // I.e.: + // (this.v * this.mul + this.add) * inverse.v == 1; + // <=> this.v * inverse.v * [this.mul] + this.v * [ 0 ] + inverse.v * [this.add] + 0 * [ 0 ] + [ -1] == 0 + // <=> this.v * inverse.v * [ q_m ] + this.v * [q_l] + inverse.v * [ q_r ] + 0 * [q_o] + [q_c] == 0 + // (a * mul_const + add_const) * b - 1 = 0 const waffle::poly_triple gate_coefficients{ - witness_index, // input value - inverse.witness_index, // inverse - ctx->zero_idx, // no output - multiplicative_constant, // a * b * mul_const - barretenberg::fr(0), // a * 0 - additive_constant, // b * mul_const - barretenberg::fr(0), // c * 0 - barretenberg::fr(-1), // -1 + .a = witness_index, // input value + .b = inverse.witness_index, // inverse + .c = ctx->zero_idx, // no output + .q_m = multiplicative_constant, // a * b * mul_const + .q_l = barretenberg::fr(0), // a * 0 + .q_r = additive_constant, // b * mul_const + .q_o = barretenberg::fr(0), // c * 0 + .q_c = barretenberg::fr(-1), // -1 }; context->create_poly_gate(gate_coefficients); } @@ -492,17 +643,27 @@ template bool_t field_tcreate_poly_gate(gate_coefficients_a); // is_zero * k_inverse - is_zero = 0 q_o = barretenberg::fr::neg_one(); q_c = barretenberg::fr::zero(); - const waffle::poly_triple gate_coefficients_b{ - is_zero.witness_index, k_inverse.witness_index, is_zero.witness_index, q_m, q_l, q_r, q_o, q_c - }; + const waffle::poly_triple gate_coefficients_b{ .a = is_zero.witness_index, + .b = k_inverse.witness_index, + .c = is_zero.witness_index, + .q_m = q_m, + .q_l = q_l, + .q_r = q_r, + .q_o = q_o, + .q_c = q_c }; context->create_poly_gate(gate_coefficients_b); return is_zero; } @@ -513,6 +674,7 @@ template barretenberg::fr field_t::g ASSERT(context != nullptr); return (multiplicative_constant * context->get_variable(witness_index)) + additive_constant; } else { + // A constant field_t's value is tracked wholly by its additive_constant member. return additive_constant; } } @@ -580,7 +742,10 @@ void field_t::create_range_constraint(const size_t num_bits, st ASSERT(uint256_t(get_value()).get_msb() < num_bits); } else { if constexpr (ComposerContext::type == waffle::ComposerType::PLOOKUP) { - context->decompose_into_default_range(normalize().get_witness_index(), num_bits, msg); + context->decompose_into_default_range(normalize().get_witness_index(), + num_bits, + waffle::UltraComposer::DEFAULT_PLOOKUP_RANGE_BITNUM, + msg); } else { context->decompose_into_base4_accumulators(normalize().get_witness_index(), num_bits, msg); } @@ -712,6 +877,42 @@ field_t field_t::select_from_three_bit_table(c return R6; } +template +void field_t::evaluate_linear_identity(const field_t& a, + const field_t& b, + const field_t& c, + const field_t& d) +{ + ComposerContext* ctx = a.context == nullptr + ? (b.context == nullptr ? (c.context == nullptr ? d.context : c.context) : b.context) + : a.context; + + if (a.witness_index == IS_CONSTANT && b.witness_index == IS_CONSTANT && c.witness_index == IS_CONSTANT && + d.witness_index == IS_CONSTANT) { + return; + } + + // validate that a + b + c + d = 0 + barretenberg::fr q_1 = a.multiplicative_constant; + barretenberg::fr q_2 = b.multiplicative_constant; + barretenberg::fr q_3 = c.multiplicative_constant; + barretenberg::fr q_4 = d.multiplicative_constant; + barretenberg::fr q_c = a.additive_constant + b.additive_constant + c.additive_constant + d.additive_constant; + + const waffle::add_quad gate_coefficients{ + a.witness_index == IS_CONSTANT ? ctx->zero_idx : a.witness_index, + b.witness_index == IS_CONSTANT ? ctx->zero_idx : b.witness_index, + c.witness_index == IS_CONSTANT ? ctx->zero_idx : c.witness_index, + d.witness_index == IS_CONSTANT ? ctx->zero_idx : d.witness_index, + q_1, + q_2, + q_3, + q_4, + q_c, + }; + ctx->create_big_add_gate(gate_coefficients); +} + template void field_t::evaluate_polynomial_identity(const field_t& a, const field_t& b, @@ -750,7 +951,9 @@ void field_t::evaluate_polynomial_identity(const field_t& a, ctx->create_big_mul_gate(gate_coefficients); } -// compute sum of inputs +/** + * Compute sum of inputs + */ template field_t field_t::accumulate(const std::vector& input) { @@ -758,7 +961,7 @@ field_t field_t::accumulate(const std::vector< return field_t(nullptr, 0); } if (input.size() == 1) { - return input[0]; + return input[0]; //.normalize(); } /** * If we are using UltraComposer, we can accumulate 3 values into a sum per gate. @@ -782,7 +985,65 @@ field_t field_t::accumulate(const std::vector< * * If num elements is not a multiple of 3, the final gate will be padded with zero_idx wires **/ - if constexpr (ComposerContext::type == waffle::TURBO) { + if constexpr (ComposerContext::type == waffle::PLOOKUP) { + ComposerContext* ctx = nullptr; + std::vector accumulator; + field_t constant_term = 0; + + // Step 1: remove constant terms from input field elements + for (const auto& element : input) { + if (element.is_constant()) { + constant_term += element; + } else { + accumulator.emplace_back(element); + } + ctx = (element.get_context() ? element.get_context() : ctx); + } + if (accumulator.size() == 0) { + return constant_term; + } else if (accumulator.size() != input.size()) { + accumulator[0] += constant_term; + } + + // Step 2: compute output value + size_t num_elements = accumulator.size(); + barretenberg::fr output = 0; + for (const auto& acc : accumulator) { + output += acc.get_value(); + } + + // Step 3: pad accumulator to be a multiple of 3 + const size_t num_padding_wires = (num_elements % 3) == 0 ? 0 : 3 - (num_elements % 3); + for (size_t i = 0; i < num_padding_wires; ++i) { + accumulator.emplace_back(field_t::from_witness_index(ctx, ctx->zero_idx)); + } + num_elements = accumulator.size(); + const size_t num_gates = (num_elements / 3); + + field_t total = witness_t(ctx, output); + field_t accumulating_total = total; + + for (size_t i = 0; i < num_gates; ++i) { + ctx->create_big_add_gate( + { + accumulator[3 * i].get_witness_index(), + accumulator[3 * i + 1].get_witness_index(), + accumulator[3 * i + 2].get_witness_index(), + accumulating_total.witness_index, + accumulator[3 * i].multiplicative_constant, + accumulator[3 * i + 1].multiplicative_constant, + accumulator[3 * i + 2].multiplicative_constant, + -1, + accumulator[3 * i].additive_constant + accumulator[3 * i + 1].additive_constant + + accumulator[3 * i + 2].additive_constant, + }, + ((i == num_gates - 1) ? false : true)); + barretenberg::fr new_total = accumulating_total.get_value() - accumulator[3 * i].get_value() - + accumulator[3 * i + 1].get_value() - accumulator[3 * i + 2].get_value(); + accumulating_total = witness_t(ctx, new_total); + } + return total.normalize(); + } else if constexpr (ComposerContext::type == waffle::TURBO) { field_t total(0); bool odd_number = (input.size() & 0x01UL) == 0x01ULL; @@ -806,6 +1067,8 @@ template std::array, 3> field_t::slice(const uint8_t msb, const uint8_t lsb) const { ASSERT(msb >= lsb); + ASSERT(msb < rollup::MAX_NO_WRAP_INTEGER_BIT_LENGTH); // CODY: eek! Why is rollup info here? function input arg + // msb_bound or something const field_t lhs = *this; ComposerContext* ctx = lhs.get_context(); @@ -837,6 +1100,26 @@ std::array, 3> field_t::slice(const ui /** * @brief Build a circuit allowing a user to prove that they have deomposed `this` into bits. + * + * @details A bit vector `result` is extracted and used to to construct a sum `sum` using the normal binary expansion. + * Along the way, we extract a value `shifted_high_limb` which is equal to `sum_hi` in the natural decomposition + * `sum = sum_lo + 2**128*sum_hi`. + * We impose a copy constraint between `sum` and `this` but that only imposes equality in `Fr`; it could be that + * `result` has overflowed the modulus `r`. To impose a unique value of `result`, we constrain `sum` to satisfy `r - 1 + * >= sum >= 0`. In order to do this inside of `Fr`, we must reduce break the check down in the smaller checks so that + * we can check non-negativity of integers using range constraints in Fr. + * + * At circuit compilation time we build the decomposition `r - 1 = p_lo + 2**128*p_hi`. Then desired schoolbook + * subtraction is + * p_hi - b | p_lo + b*2**128 (++foo++ is foo crossed out) + * ++p_hi++ | ++p_lo++ (b = 0, 1) + * - | + * sum_hi | sum_lo + * ------------------------------------------------- + * y_lo := p_hi - b - sum_hi | y_hi := p_lo + b*2**128 - sum_lo + * + * Here `b` is the boolean "a carry is necessary". Each of the resulting values can be checked for underflow by imposing + * a small range constraint, since the negative of a small value in `Fr` is a large value in `Fr`. */ template std::vector> field_t::decompose_into_bits( @@ -849,6 +1132,8 @@ std::vector> field_t::decompose_into_bi const uint256_t val_u256 = static_cast(get_value()); field_t sum(context, 0); field_t shifted_high_limb(context, 0); // will equal high 128 bits, left shifted by 128 bits + // TODO: Guido will make a PR that will fix an error here; hard-coded 127 is incorrect when 128 < num_bits < 256. + // Extract bit vector and show that it has the same value as `this`. for (size_t i = 0; i < num_bits; ++i) { bool_t bit = get_bit(context, num_bits - 1 - i, val_u256); result[num_bits - 1 - i] = bit; @@ -860,24 +1145,38 @@ std::vector> field_t::decompose_into_bi shifted_high_limb = sum; } - this->assert_equal(sum); // note: `this` and `sum` both normalized here. + this->assert_equal(sum); // `this` and `sum` are both normalized here. constexpr uint256_t modulus_minus_one = fr::modulus - 1; auto modulus_bits = modulus_minus_one.get_msb() + 1; - // if value can be larger than modulus we must enforce unique representation + // If value can be larger than modulus we must enforce unique representation if (num_bits >= modulus_bits) { + // r - 1 = p_lo + 2**128 * p_hi const fr p_lo = modulus_minus_one.slice(0, 128); const fr p_hi = modulus_minus_one.slice(128, 256); - const fr shift = fr(uint256_t(1) << 128); + // `shift` is used to shift high limbs. It has the dual purpose of representing a borrowed bit. + const fr shift = fr(uint256_t(1) << 128); + // We always borrow from 2**128*p_hi. We handle whether this was necessary later. + // y_lo = (2**128 + p_lo) - sum_lo field_t y_lo = (-sum) + (p_lo + shift); y_lo += shifted_high_limb; - const auto low_accumulators = context->decompose_into_base4_accumulators(y_lo.normalize().witness_index, 130); - field_t y_borrow = - -(field_t::from_witness_index(context, low_accumulators[0]) - 1); - + y_lo.normalize(); + + // A carry was necessary if and only if the 128th bit y_lo_hi of y_lo is 0. + auto [y_lo_lo, y_lo_hi, zeros] = y_lo.slice(128, 128); + // This copy constraint, along with the constraints of field_t::slice, imposes that y_lo has bit length 129. + // Since the integer y_lo is at least -2**128+1, which has more than 129 bits in `Fr`, the implicit range + // constraint shows that y_lo is non-negative. + context->assert_equal( + zeros.witness_index, context->zero_idx, "field_t: bit decomposition_fails: high limb non-zero"); + // y_borrow is the boolean "a carry was necessary" + field_t y_borrow = -(y_lo_hi - 1); + // If a carry was necessary, subtract that carry from p_hi + // y_hi = (p_hi - y_borrow) - sum_hi field_t y_hi = -(shifted_high_limb / shift) + (p_hi); y_hi -= y_borrow; - y_hi.create_range_constraint(128); + // As before, except that now the range constraint is explicit, this shows that y_hi is non-negative. + y_hi.create_range_constraint(128, "field_t: bit decomposition fails: y_hi is too large."); } return result; diff --git a/cpp/src/aztec/stdlib/primitives/field/field.hpp b/cpp/src/aztec/stdlib/primitives/field/field.hpp index f12a764453..d71e8324fd 100644 --- a/cpp/src/aztec/stdlib/primitives/field/field.hpp +++ b/cpp/src/aztec/stdlib/primitives/field/field.hpp @@ -45,15 +45,14 @@ template class field_t { witness_index = IS_CONSTANT; } - field_t(uint256_t const& value) + field_t(const barretenberg::fr& value) : context(nullptr) - { - additive_constant = barretenberg::fr(value); - multiplicative_constant = barretenberg::fr(0); - witness_index = IS_CONSTANT; - } + , additive_constant(value) + , multiplicative_constant(barretenberg::fr(1)) + , witness_index(IS_CONSTANT) + {} - field_t(const barretenberg::fr& value) + field_t(const uint256_t& value) : context(nullptr) , additive_constant(value) , multiplicative_constant(barretenberg::fr(1)) @@ -83,7 +82,7 @@ template class field_t { static field_t from_witness_index(ComposerContext* parent_context, const uint32_t witness_index); - explicit operator bool_t(); + explicit operator bool_t() const; field_t& operator=(const field_t& other) { @@ -111,6 +110,9 @@ template class field_t { field_t sqr() const { return operator*(*this); } + // N.B. we implicitly range-constrain 'other' to be a 32-bit integer! + field_t pow(const field_t& exponent) const; + field_t operator+=(const field_t& other) { *this = *this + other; @@ -181,6 +183,7 @@ template class field_t { const bool_t& t1, const bool_t& t0); + static void evaluate_linear_identity(const field_t& a, const field_t& b, const field_t& c, const field_t& d); static void evaluate_polynomial_identity(const field_t& a, const field_t& b, const field_t& c, const field_t& d); static field_t accumulate(const std::vector& to_add); @@ -197,11 +200,17 @@ template class field_t { bool_t operator!=(const field_t& other) const; /** - * normalize returns a field_t element where `multiplicative_constant = 1` and `additive_constant = 0` - * i.e. the value is defined entirely by the composer variable that `witness_index` points to - * If the witness_index is ever needed, `normalize` should be called first + * normalize returns a field_t element with equivalent value to `this`, but where `multiplicative_constant = 1` and + *`additive_constant = 0`. + * I.e. the returned value is defined entirely by the composer variable that `witness_index` points to (no scaling + * factors). * - * Will cost 1 constraint if the field element is not already normalized (or is constant) + * If the witness_index of `this` is ever needed, `normalize` should be called first. + * + * Will cost 1 constraint if the field element is not already normalized, as a new witness value would need to be + * created. + * 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; @@ -238,6 +247,11 @@ template class field_t { context->fix_witness(witness_index, get_value()); } + static field_t from_witness(ComposerContext* ctx, const barretenberg::fr& input) + { + return field_t(witness_t(ctx, input)); + } + /** * Fix a witness. The value of the witness is constrained with a selector * */ @@ -251,56 +265,65 @@ template class field_t { uint32_t get_witness_index() const { return witness_index; } - mutable ComposerContext* context = nullptr; - std::vector> decompose_into_bits( const size_t num_bits = 256, std::function(ComposerContext* ctx, uint64_t, uint256_t)> get_bit = [](ComposerContext* ctx, uint64_t j, uint256_t val) { return witness_t(ctx, val.get_bit(j)); }) const; + + mutable ComposerContext* context = nullptr; + /** - * additive_constant, multiplicative_constant are constant scaling factors applied to a field_t object. + * `additive_constant` and `multiplicative_constant` are constant scaling factors applied to a field_t object. + * + * The 'value' represented by a field_t is calculated as: + * - For `field_t`s with `witness_index = IS_CONSTANT`: + * `this.additive_constant` + * - For non-constant `field_t`s: + * `this.context->variables[this.witness_index] * this.multiplicative_constant + this.additive_constant` + * * We track these scaling factors, because we can apply the same scaling factors to Plonk wires when creating - *gates. i.e. if we want to multiply a wire by a constant, or add a constant, we do not need to add extra gates - *to do this. Instead, we track the scaling factors, and apply them to the relevant wires when adding - *constraints + * gates. I.e. if we want to multiply a wire by a constant, or add a constant, we do not need to add extra gates + * to do this. Instead, we track the scaling factors, and apply them to the relevant wires when adding + * constraints. * - * This also makes constant field_t objects effectively free. Where 'constant' is a circuit constant, not a C++ - *constant! e.g. the following 3 lines of code add 0 constraints into a circuit: + * This also makes constant field_t objects effectively free. (Where 'constant' is a circuit constant, not a C++ + * constant!). + * E.g. the following 3 lines of code add 0 constraints into a circuit: * - * field_t foo = 1; - * field_t bar = 5; - * field_t bar *= foo; + * field_t foo = 1; + * field_t bar = 5; + * field_t bar *= foo; * * Similarly if we add in: * - * field_t zip = witness_t(context, 10); - * zip *= bar + foo; + * field_t zip = witness_t(context, 10); + * zip *= bar + foo; * * The above adds 0 constraints, the only effect is that `zip`'s scaling factors have been modified. However if - *we now add: + * we now add: * - * field_t zap = witness_t(context, 50); - * zip *= zap; + * field_t zap = witness_t(context, 50); + * zip *= zap; * - * This will add a constraint, as both zip and zap map to circuit witnesses + * This will add a constraint, as both zip and zap map to circuit witnesses. **/ mutable barretenberg::fr additive_constant; mutable barretenberg::fr multiplicative_constant; /** - * Every composer object contains a list of 'witnesses', circuit variables that can be assigned to wires when - *creating constraints `witness_index` describes a location in this container. i.e. it 'points' to a circuit - *variable + * Every composer object contains a vector `variables` (a.k.a. 'witnesses'); circuit variables that can be + * assigned to wires when creating constraints. `witness_index` describes a location in this container. I.e. it + * 'points' to a circuit variable. * * A witness is not the same thing as a 'wire' in a circuit. Multiple wires can be assigned to the same witness via - *Plonk's copy constraints. Alternatively, a witness might not be assigned to any wires! This case would be similar - *to an unused variable in a regular program + * Plonk's copy constraints. Alternatively, a witness might not be assigned to any wires! This case would be similar + * to an unused variable in a regular program * - * e.g. if we write `field_t foo = witness_t(context, 100)`, this will add the value `100` into `context`'s list of - *circuit variables. However if we do not use `foo` in any operations, then this value will never be assigned to a - *wire in a circuit + * E.g. if we write `field_t foo = witness_t(context, 100)`, this will add the value `100` into `context`'s list of + * circuit `variables`. However if we do not use `foo` in any operations, then this value will never be assigned to + * a wire in a circuit. * * For a more in depth example, consider the following code: * @@ -308,25 +331,25 @@ template class field_t { * field_t bar = witness_t(context, 50); * field_t baz = foo * (bar + 7); * - * This will add 3 new circuit witnesses (10, 50, 570). One constraint will also be created, that validates `baz` - *has been correctly constructed The composer will assign `foo, bar, baz` to wires `w_1, w_2, w_3` in a gate, and - *check that: + * This will add 3 new circuit witnesses (10, 50, 570) to `variables`. One constraint will also be created, that + * validates `baz` has been correctly constructed. The composer will assign `foo, bar, baz` to wires `w_1, w_2, w_3` + * in a new gate which checks that: * * w_1 * w_2 + w_1 * 7 - w_3 = 0 * * If any of `foo, bar, baz` are used in future arithmetic, copy constraints will be automatically applied, - * this ensure that all gate wires that map to `foo`, for example, will contain the same value + * this ensure that all gate wires that map to `foo`, for example, will contain the same value. * * If witness_index == IS_CONSTANT, the object represents a constant value. - * i.e. a value that's hardcoded in the circuit, that a prover cannot change by modifying their witness transcript + * i.e. a value that's hardcoded in the circuit, that a prover cannot change by modifying their witness transcript. * * A Plonk gate is a mix of witness values and selector values. e.g. the regular PLONK arithmetic gate checks that: * * w_1 * w_2 * q_m + w_1 * q_1 + w_2 * w_2 + w_3 * q_3 + q_c = 0 * - * The `w` value are wires, the `q` values are selector constants. If a field object contains a witness index, it - *will be assigned to `w` values when constraints are applied. If it's a circuit constant, it will be assigned to - *`q` values + * The `w` value are wires, the `q` values are selector constants. If a field object contains a `witness_index`, it + * will be assigned to `w` values when constraints are applied. If it's a circuit constant, it will be assigned to + * `q` values. * * TLDR: witness_index is a pseudo pointer to a circuit witness **/ diff --git a/cpp/src/aztec/stdlib/primitives/field/field.test.cpp b/cpp/src/aztec/stdlib/primitives/field/field.test.cpp index 0b44a69976..306d5bef74 100644 --- a/cpp/src/aztec/stdlib/primitives/field/field.test.cpp +++ b/cpp/src/aztec/stdlib/primitives/field/field.test.cpp @@ -2,7 +2,7 @@ #include "field.hpp" #include #include -#include +#include #include #include @@ -20,679 +20,995 @@ template void ignore_unused(T&) {} // use to ignore unused variables i using namespace barretenberg; using namespace plonk; -typedef waffle::StandardComposer Composer; -typedef stdlib::bool_t bool_t; -typedef stdlib::field_t field_t; -typedef stdlib::witness_t witness_t; -typedef stdlib::public_witness_t public_witness_t; +template class stdlib_field : public testing::Test { + typedef stdlib::bool_t bool_ct; + typedef stdlib::field_t field_ct; + typedef stdlib::witness_t witness_ct; + typedef stdlib::public_witness_t public_witness_ct; -void fibbonaci(Composer& composer) -{ - field_t a(stdlib::witness_t(&composer, fr::one())); - field_t b(stdlib::witness_t(&composer, fr::one())); + static void fibbonaci(Composer& composer) + { + field_ct a(witness_ct(&composer, fr::one())); + field_ct b(witness_ct(&composer, fr::one())); - field_t c = a + b; + field_ct c = a + b; - for (size_t i = 0; i < 17; ++i) { - b = a; - a = c; - c = a + b; - } -} -uint64_t fidget(Composer& composer) -{ - field_t a(public_witness_t(&composer, fr::one())); // a is a legit wire value in our circuit - field_t b(&composer, - (fr::one())); // b is just a constant, and should not turn up as a wire value in our circuit - - // this shouldn't create a constraint - we just need to scale the addition/multiplication gates that `a` is involved - // in c should point to the same wire value as a - field_t c = a + b; - field_t d(&composer, fr::coset_generator(0)); // like b, d is just a constant and not a wire value - - // by this point, we shouldn't have added any constraints in our circuit - for (size_t i = 0; i < 17; ++i) { - c = c * d; // shouldn't create a constraint - just scales up c (which points to same wire value as a) - c = c - d; // shouldn't create a constraint - just adds a constant term into c's gates - c = c * a; // will create a constraint - both c and a are wires in our circuit (the same wire actually, so this - // is a square-ish gate) + for (size_t i = 0; i < 17; ++i) { + b = a; + a = c; + c = a + b; + } } + static uint64_t fidget(Composer& composer) + { + field_ct a(public_witness_ct(&composer, fr::one())); // a is a legit wire value in our circuit + field_ct b(&composer, + (fr::one())); // b is just a constant, and should not turn up as a wire value in our circuit + + // this shouldn't create a constraint - we just need to scale the addition/multiplication gates that `a` is + // involved in c should point to the same wire value as a + field_ct c = a + b; + field_ct d(&composer, fr::coset_generator(0)); // like b, d is just a constant and not a wire value + + // by this point, we shouldn't have added any constraints in our circuit + for (size_t i = 0; i < 17; ++i) { + c = c * d; // shouldn't create a constraint - just scales up c (which points to same wire value as a) + c = c - d; // shouldn't create a constraint - just adds a constant term into c's gates + c = c * a; // will create a constraint - both c and a are wires in our circuit (the same wire actually, so + // this is a square-ish gate) + } - // run the same computation using normal types so we can compare the output - uint64_t aa = 1; - uint64_t bb = 1; - uint64_t cc = aa + bb; - uint64_t dd = 5; - for (size_t i = 0; i < 17; ++i) { - cc = cc * dd; - cc = cc - dd; - cc = cc * aa; + // run the same computation using normal types so we can compare the output + uint64_t aa = 1; + uint64_t bb = 1; + uint64_t cc = aa + bb; + uint64_t dd = 5; + for (size_t i = 0; i < 17; ++i) { + cc = cc * dd; + cc = cc - dd; + cc = cc * aa; + } + return cc; } - return cc; -} -void generate_test_plonk_circuit(Composer& composer, size_t num_gates) -{ - field_t a(public_witness_t(&composer, barretenberg::fr::random_element())); - field_t b(public_witness_t(&composer, barretenberg::fr::random_element())); - - field_t c(&composer); - for (size_t i = 0; i < (num_gates / 4) - 4; ++i) { - c = a + b; - c = a * c; - a = b * b; - b = c * c; + static void generate_test_plonk_circuit(Composer& composer, size_t num_gates) + { + field_ct a(public_witness_ct(&composer, barretenberg::fr::random_element())); + field_ct b(public_witness_ct(&composer, barretenberg::fr::random_element())); + + field_ct c(&composer); + for (size_t i = 0; i < (num_gates / 4) - 4; ++i) { + c = a + b; + c = a * c; + a = b * b; + b = c * c; + } } -} -/** - * @brief Demonstrate current behavior of assert_equal. - */ -TEST(stdlib_field, test_assert_equal) -{ - auto run_test = [](bool constrain, bool true_when_y_val_zero = true) { - waffle::StandardComposer composer = waffle::StandardComposer(); - field_t x = witness_t(&composer, 1); - field_t y = witness_t(&composer, 0); - - // With no constraints, the proof verification will pass even though - // we assert x and y are equal. - bool expected_result = true; - - if (constrain) { - /* The fact that we have a passing test in both cases that follow tells us - * that the failure in the first case comes from the additive constraint, - * not from a copy constraint. That failure is because the assert_equal - * below says that 'the value of y was always x'--the value 1 is substituted - * for x when evaluating the gate identity. - */ - if (true_when_y_val_zero) { - // constraint: 0*x + 1*y + 0*0 + 0 == 0 - waffle::add_triple t{ .a = x.witness_index, - .b = y.witness_index, - .c = composer.zero_idx, - .a_scaling = 0, - .b_scaling = 1, - .c_scaling = 0, - .const_scaling = 0 }; - - composer.create_add_gate(t); - expected_result = false; - } else { - // constraint: 0*x + 1*y + 0*0 - 1 == 0 - waffle::add_triple t{ .a = x.witness_index, - .b = y.witness_index, - .c = composer.zero_idx, - .a_scaling = 0, - .b_scaling = 1, - .c_scaling = 0, - .const_scaling = -1 }; - - composer.create_add_gate(t); - expected_result = true; + public: + static void create_range_constraint() + { + auto run_test = [&](fr elt, size_t num_bits, bool expect_verified) { + Composer composer = Composer(); + field_ct a(witness_ct(&composer, elt)); + a.create_range_constraint(num_bits, "field_tests: range_constraint on a fails"); + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); + bool verified = verifier.verify_proof(proof); + EXPECT_EQ(verified, expect_verified); + if (verified != expect_verified) { + info("Range constraint malfunction on ", elt, " with num_bits ", num_bits); } + }; + + run_test(2, 1, false); + run_test(2, 2, true); + run_test(3, 2, true); + // 130 = 0b10000010, 8 bits + for (size_t num_bits = 1; num_bits < 17; num_bits++) { + run_test(130, num_bits, num_bits < 8 ? false : true); } - x.assert_equal(y); + // -1 has maximum bit length + run_test(-1, fr::modulus.get_msb(), false); + run_test(-1, 128, false); + run_test(-1, fr::modulus.get_msb() + 1, true); + } + + /** + * @brief Demonstrate current behavior of assert_equal. + */ + static void test_assert_equal() + { + auto run_test = [](bool constrain, bool true_when_y_val_zero = true) { + Composer composer = Composer(); + field_ct x = witness_ct(&composer, 1); + field_ct y = witness_ct(&composer, 0); + + // With no constraints, the proof verification will pass even though + // we assert x and y are equal. + bool expected_result = true; + + if (constrain) { + /* The fact that we have a passing test in both cases that follow tells us + * that the failure in the first case comes from the additive constraint, + * not from a copy constraint. That failure is because the assert_equal + * below says that 'the value of y was always x'--the value 1 is substituted + * for x when evaluating the gate identity. + */ + if (true_when_y_val_zero) { + // constraint: 0*x + 1*y + 0*0 + 0 == 0 + waffle::add_triple t{ .a = x.witness_index, + .b = y.witness_index, + .c = composer.zero_idx, + .a_scaling = 0, + .b_scaling = 1, + .c_scaling = 0, + .const_scaling = 0 }; + + composer.create_add_gate(t); + expected_result = false; + } else { + // constraint: 0*x + 1*y + 0*0 - 1 == 0 + waffle::add_triple t{ .a = x.witness_index, + .b = y.witness_index, + .c = composer.zero_idx, + .a_scaling = 0, + .b_scaling = 1, + .c_scaling = 0, + .const_scaling = -1 }; + + composer.create_add_gate(t); + expected_result = true; + } + } + + x.assert_equal(y); - // both field elements have real value 1 now - EXPECT_EQ(x.get_value(), 1); - EXPECT_EQ(y.get_value(), 1); + // both field elements have real value 1 now + EXPECT_EQ(x.get_value(), 1); + EXPECT_EQ(y.get_value(), 1); + auto prover = composer.create_prover(); + waffle::plonk_proof proof = prover.construct_proof(); + auto verifier = composer.create_verifier(); + bool result = verifier.verify_proof(proof); + + EXPECT_EQ(result, expected_result); + }; + + run_test(false); + run_test(true, true); + run_test(true, false); + } + + static void test_add_mul_with_constants() + { + Composer composer = Composer(); + + uint64_t expected = fidget(composer); auto prover = composer.create_prover(); - waffle::plonk_proof proof = prover.construct_proof(); + EXPECT_EQ(prover.key->polynomial_cache.get("w_3_lagrange")[18], fr(expected)); + + EXPECT_EQ(prover.n, 32UL); auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } - EXPECT_EQ(result, expected_result); - }; + static void test_div() + { + Composer composer = Composer(); - run_test(false); - run_test(true, true); - run_test(true, false); -} + field_ct a = witness_ct(&composer, barretenberg::fr::random_element()); + a *= barretenberg::fr::random_element(); + a += barretenberg::fr::random_element(); -TEST(stdlib_field, test_add_mul_with_constants) -{ - Composer composer = Composer(); + field_ct b = witness_ct(&composer, barretenberg::fr::random_element()); + b *= barretenberg::fr::random_element(); + b += barretenberg::fr::random_element(); - uint64_t expected = fidget(composer); - auto prover = composer.create_prover(); - EXPECT_EQ(prover.key->polynomial_cache.get("w_3_lagrange")[18], fr(expected)); + // numerator constant + field_ct out = field_ct(&composer, b.get_value()) / a; + EXPECT_EQ(out.get_value(), b.get_value() / a.get_value()); - EXPECT_EQ(prover.n, 32UL); - auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + out = b / a; + EXPECT_EQ(out.get_value(), b.get_value() / a.get_value()); -TEST(stdlib_field, test_div) -{ - Composer composer = Composer(); + // denominator constant + out = a / b.get_value(); + EXPECT_EQ(out.get_value(), a.get_value() / b.get_value()); - field_t a = witness_t(&composer, barretenberg::fr::random_element()); - a *= barretenberg::fr::random_element(); - a += barretenberg::fr::random_element(); + // numerator 0 + out = field_ct(0) / b; + EXPECT_EQ(out.get_value(), 0); + EXPECT_EQ(out.is_constant(), true); - field_t b = witness_t(&composer, barretenberg::fr::random_element()); - b *= barretenberg::fr::random_element(); - b += barretenberg::fr::random_element(); + auto prover = composer.create_prover(); - // numerator constant - field_t out = field_t(&composer, b.get_value()) / a; - EXPECT_EQ(out.get_value(), b.get_value() / a.get_value()); + auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } - out = b / a; - EXPECT_EQ(out.get_value(), b.get_value() / a.get_value()); + static void test_field_fibbonaci() + { + Composer composer = Composer(); - // denominator constant - out = a / b.get_value(); - EXPECT_EQ(out.get_value(), a.get_value() / b.get_value()); + fibbonaci(composer); - // numerator 0 - out = field_t(0) / b; - EXPECT_EQ(out.get_value(), 0); - EXPECT_EQ(out.is_constant(), true); + auto prover = composer.create_prover(); - auto prover = composer.create_prover(); + EXPECT_EQ(prover.key->polynomial_cache.get("w_3_lagrange")[17], fr(4181)); + EXPECT_EQ(prover.n, 32UL); + auto verifier = composer.create_verifier(); - auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + waffle::plonk_proof proof = prover.construct_proof(); -TEST(stdlib_field, test_field_fibbonaci) -{ - Composer composer = Composer(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } - fibbonaci(composer); + static void test_field_pythagorean() + { + Composer composer = Composer(); + field_ct a(witness_ct(&composer, 3)); + field_ct b(witness_ct(&composer, 4)); + field_ct c(witness_ct(&composer, 5)); - auto prover = composer.create_prover(); + field_ct a_sqr = a * a; + field_ct b_sqr = b * b; + field_ct c_sqr = c * c; + c_sqr.set_public(); + field_ct sum_sqrs = a_sqr + b_sqr; - EXPECT_EQ(prover.key->polynomial_cache.get("w_3_lagrange")[17], fr(4181)); - EXPECT_EQ(prover.n, 32UL); - auto verifier = composer.create_verifier(); + // composer.assert_equal(sum_sqrs.witness_index, c_sqr.witness_index, "triple is not pythagorean"); + c_sqr.assert_equal(sum_sqrs); - waffle::plonk_proof proof = prover.construct_proof(); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); + bool verified = verifier.verify_proof(proof); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + for (size_t i = 0; i < composer.variables.size(); i++) { + info(i, composer.variables[i]); + } + ASSERT_TRUE(verified); + } -TEST(stdlib_field, test_equality) -{ - Composer composer = Composer(); + static void test_equality() + { + Composer composer = Composer(); - field_t a(stdlib::witness_t(&composer, 4)); - field_t b(stdlib::witness_t(&composer, 4)); - bool_t r = a == b; + field_ct a(witness_ct(&composer, 4)); + field_ct b(witness_ct(&composer, 4)); + bool_ct r = a == b; - EXPECT_EQ(r.get_value(), true); + EXPECT_EQ(r.get_value(), true); - auto prover = composer.create_prover(); + auto prover = composer.create_prover(); - fr x = composer.get_variable(r.witness_index); - EXPECT_EQ(x, fr(1)); + fr x = composer.get_variable(r.witness_index); + EXPECT_EQ(x, fr(1)); - EXPECT_EQ(prover.n, 16UL); - auto verifier = composer.create_verifier(); + EXPECT_EQ(prover.n, 16UL); + auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); + waffle::plonk_proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } -TEST(stdlib_field, test_equality_false) -{ - Composer composer = Composer(); + static void test_equality_false() + { + Composer composer = Composer(); - field_t a(stdlib::witness_t(&composer, 4)); - field_t b(stdlib::witness_t(&composer, 3)); - bool_t r = a == b; + field_ct a(witness_ct(&composer, 4)); + field_ct b(witness_ct(&composer, 3)); + bool_ct r = a == b; - EXPECT_EQ(r.get_value(), false); + EXPECT_EQ(r.get_value(), false); - auto prover = composer.create_prover(); + auto prover = composer.create_prover(); - fr x = composer.get_variable(r.witness_index); - EXPECT_EQ(x, fr(0)); + fr x = composer.get_variable(r.witness_index); + EXPECT_EQ(x, fr(0)); - EXPECT_EQ(prover.n, 16UL); - auto verifier = composer.create_verifier(); + EXPECT_EQ(prover.n, 16UL); + auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); + waffle::plonk_proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } -TEST(stdlib_field, test_equality_with_constants) -{ - Composer composer = Composer(); + static void test_equality_with_constants() + { + Composer composer = Composer(); - field_t a(stdlib::witness_t(&composer, 4)); - field_t b = 3; - field_t c = 7; - bool_t r = a * c == b * c + c && b + 1 == a; + field_ct a(witness_ct(&composer, 4)); + field_ct b = 3; + field_ct c = 7; + bool_ct r = a * c == b * c + c && b + 1 == a; - EXPECT_EQ(r.get_value(), true); + EXPECT_EQ(r.get_value(), true); - auto prover = composer.create_prover(); + auto prover = composer.create_prover(); - fr x = composer.get_variable(r.witness_index); - EXPECT_EQ(x, fr(1)); + fr x = composer.get_variable(r.witness_index); + EXPECT_EQ(x, fr(1)); - EXPECT_EQ(prover.n, 16UL); - auto verifier = composer.create_verifier(); + EXPECT_EQ(prover.n, 16UL); + auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); + waffle::plonk_proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } -TEST(stdlib_field, test_larger_circuit) -{ - size_t n = 16384; - Composer composer = Composer("../srs_db/ignition", n); + static void test_larger_circuit() + { + size_t n = 16384; + Composer composer = Composer("../srs_db/ignition", n); - generate_test_plonk_circuit(composer, n); + generate_test_plonk_circuit(composer, n); - auto prover = composer.create_prover(); + auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); + auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); + waffle::plonk_proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } -TEST(stdlib_field, is_zero) -{ - Composer composer = Composer(); + static void is_zero() + { + Composer composer = Composer(); - // yuck - field_t a = (public_witness_t(&composer, fr::random_element())); - field_t b = (public_witness_t(&composer, fr::neg_one())); - field_t c_1(&composer, uint256_t(0x1122334455667788, 0x8877665544332211, 0xaabbccddeeff9933, 0x1122112211221122)); - field_t c_2(&composer, uint256_t(0xaabbccddeeff9933, 0x8877665544332211, 0x1122334455667788, 0x1122112211221122)); - field_t c_3(&composer, barretenberg::fr::one()); + // yuck + field_ct a = (public_witness_ct(&composer, fr::random_element())); + field_ct b = (public_witness_ct(&composer, fr::neg_one())); + field_ct c_1(&composer, + uint256_t(0x1122334455667788, 0x8877665544332211, 0xaabbccddeeff9933, 0x1122112211221122)); + field_ct c_2(&composer, + uint256_t(0xaabbccddeeff9933, 0x8877665544332211, 0x1122334455667788, 0x1122112211221122)); + field_ct c_3(&composer, barretenberg::fr::one()); + + field_ct c_4 = c_1 + c_2; + a = a * c_4 + c_4; // add some constant terms in to validate our normalization check works + b = b * c_4 + c_4; + b = (b - c_1 - c_2) / c_4; + b = b + c_3; + + field_ct d(&composer, fr::zero()); + field_ct e(&composer, fr::one()); + + const size_t old_n = composer.get_num_gates(); + bool_ct d_zero = d.is_zero(); + bool_ct e_zero = e.is_zero(); + const size_t new_n = composer.get_num_gates(); + EXPECT_EQ(old_n, new_n); + + bool_ct a_zero = a.is_zero(); + bool_ct b_zero = b.is_zero(); + + EXPECT_EQ(a_zero.get_value(), false); + EXPECT_EQ(b_zero.get_value(), true); + EXPECT_EQ(d_zero.get_value(), true); + EXPECT_EQ(e_zero.get_value(), false); - field_t c_4 = c_1 + c_2; - a = a * c_4 + c_4; // add some constant terms in to validate our normalization check works - b = b * c_4 + c_4; - b = (b - c_1 - c_2) / c_4; - b = b + c_3; + auto prover = composer.create_prover(); - field_t d(&composer, fr::zero()); - field_t e(&composer, fr::one()); + auto verifier = composer.create_verifier(); - const size_t old_n = composer.get_num_gates(); - bool_t d_zero = d.is_zero(); - bool_t e_zero = e.is_zero(); - const size_t new_n = composer.get_num_gates(); - EXPECT_EQ(old_n, new_n); + waffle::plonk_proof proof = prover.construct_proof(); - bool_t a_zero = a.is_zero(); - bool_t b_zero = b.is_zero(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } - EXPECT_EQ(a_zero.get_value(), false); - EXPECT_EQ(b_zero.get_value(), true); - EXPECT_EQ(d_zero.get_value(), true); - EXPECT_EQ(e_zero.get_value(), false); + static void madd() + { + Composer composer = Composer(); - auto prover = composer.create_prover(); + field_ct a(witness_ct(&composer, fr::random_element())); + field_ct b(witness_ct(&composer, fr::random_element())); + field_ct c(witness_ct(&composer, fr::random_element())); + field_ct ma(&composer, fr::random_element()); + field_ct ca(&composer, fr::random_element()); + field_ct mb(&composer, fr::random_element()); + field_ct cb(&composer, fr::random_element()); + field_ct mc(&composer, fr::random_element()); + field_ct cc(&composer, fr::random_element()); + + // test madd when all operands are witnesses + field_ct d = a * ma + ca; + field_ct e = b * mb + cb; + field_ct f = c * mc + cc; + field_ct g = d.madd(e, f); + field_ct h = d * e + f; + h = h.normalize(); + g = g.normalize(); + EXPECT_EQ(g.get_value(), h.get_value()); + + // test madd when to_add = constant + field_ct i = a.madd(b, ma); + field_ct j = a * b + ma; + i = i.normalize(); + j = j.normalize(); + EXPECT_EQ(i.get_value(), j.get_value()); + + // test madd when to_mul = constant + field_ct k = a.madd(mb, c); + field_ct l = a * mb + c; + k = k.normalize(); + l = l.normalize(); + EXPECT_EQ(k.get_value(), l.get_value()); + + // test madd when lhs is constant + field_ct m = ma.madd(b, c); + field_ct n = ma * b + c; + m = m.normalize(); + n = n.normalize(); + EXPECT_EQ(m.get_value(), n.get_value()); - auto verifier = composer.create_verifier(); + auto prover = composer.create_prover(); - waffle::plonk_proof proof = prover.construct_proof(); + auto verifier = composer.create_verifier(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + waffle::plonk_proof proof = prover.construct_proof(); -TEST(stdlib_field, madd) -{ - Composer composer = Composer(); - - field_t a(stdlib::witness_t(&composer, fr::random_element())); - field_t b(stdlib::witness_t(&composer, fr::random_element())); - field_t c(stdlib::witness_t(&composer, fr::random_element())); - field_t ma(&composer, fr::random_element()); - field_t ca(&composer, fr::random_element()); - field_t mb(&composer, fr::random_element()); - field_t cb(&composer, fr::random_element()); - field_t mc(&composer, fr::random_element()); - field_t cc(&composer, fr::random_element()); - - // test madd when all operands are witnesses - field_t d = a * ma + ca; - field_t e = b * mb + cb; - field_t f = c * mc + cc; - field_t g = d.madd(e, f); - field_t h = d * e + f; - h = h.normalize(); - g = g.normalize(); - EXPECT_EQ(g.get_value(), h.get_value()); - - // test madd when to_add = constant - field_t i = a.madd(b, ma); - field_t j = a * b + ma; - i = i.normalize(); - j = j.normalize(); - EXPECT_EQ(i.get_value(), j.get_value()); - - // test madd when to_mul = constant - field_t k = a.madd(mb, c); - field_t l = a * mb + c; - k = k.normalize(); - l = l.normalize(); - EXPECT_EQ(k.get_value(), l.get_value()); - - // test madd when lhs is constant - field_t m = ma.madd(b, c); - field_t n = ma * b + c; - m = m.normalize(); - n = n.normalize(); - EXPECT_EQ(m.get_value(), n.get_value()); - - auto prover = composer.create_prover(); - - auto verifier = composer.create_verifier(); - - waffle::plonk_proof proof = prover.construct_proof(); - - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } -TEST(stdlib_field, two_bit_table) -{ - Composer composer = Composer(); - field_t a(witness_t(&composer, fr::random_element())); - field_t b(witness_t(&composer, fr::random_element())); - field_t c(witness_t(&composer, fr::random_element())); - field_t d(witness_t(&composer, fr::random_element())); + static void two_bit_table() + { + Composer composer = Composer(); + field_ct a(witness_ct(&composer, fr::random_element())); + field_ct b(witness_ct(&composer, fr::random_element())); + field_ct c(witness_ct(&composer, fr::random_element())); + field_ct d(witness_ct(&composer, fr::random_element())); - std::array table = field_t::preprocess_two_bit_table(a, b, c, d); + std::array table = field_ct::preprocess_two_bit_table(a, b, c, d); - bool_t zero(witness_t(&composer, false)); - bool_t one(witness_t(&composer, true)); + bool_ct zero(witness_ct(&composer, false)); + bool_ct one(witness_ct(&composer, true)); - field_t result_a = field_t::select_from_two_bit_table(table, zero, zero).normalize(); - field_t result_b = field_t::select_from_two_bit_table(table, zero, one).normalize(); - field_t result_c = field_t::select_from_two_bit_table(table, one, zero).normalize(); - field_t result_d = field_t::select_from_two_bit_table(table, one, one).normalize(); + field_ct result_a = field_ct::select_from_two_bit_table(table, zero, zero).normalize(); + field_ct result_b = field_ct::select_from_two_bit_table(table, zero, one).normalize(); + field_ct result_c = field_ct::select_from_two_bit_table(table, one, zero).normalize(); + field_ct result_d = field_ct::select_from_two_bit_table(table, one, one).normalize(); - EXPECT_EQ(result_a.get_value(), a.get_value()); - EXPECT_EQ(result_b.get_value(), b.get_value()); - EXPECT_EQ(result_c.get_value(), c.get_value()); - EXPECT_EQ(result_d.get_value(), d.get_value()); + EXPECT_EQ(result_a.get_value(), a.get_value()); + EXPECT_EQ(result_b.get_value(), b.get_value()); + EXPECT_EQ(result_c.get_value(), c.get_value()); + EXPECT_EQ(result_d.get_value(), d.get_value()); - auto prover = composer.create_prover(); + auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); + auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); + waffle::plonk_proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } -TEST(stdlib_field, test_slice) -{ - Composer composer = Composer(); - // 0b11110110101001011 - // ^ ^ - // msb lsb - // 10 3 - // hi=0x111101, lo=0x011, slice=0x10101001 - // - field_t a(witness_t(&composer, fr(126283))); - auto slice_data = a.slice(10, 3); + static void test_slice() + { + Composer composer = Composer(); + // 0b11110110101001011 + // ^ ^ + // msb lsb + // 10 3 + // hi=0x111101, lo=0x011, slice=0x10101001 + // + field_ct a(witness_ct(&composer, fr(126283))); + auto slice_data = a.slice(10, 3); + + EXPECT_EQ(slice_data[0].get_value(), fr(3)); + EXPECT_EQ(slice_data[1].get_value(), fr(169)); + EXPECT_EQ(slice_data[2].get_value(), fr(61)); - EXPECT_EQ(slice_data[0].get_value(), fr(3)); - EXPECT_EQ(slice_data[1].get_value(), fr(169)); - EXPECT_EQ(slice_data[2].get_value(), fr(61)); + auto prover = composer.create_prover(); - auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); - auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); - waffle::plonk_proof proof = prover.construct_proof(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + static void test_slice_equal_msb_lsb() + { + Composer composer = Composer(); + // 0b11110110101001011 + // ^ + // msb = lsb + // 6 + // hi=0b1111011010, lo=0b001011, slice=0b1 + // + field_ct a(witness_ct(&composer, fr(126283))); + auto slice_data = a.slice(6, 6); + + EXPECT_EQ(slice_data[0].get_value(), fr(11)); + EXPECT_EQ(slice_data[1].get_value(), fr(1)); + EXPECT_EQ(slice_data[2].get_value(), fr(986)); -TEST(stdlib_field, test_slice_equal_msb_lsb) -{ - Composer composer = Composer(); - // 0b11110110101001011 - // ^ - // msb = lsb - // 6 - // hi=0b1111011010, lo=0b001011, slice=0b1 - // - field_t a(witness_t(&composer, fr(126283))); - auto slice_data = a.slice(6, 6); + auto prover = composer.create_prover(); - EXPECT_EQ(slice_data[0].get_value(), fr(11)); - EXPECT_EQ(slice_data[1].get_value(), fr(1)); - EXPECT_EQ(slice_data[2].get_value(), fr(986)); + auto verifier = composer.create_verifier(); - auto prover = composer.create_prover(); + waffle::plonk_proof proof = prover.construct_proof(); - auto verifier = composer.create_verifier(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } - waffle::plonk_proof proof = prover.construct_proof(); + static void test_slice_random() + { + Composer composer = Composer(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + uint8_t lsb = 106; + uint8_t msb = 189; + fr a_ = fr(uint256_t(fr::random_element()) && ((uint256_t(1) << 252) - 1)); + field_ct a(witness_ct(&composer, a_)); + auto slice = a.slice(msb, lsb); -TEST(stdlib_field, test_slice_random) -{ - Composer composer = Composer(); + const uint256_t expected0 = uint256_t(a_) & ((uint256_t(1) << uint64_t(lsb)) - 1); + const uint256_t expected1 = (uint256_t(a_) >> lsb) & ((uint256_t(1) << (uint64_t(msb - lsb) + 1)) - 1); + const uint256_t expected2 = (uint256_t(a_) >> (msb + 1)) & ((uint256_t(1) << (uint64_t(252 - msb) - 1)) - 1); - uint8_t lsb = 106; - uint8_t msb = 189; - fr a_ = fr(uint256_t(fr::random_element()) && ((uint256_t(1) << 252) - 1)); - field_t a(witness_t(&composer, a_)); - auto slice = a.slice(msb, lsb); + EXPECT_EQ(slice[0].get_value(), fr(expected0)); + EXPECT_EQ(slice[1].get_value(), fr(expected1)); + EXPECT_EQ(slice[2].get_value(), fr(expected2)); - const uint256_t expected0 = uint256_t(a_) & ((uint256_t(1) << uint64_t(lsb)) - 1); - const uint256_t expected1 = (uint256_t(a_) >> lsb) & ((uint256_t(1) << (uint64_t(msb - lsb) + 1)) - 1); - const uint256_t expected2 = (uint256_t(a_) >> (msb + 1)) & ((uint256_t(1) << (uint64_t(252 - msb) - 1)) - 1); + auto prover = composer.create_prover(); - EXPECT_EQ(slice[0].get_value(), fr(expected0)); - EXPECT_EQ(slice[1].get_value(), fr(expected1)); - EXPECT_EQ(slice[2].get_value(), fr(expected2)); + auto verifier = composer.create_verifier(); - auto prover = composer.create_prover(); + waffle::plonk_proof proof = prover.construct_proof(); - auto verifier = composer.create_verifier(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } - waffle::plonk_proof proof = prover.construct_proof(); + static void three_bit_table() + { + Composer composer = Composer(); + field_ct a(witness_ct(&composer, fr::random_element())); + field_ct b(witness_ct(&composer, fr::random_element())); + field_ct c(witness_ct(&composer, fr::random_element())); + field_ct d(witness_ct(&composer, fr::random_element())); + field_ct e(witness_ct(&composer, fr::random_element())); + field_ct f(witness_ct(&composer, fr::random_element())); + field_ct g(witness_ct(&composer, fr::random_element())); + field_ct h(witness_ct(&composer, fr::random_element())); + + std::array table = field_ct::preprocess_three_bit_table(a, b, c, d, e, f, g, h); + + bool_ct zero(witness_ct(&composer, false)); + bool_ct one(witness_ct(&composer, true)); + + field_ct result_a = field_ct::select_from_three_bit_table(table, zero, zero, zero).normalize(); + field_ct result_b = field_ct::select_from_three_bit_table(table, zero, zero, one).normalize(); + field_ct result_c = field_ct::select_from_three_bit_table(table, zero, one, zero).normalize(); + field_ct result_d = field_ct::select_from_three_bit_table(table, zero, one, one).normalize(); + field_ct result_e = field_ct::select_from_three_bit_table(table, one, zero, zero).normalize(); + field_ct result_f = field_ct::select_from_three_bit_table(table, one, zero, one).normalize(); + field_ct result_g = field_ct::select_from_three_bit_table(table, one, one, zero).normalize(); + field_ct result_h = field_ct::select_from_three_bit_table(table, one, one, one).normalize(); + + EXPECT_EQ(result_a.get_value(), a.get_value()); + EXPECT_EQ(result_b.get_value(), b.get_value()); + EXPECT_EQ(result_c.get_value(), c.get_value()); + EXPECT_EQ(result_d.get_value(), d.get_value()); + EXPECT_EQ(result_e.get_value(), e.get_value()); + EXPECT_EQ(result_f.get_value(), f.get_value()); + EXPECT_EQ(result_g.get_value(), g.get_value()); + EXPECT_EQ(result_h.get_value(), h.get_value()); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + auto prover = composer.create_prover(); -TEST(stdlib_field, three_bit_table) -{ - Composer composer = Composer(); - field_t a(witness_t(&composer, fr::random_element())); - field_t b(witness_t(&composer, fr::random_element())); - field_t c(witness_t(&composer, fr::random_element())); - field_t d(witness_t(&composer, fr::random_element())); - field_t e(witness_t(&composer, fr::random_element())); - field_t f(witness_t(&composer, fr::random_element())); - field_t g(witness_t(&composer, fr::random_element())); - field_t h(witness_t(&composer, fr::random_element())); - - std::array table = field_t::preprocess_three_bit_table(a, b, c, d, e, f, g, h); - - bool_t zero(witness_t(&composer, false)); - bool_t one(witness_t(&composer, true)); - - field_t result_a = field_t::select_from_three_bit_table(table, zero, zero, zero).normalize(); - field_t result_b = field_t::select_from_three_bit_table(table, zero, zero, one).normalize(); - field_t result_c = field_t::select_from_three_bit_table(table, zero, one, zero).normalize(); - field_t result_d = field_t::select_from_three_bit_table(table, zero, one, one).normalize(); - field_t result_e = field_t::select_from_three_bit_table(table, one, zero, zero).normalize(); - field_t result_f = field_t::select_from_three_bit_table(table, one, zero, one).normalize(); - field_t result_g = field_t::select_from_three_bit_table(table, one, one, zero).normalize(); - field_t result_h = field_t::select_from_three_bit_table(table, one, one, one).normalize(); - - EXPECT_EQ(result_a.get_value(), a.get_value()); - EXPECT_EQ(result_b.get_value(), b.get_value()); - EXPECT_EQ(result_c.get_value(), c.get_value()); - EXPECT_EQ(result_d.get_value(), d.get_value()); - EXPECT_EQ(result_e.get_value(), e.get_value()); - EXPECT_EQ(result_f.get_value(), f.get_value()); - EXPECT_EQ(result_g.get_value(), g.get_value()); - EXPECT_EQ(result_h.get_value(), h.get_value()); - - auto prover = composer.create_prover(); - - auto verifier = composer.create_verifier(); - - waffle::plonk_proof proof = prover.construct_proof(); - - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + auto verifier = composer.create_verifier(); -/** - * @brief Test success and failure cases for decompose_into_bits. - * - * @details The target function constructs `sum` from a supplied collection of bits and compares it with a value - * `val_256`. We supply bit vectors to test some failure cases. - */ + waffle::plonk_proof proof = prover.construct_proof(); -TEST(stdlib_field, decompose_into_bits) -{ - using witness_supplier_type = std::function; + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } - // check that constraints are satisfied for a variety of inputs - auto run_success_test = [&]() { - Composer composer = Composer(); + /** + * @brief Test success and failure cases for decompose_into_bits. + * + * @details The target function constructs `sum` from a supplied collection of bits and compares it with a value + * `val_256`. We supply bit vectors to test some failure cases. + */ + + static void decompose_into_bits() + { + using witness_supplier_type = std::function; + + // check that constraints are satisfied for a variety of inputs + auto run_success_test = [&]() { + Composer composer = Composer(); + + constexpr uint256_t modulus_minus_one = fr::modulus - 1; + const fr p_lo = modulus_minus_one.slice(0, 130); + + std::vector test_elements = { + barretenberg::fr::random_element(), + 0, + -1, + barretenberg::fr(static_cast(engine.get_random_uint8())), + barretenberg::fr((static_cast(1) << 130) + 1 + p_lo) + }; + + for (auto a_expected : test_elements) { + field_ct a = witness_ct(&composer, a_expected); + std::vector c = a.decompose_into_bits(256); + fr bit_sum = 0; + for (size_t i = 0; i < c.size(); i++) { + fr scaling_factor_value = fr(2).pow(static_cast(i)); + bit_sum += (fr(c[i].get_value()) * scaling_factor_value); + } + EXPECT_EQ(bit_sum, a_expected); + }; + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); + bool verified = verifier.verify_proof(proof); + ASSERT_TRUE(verified); + }; - constexpr uint256_t modulus_minus_one = fr::modulus - 1; - const fr p_lo = modulus_minus_one.slice(0, 130); + // Now try to supply unintended witness values and test for failure. + // Fr::modulus is equivalent to zero in Fr, but this should be forbidden by a range constraint. + witness_supplier_type supply_modulus_bits = [](Composer* ctx, uint64_t j, uint256_t val_256) { + ignore_unused(val_256); + // use this to get `sum` to be fr::modulus. + return witness_ct(ctx, fr::modulus.get_bit(j)); + }; - std::vector test_elements = { - barretenberg::fr::random_element(), - 0, - -1, - barretenberg::fr(static_cast(engine.get_random_uint8())), - barretenberg::fr((static_cast(1) << 130) + 1 + p_lo) + // design a bit vector that will pass all range constraints, but it fails the copy constraint. + witness_supplier_type supply_half_modulus_bits = [](Composer* ctx, uint64_t j, uint256_t val_256) { + // use this to fit y_hi into 128 bits + if (j > 127) { + return witness_ct(ctx, val_256.get_bit(j)); + }; + return witness_ct(ctx, (fr::modulus).get_bit(j)); }; - for (auto a_expected : test_elements) { - field_t a = witness_t(&composer, a_expected); - std::vector c = a.decompose_into_bits(256); - fr bit_sum = 0; - for (size_t i = 0; i < c.size(); i++) { - fr scaling_factor_value = fr(2).pow(static_cast(i)); - bit_sum += (fr(c[i].get_value()) * scaling_factor_value); - } - EXPECT_EQ(bit_sum, a_expected); + auto run_failure_test = [&](witness_supplier_type witness_supplier) { + Composer composer = Composer(); + + fr a_expected = 0; + field_ct a = witness_ct(&composer, a_expected); + std::vector c = a.decompose_into_bits(256, witness_supplier); + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); + bool verified = verifier.verify_proof(proof); + ASSERT_FALSE(verified); }; + run_success_test(); + run_failure_test(supply_modulus_bits); + run_failure_test(supply_half_modulus_bits); + } + + static void test_assert_is_in_set() + { + Composer composer = Composer(); + + field_ct a(witness_ct(&composer, fr(1))); + field_ct b(witness_ct(&composer, fr(2))); + field_ct c(witness_ct(&composer, fr(3))); + field_ct d(witness_ct(&composer, fr(4))); + field_ct e(witness_ct(&composer, fr(5))); + std::vector set = { a, b, c, d, e }; + + a.assert_is_in_set(set); + auto prover = composer.create_prover(); auto verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); - bool verified = verifier.verify_proof(proof); - ASSERT_TRUE(verified); - }; + info("composer gates = ", composer.get_num_gates()); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } - // Now try to supply unintended witness values and test for failure. - // Fr::modulus is equivalent to zero in Fr, but this should be forbidden by a range constraint. - witness_supplier_type supply_modulus_bits = [](Composer* ctx, uint64_t j, uint256_t val_256) { - ignore_unused(val_256); - // use this to get `sum` to be fr::modulus. - return witness_t(ctx, fr::modulus.get_bit(j)); - }; + static void test_assert_is_in_set_fails() + { + Composer composer = Composer(); - // design a bit vector that will pass all range constraints, but it fails the copy constraint. - witness_supplier_type supply_half_modulus_bits = [](Composer* ctx, uint64_t j, uint256_t val_256) { - // use this to fit y_hi into 128 bits - if (j > 127) { - return witness_t(ctx, val_256.get_bit(j)); - }; - return witness_t(ctx, (fr::modulus).get_bit(j)); - }; + field_ct a(witness_ct(&composer, fr(1))); + field_ct b(witness_ct(&composer, fr(2))); + field_ct c(witness_ct(&composer, fr(3))); + field_ct d(witness_ct(&composer, fr(4))); + field_ct e(witness_ct(&composer, fr(5))); + std::vector set = { a, b, c, d, e }; + + field_ct f(witness_ct(&composer, fr(6))); + f.assert_is_in_set(set); + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); + info("composer gates = ", composer.get_num_gates()); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, false); + } + + static void test_pow() + { + Composer composer = Composer(); - auto run_failure_test = [&](witness_supplier_type witness_supplier) { + barretenberg::fr base_val(engine.get_random_uint256()); + uint32_t exponent_val = engine.get_random_uint32(); + + field_ct base = witness_ct(&composer, base_val); + field_ct exponent = witness_ct(&composer, exponent_val); + field_ct result = base.pow(exponent); + barretenberg::fr expected = base_val.pow(exponent_val); + + EXPECT_EQ(result.get_value(), expected); + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + info("composer gates = ", composer.get_num_gates()); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } + + static void test_pow_zero() + { Composer composer = Composer(); - fr a_expected = 0; - field_t a = witness_t(&composer, a_expected); - std::vector c = a.decompose_into_bits(256, witness_supplier); + barretenberg::fr base_val(engine.get_random_uint256()); + uint32_t exponent_val = 0; + + field_ct base = witness_ct(&composer, base_val); + field_ct exponent = witness_ct(&composer, exponent_val); + field_ct result = base.pow(exponent); + + EXPECT_EQ(result.get_value(), barretenberg::fr(1)); auto prover = composer.create_prover(); auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); - bool verified = verifier.verify_proof(proof); - ASSERT_FALSE(verified); - }; + auto proof = prover.construct_proof(); + info("composer gates = ", composer.get_num_gates()); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } - run_success_test(); - run_failure_test(supply_modulus_bits); - run_failure_test(supply_half_modulus_bits); -} + static void test_pow_one() + { + Composer composer = Composer(); -TEST(stdlib_field, test_assert_is_in_set) -{ - waffle::StandardComposer composer = waffle::StandardComposer(); - - field_t a(witness_t(&composer, fr(1))); - field_t b(witness_t(&composer, fr(2))); - field_t c(witness_t(&composer, fr(3))); - field_t d(witness_t(&composer, fr(4))); - field_t e(witness_t(&composer, fr(5))); - std::vector set = { a, b, c, d, e }; - - a.assert_is_in_set(set); - - waffle::Prover prover = composer.preprocess(); - waffle::Verifier verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); - info("composer gates = ", composer.get_num_gates()); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + barretenberg::fr base_val(engine.get_random_uint256()); + uint32_t exponent_val = 1; -TEST(stdlib_field, test_assert_is_in_set_fails) -{ - waffle::StandardComposer composer = waffle::StandardComposer(); + field_ct base = witness_ct(&composer, base_val); + field_ct exponent = witness_ct(&composer, exponent_val); + field_ct result = base.pow(exponent); - field_t a(witness_t(&composer, fr(1))); - field_t b(witness_t(&composer, fr(2))); - field_t c(witness_t(&composer, fr(3))); - field_t d(witness_t(&composer, fr(4))); - field_t e(witness_t(&composer, fr(5))); - std::vector set = { a, b, c, d, e }; + EXPECT_EQ(result.get_value(), base_val); - field_t f(witness_t(&composer, fr(6))); - f.assert_is_in_set(set); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + info("composer gates = ", composer.get_num_gates()); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } - waffle::Prover prover = composer.preprocess(); + static void test_pow_both_constant() + { + Composer composer = Composer(); - waffle::Verifier verifier = composer.create_verifier(); + const size_t num_gates_start = composer.n; - waffle::plonk_proof proof = prover.construct_proof(); + barretenberg::fr base_val(engine.get_random_uint256()); + uint32_t exponent_val = engine.get_random_uint32(); - info("composer gates = ", composer.get_num_gates()); + field_ct base(&composer, base_val); + field_ct exponent(&composer, exponent_val); + field_ct result = base.pow(exponent); + barretenberg::fr expected = base_val.pow(exponent_val); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, false); -} + EXPECT_EQ(result.get_value(), expected); + + const size_t num_gates_end = composer.n; + EXPECT_EQ(num_gates_start, num_gates_end); + } + + static void test_pow_base_constant() + { + Composer composer = Composer(); + + barretenberg::fr base_val(engine.get_random_uint256()); + uint32_t exponent_val = engine.get_random_uint32(); + + field_ct base(&composer, base_val); + field_ct exponent = witness_ct(&composer, exponent_val); + field_ct result = base.pow(exponent); + barretenberg::fr expected = base_val.pow(exponent_val); + + EXPECT_EQ(result.get_value(), expected); + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + info("composer gates = ", composer.get_num_gates()); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } + + static void test_pow_exponent_constant() + { + Composer composer = Composer(); + + barretenberg::fr base_val(engine.get_random_uint256()); + uint32_t exponent_val = engine.get_random_uint32(); + + field_ct base = witness_ct(&composer, base_val); + field_ct exponent(&composer, exponent_val); + field_ct result = base.pow(exponent); + barretenberg::fr expected = base_val.pow(exponent_val); + + EXPECT_EQ(result.get_value(), expected); + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + info("composer gates = ", composer.get_num_gates()); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + }; + + static void test_pow_exponent_out_of_range() + { + Composer composer = Composer(); + + barretenberg::fr base_val(engine.get_random_uint256()); + uint64_t exponent_val = engine.get_random_uint32(); + exponent_val += (uint64_t(1) << 32); + + field_ct base = witness_ct(&composer, base_val); + field_ct exponent = witness_ct(&composer, exponent_val); + field_ct result = base.pow(exponent); + barretenberg::fr expected = base_val.pow(exponent_val); + + EXPECT_NE(result.get_value(), expected); + EXPECT_EQ(composer.failed(), true); + EXPECT_EQ(composer.err(), "field_t::pow exponent accumulator incorrect"); + }; +}; +typedef testing::Types ComposerTypes; + +TYPED_TEST_SUITE(stdlib_field, ComposerTypes); + +TYPED_TEST(stdlib_field, test_create_range_constraint) +{ + TestFixture::create_range_constraint(); +} +TYPED_TEST(stdlib_field, test_assert_equal) +{ + TestFixture::test_assert_equal(); +} +TYPED_TEST(stdlib_field, test_add_mul_with_constants) +{ + TestFixture::test_add_mul_with_constants(); +} +TYPED_TEST(stdlib_field, test_div) +{ + TestFixture::test_div(); +} +TYPED_TEST(stdlib_field, test_field_fibbonaci) +{ + TestFixture::test_field_fibbonaci(); +} +TYPED_TEST(stdlib_field, test_field_pythagorean) +{ + TestFixture::test_field_pythagorean(); +} +TYPED_TEST(stdlib_field, test_equality) +{ + TestFixture::test_equality(); +} +TYPED_TEST(stdlib_field, test_equality_false) +{ + TestFixture::test_equality_false(); +} +TYPED_TEST(stdlib_field, test_equality_with_constants) +{ + TestFixture::test_equality_with_constants(); +} +TYPED_TEST(stdlib_field, test_larger_circuit) +{ + TestFixture::test_larger_circuit(); +} +TYPED_TEST(stdlib_field, is_zero) +{ + TestFixture::is_zero(); +} +TYPED_TEST(stdlib_field, madd) +{ + TestFixture::madd(); +} +TYPED_TEST(stdlib_field, two_bit_table) +{ + TestFixture::two_bit_table(); +} +TYPED_TEST(stdlib_field, test_slice) +{ + TestFixture::test_slice(); +} +TYPED_TEST(stdlib_field, test_slice_equal_msb_lsb) +{ + TestFixture::test_slice_equal_msb_lsb(); +} +TYPED_TEST(stdlib_field, test_slice_random) +{ + TestFixture::test_slice_random(); +} +TYPED_TEST(stdlib_field, three_bit_table) +{ + TestFixture::three_bit_table(); +} +TYPED_TEST(stdlib_field, decompose_into_bits) +{ + TestFixture::decompose_into_bits(); +} +TYPED_TEST(stdlib_field, test_assert_is_in_set) +{ + TestFixture::test_assert_is_in_set(); +} +TYPED_TEST(stdlib_field, test_assert_is_in_set_fails) +{ + TestFixture::test_assert_is_in_set_fails(); +} +TYPED_TEST(stdlib_field, test_pow) +{ + TestFixture::test_pow(); +} +TYPED_TEST(stdlib_field, test_pow_zero) +{ + TestFixture::test_pow_zero(); +} +TYPED_TEST(stdlib_field, test_pow_one) +{ + TestFixture::test_pow_one(); +} +TYPED_TEST(stdlib_field, test_pow_both_constant) +{ + TestFixture::test_pow_both_constant(); +} +TYPED_TEST(stdlib_field, test_pow_base_constant) +{ + TestFixture::test_pow_base_constant(); +} +TYPED_TEST(stdlib_field, test_pow_exponent_constant) +{ + TestFixture::test_pow_exponent_constant(); +} +TYPED_TEST(stdlib_field, test_pow_exponent_out_of_range) +{ + TestFixture::test_pow_exponent_out_of_range(); +} } // namespace test_stdlib_field \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/field/pow.hpp b/cpp/src/aztec/stdlib/primitives/field/pow.hpp deleted file mode 100644 index ff1b0ea92b..0000000000 --- a/cpp/src/aztec/stdlib/primitives/field/pow.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include - -#include "./field.hpp" -#include "../uint/uint.hpp" - -namespace plonk { -namespace stdlib { - -template -static field_t pow(const field_t& base, const uint32& exponent) -{ - using field_pt = field_t; - - auto* ctx = base.get_context() ? base.get_context() : exponent.get_context(); - - field_pt accumulator(ctx, 1); - field_pt mul_coefficient = base - 1; - for (size_t i = 0; i < 32; ++i) { - accumulator *= accumulator; - const auto bit = exponent.at(31 - i); - accumulator *= (mul_coefficient * bit + 1); - } - accumulator = accumulator.normalize(); - return accumulator; -} - -} // namespace stdlib -} // namespace plonk diff --git a/cpp/src/aztec/stdlib/primitives/field/pow.test.cpp b/cpp/src/aztec/stdlib/primitives/field/pow.test.cpp deleted file mode 100644 index 9bc168b7d9..0000000000 --- a/cpp/src/aztec/stdlib/primitives/field/pow.test.cpp +++ /dev/null @@ -1,151 +0,0 @@ -#include "field.hpp" -#include "pow.hpp" -#include - -#include -#include -#include - -namespace test_stdlib_field_pow { - -namespace { -auto& engine = numeric::random::get_debug_engine(); -} - -using namespace plonk::stdlib::types::turbo; - -TEST(stdlib_field_pow, test_pow) -{ - Composer composer; - - barretenberg::fr base_val(engine.get_random_uint256()); - uint32_t exponent_val = engine.get_random_uint32(); - - field_ct base = witness_ct(&composer, base_val); - uint32_ct exponent = witness_ct(&composer, exponent_val); - - field_ct result = plonk::stdlib::pow(base, exponent); - - barretenberg::fr expected = base_val.pow(exponent_val); - - EXPECT_EQ(result.get_value(), expected); - - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - auto proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} - -TEST(stdlib_field_pow, test_pow_zero) -{ - Composer composer; - - barretenberg::fr base_val(engine.get_random_uint256()); - uint32_t exponent_val = 0; - - field_ct base = witness_ct(&composer, base_val); - uint32_ct exponent = witness_ct(&composer, exponent_val); - - field_ct result = plonk::stdlib::pow(base, exponent); - - EXPECT_EQ(result.get_value(), barretenberg::fr(1)); - - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - auto proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} - -TEST(stdlib_field_pow, test_pow_one) -{ - Composer composer; - - barretenberg::fr base_val(engine.get_random_uint256()); - uint32_t exponent_val = 1; - - field_ct base = witness_ct(&composer, base_val); - uint32_ct exponent = witness_ct(&composer, exponent_val); - - field_ct result = plonk::stdlib::pow(base, exponent); - - EXPECT_EQ(result.get_value(), base_val); - - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - auto proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} - -TEST(stdlib_field_pow, test_pow_both_constant) -{ - Composer composer; - - const size_t num_gates_start = composer.n; - - barretenberg::fr base_val(engine.get_random_uint256()); - uint32_t exponent_val = engine.get_random_uint32(); - - field_ct base(&composer, base_val); - uint32_ct exponent(&composer, exponent_val); - - field_ct result = plonk::stdlib::pow(base, exponent); - - barretenberg::fr expected = base_val.pow(exponent_val); - - EXPECT_EQ(result.get_value(), expected); - - const size_t num_gates_end = composer.n; - - EXPECT_EQ(num_gates_start, num_gates_end); -} - -TEST(stdlib_field_pow, test_pow_base_constant) -{ - Composer composer; - - barretenberg::fr base_val(engine.get_random_uint256()); - uint32_t exponent_val = engine.get_random_uint32(); - - field_ct base(&composer, base_val); - uint32_ct exponent = witness_ct(&composer, exponent_val); - - field_ct result = plonk::stdlib::pow(base, exponent); - - barretenberg::fr expected = base_val.pow(exponent_val); - - EXPECT_EQ(result.get_value(), expected); - - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - auto proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} - -TEST(stdlib_field_pow, test_pow_exponent_constant) -{ - Composer composer; - - barretenberg::fr base_val(engine.get_random_uint256()); - uint32_t exponent_val = engine.get_random_uint32(); - - field_ct base = witness_ct(&composer, base_val); - uint32_ct exponent(&composer, exponent_val); - - field_ct result = plonk::stdlib::pow(base, exponent); - - barretenberg::fr expected = base_val.pow(exponent_val); - - EXPECT_EQ(result.get_value(), expected); - - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - auto proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} - -} // namespace test_stdlib_field_pow \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/group/group.hpp b/cpp/src/aztec/stdlib/primitives/group/group.hpp index 4fd53beb70..428a4a9a09 100644 --- a/cpp/src/aztec/stdlib/primitives/group/group.hpp +++ b/cpp/src/aztec/stdlib/primitives/group/group.hpp @@ -92,9 +92,10 @@ auto group::fixed_base_scalar_mul_internal(const field_t= num_bits) { - ctx->failed = true; - ctx->err = format( - "fixed_base_scalar_mul scalar multiplier ", scalar_multiplier, " is larger than num_bits ", num_bits); + ctx->failure(format("group::fixed_base_scalar_mul scalar multiplier ", + scalar_multiplier, + " is larger than num_bits ", + num_bits)); } // constexpr size_t num_bits = 250; @@ -231,4 +232,4 @@ auto group::fixed_base_scalar_mul_internal(const field_t #include using namespace barretenberg; -using namespace plonk::stdlib::types::turbo; +using namespace plonk::stdlib::types; namespace { auto& engine = numeric::random::get_debug_engine(); @@ -55,7 +55,7 @@ TEST(stdlib_group, test_fixed_base_scalar_mul_zero_fails) bool proof_result = verifier.verify_proof(proof); EXPECT_EQ(proof_result, false); - EXPECT_EQ(composer.err, "input scalar to fixed_base_scalar_mul_internal cannot be 0"); + EXPECT_EQ(composer.err(), "input scalar to fixed_base_scalar_mul_internal cannot be 0"); } TEST(stdlib_group, test_fixed_base_scalar_mul_with_two_limbs) diff --git a/cpp/src/aztec/stdlib/primitives/memory/rom_table.cpp b/cpp/src/aztec/stdlib/primitives/memory/rom_table.cpp new file mode 100644 index 0000000000..80b8f00398 --- /dev/null +++ b/cpp/src/aztec/stdlib/primitives/memory/rom_table.cpp @@ -0,0 +1,127 @@ +#include "rom_table.hpp" + +#include "../composers/composers.hpp" + +using namespace barretenberg; + +namespace plonk { +namespace stdlib { + +template rom_table::rom_table(const std::vector& table_entries) +{ + static_assert(Composer::type == waffle::ComposerType::PLOOKUP); + // get the composer context + for (const auto& entry : table_entries) { + if (entry.get_context() != nullptr) { + context = entry.get_context(); + break; + } + } + raw_entries = table_entries; + length = raw_entries.size(); + // do not initialize the table yet. The input entries might all be constant, + // if this is the case we might not have a valid pointer to a Composer + // We get around this, by initializing the table when `operator[]` is called + // with a non-const field element. +} + +// initialize the table once we perform a read. This ensures we always have a valid +// pointer to a Composer. +// (if both the table entries and the index are constant, we don't need a composer as we +// can directly extract the desired value from `raw_entries`) +template void rom_table::initialize_table() const +{ + if (initialized) { + return; + } + ASSERT(context != nullptr); + // populate table. Table entries must be normalized and cannot be constants + for (const auto& entry : raw_entries) { + if (entry.is_constant()) { + entries.emplace_back( + field_pt::from_witness_index(context, context->put_constant_variable(entry.get_value()))); + } else { + entries.emplace_back(entry.normalize()); + } + } + rom_id = context->create_ROM_array(length); + + for (size_t i = 0; i < length; ++i) { + context->set_ROM_element(rom_id, i, entries[i].get_witness_index()); + } + + initialized = true; +} + +template +rom_table::rom_table(const rom_table& other) + : raw_entries(other.raw_entries) + , entries(other.entries) + , length(other.length) + , rom_id(other.rom_id) + , initialized(other.initialized) + , context(other.context) +{} + +template +rom_table::rom_table(rom_table&& other) + : raw_entries(other.raw_entries) + , entries(other.entries) + , length(other.length) + , rom_id(other.rom_id) + , initialized(other.initialized) + , context(other.context) +{} + +template rom_table& rom_table::operator=(const rom_table& other) +{ + raw_entries = other.raw_entries; + entries = other.entries; + length = other.length; + rom_id = other.rom_id; + initialized = other.initialized; + context = other.context; + return *this; +} + +template rom_table& rom_table::operator=(rom_table&& other) +{ + raw_entries = other.raw_entries; + entries = other.entries; + length = other.length; + rom_id = other.rom_id; + initialized = other.initialized; + context = other.context; + return *this; +} + +template field_t rom_table::operator[](const size_t index) const +{ + if (index >= length) { + ASSERT(context != nullptr); + context->failure("rom_rable: ROM array access out of bounds"); + } + + return entries[index]; +} + +template field_t rom_table::operator[](const field_pt& index) const +{ + if (index.is_constant()) { + return operator[](static_cast(uint256_t(index.get_value()).data[0])); + } + if (context == nullptr) { + context = index.get_context(); + } + initialize_table(); + if (uint256_t(index.get_value()) >= length) { + context->failure("rom_table: ROM array access out of bounds"); + } + + uint32_t output_idx = context->read_ROM_array(rom_id, index.normalize().get_witness_index()); + return field_pt::from_witness_index(context, output_idx); +} + +INSTANTIATE_STDLIB_ULTRA_TYPE(rom_table); +} // namespace stdlib +} // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/memory/rom_table.hpp b/cpp/src/aztec/stdlib/primitives/memory/rom_table.hpp new file mode 100644 index 0000000000..0b93992e0f --- /dev/null +++ b/cpp/src/aztec/stdlib/primitives/memory/rom_table.hpp @@ -0,0 +1,47 @@ +#pragma once +#include "../composers/composers_fwd.hpp" +#include "../field/field.hpp" + +namespace plonk { +namespace stdlib { + +// A runtime-defined read-only memory table. Table entries must be initialized in the constructor. +// N.B. Only works with the UltraComposer at the moment! +template class rom_table { + private: + typedef field_t field_pt; + + public: + rom_table(){}; + rom_table(const std::vector& table_entries); + rom_table(const rom_table& other); + rom_table(rom_table&& other); + + void initialize_table() const; + + rom_table& operator=(const rom_table& other); + rom_table& operator=(rom_table&& other); + + // read from table with a constant index value. Does not add any gates + field_pt operator[](const size_t index) const; + + // read from table with a witness index value. Adds 2 gates + field_pt operator[](const field_pt& index) const; + + size_t size() const { return length; } + + Composer* get_context() const { return context; } + + private: + std::vector raw_entries; + mutable std::vector entries; + size_t length = 0; + mutable size_t rom_id = 0; // Composer identifier for this ROM table + mutable bool initialized = false; + mutable Composer* context = nullptr; +}; + +EXTERN_STDLIB_ULTRA_TYPE(rom_table); + +} // namespace stdlib +} // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/memory/rom_table.test.cpp b/cpp/src/aztec/stdlib/primitives/memory/rom_table.test.cpp new file mode 100644 index 0000000000..a716b7906a --- /dev/null +++ b/cpp/src/aztec/stdlib/primitives/memory/rom_table.test.cpp @@ -0,0 +1,71 @@ +#include "rom_table.hpp" + +#include + +#include + +#include + +namespace test_stdlib_rom_array { +using namespace barretenberg; +using namespace plonk; + +// Defining ultra-specific types for local testing. +using Composer = waffle::UltraComposer; +using field_ct = stdlib::field_t; +using witness_ct = stdlib::witness_t; +using rom_table_ct = stdlib::rom_table; + +namespace { +auto& engine = numeric::random::get_debug_engine(); +} + +TEST(rom_table, rom_table_read_write_consistency) +{ + Composer composer; + + std::vector table_values; + const size_t table_size = 10; + for (size_t i = 0; i < table_size; ++i) { + table_values.emplace_back(witness_ct(&composer, fr::random_element())); + } + + rom_table_ct table(table_values); + + field_ct result(0); + fr expected(0); + + for (size_t i = 0; i < 10; ++i) { + field_ct index(witness_ct(&composer, (uint64_t)i)); + + if (i % 2 == 0) { + const auto before_n = composer.n; + const auto to_add = table[index]; + const auto after_n = composer.n; + // should cost 1 gates (the ROM read adds 1 extra gate when the proving key is constructed) + // (but not for 1st entry, the 1st ROM read also builts the ROM table, which will cost table_size * 2 gates) + if (i != 0) { + EXPECT_EQ(after_n - before_n, 1ULL); + } + result += to_add; // variable lookup + } else { + const auto before_n = composer.n; + const auto to_add = table[i]; // constant lookup + const auto after_n = composer.n; + // should cost 0 gates. Constant lookups are free + EXPECT_EQ(after_n - before_n, 0ULL); + result += to_add; + } + expected += table_values[i].get_value(); + } + + EXPECT_EQ(result.get_value(), expected); + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + bool verified = verifier.verify_proof(proof); + EXPECT_EQ(verified, true); +} + +} // namespace test_stdlib_rom_array \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/memory/twin_rom_table.cpp b/cpp/src/aztec/stdlib/primitives/memory/twin_rom_table.cpp new file mode 100644 index 0000000000..3a3317a29a --- /dev/null +++ b/cpp/src/aztec/stdlib/primitives/memory/twin_rom_table.cpp @@ -0,0 +1,144 @@ +#include "twin_rom_table.hpp" + +#include "../composers/composers.hpp" + +using namespace barretenberg; + +namespace plonk { +namespace stdlib { + +template +twin_rom_table::twin_rom_table(const std::vector>& table_entries) +{ + static_assert(Composer::type == waffle::ComposerType::PLOOKUP); + // get the composer context + for (const auto& entry : table_entries) { + if (entry[0].get_context() != nullptr) { + context = entry[0].get_context(); + break; + } + if (entry[1].get_context() != nullptr) { + context = entry[1].get_context(); + break; + } + } + raw_entries = table_entries; + length = raw_entries.size(); + // do not initialize the table yet. The input entries might all be constant, + // if this is the case we might not have a valid pointer to a Composer + // We get around this, by initializing the table when `operator[]` is called + // with a non-const field element. +} + +// initialize the table once we perform a read. This ensures we always have a valid +// pointer to a Composer. +// (if both the table entries and the index are constant, we don't need a composer as we +// can directly extract the desired value from `raw_entries`) +template void twin_rom_table::initialize_table() const +{ + if (initialized) { + return; + } + ASSERT(context != nullptr); + // populate table. Table entries must be normalized and cannot be constants + for (const auto& entry : raw_entries) { + field_pt first; + field_pt second; + if (entry[0].is_constant()) { + first = field_pt::from_witness_index(context, context->put_constant_variable(entry[0].get_value())); + } else { + first = entry[0].normalize(); + } + if (entry[1].is_constant()) { + second = field_pt::from_witness_index(context, context->put_constant_variable(entry[1].get_value())); + } else { + second = entry[1].normalize(); + } + entries.emplace_back(field_pair_pt{ first, second }); + } + rom_id = context->create_ROM_array(length); + + for (size_t i = 0; i < length; ++i) { + context->set_ROM_element_pair( + rom_id, i, std::array{ entries[i][0].get_witness_index(), entries[i][1].get_witness_index() }); + } + initialized = true; +} + +template +twin_rom_table::twin_rom_table(const twin_rom_table& other) + : raw_entries(other.raw_entries) + , entries(other.entries) + , length(other.length) + , rom_id(other.rom_id) + , initialized(other.initialized) + , context(other.context) +{} + +template +twin_rom_table::twin_rom_table(twin_rom_table&& other) + : raw_entries(other.raw_entries) + , entries(other.entries) + , length(other.length) + , rom_id(other.rom_id) + , initialized(other.initialized) + , context(other.context) +{} + +template twin_rom_table& twin_rom_table::operator=(const twin_rom_table& other) +{ + raw_entries = other.raw_entries; + entries = other.entries; + length = other.length; + rom_id = other.rom_id; + initialized = other.initialized; + context = other.context; + return *this; +} + +template twin_rom_table& twin_rom_table::operator=(twin_rom_table&& other) +{ + raw_entries = other.raw_entries; + entries = other.entries; + length = other.length; + rom_id = other.rom_id; + initialized = other.initialized; + context = other.context; + return *this; +} + +template +std::array, 2> twin_rom_table::operator[](const size_t index) const +{ + if (index >= length) { + ASSERT(context != nullptr); + context->failure("twin_rom_table: ROM array access out of bounds"); + } + + return entries[index]; +} + +template +std::array, 2> twin_rom_table::operator[](const field_pt& index) const +{ + if (index.is_constant()) { + return operator[](static_cast(uint256_t(index.get_value()).data[0])); + } + if (context == nullptr) { + context = index.get_context(); + } + initialize_table(); + if (uint256_t(index.get_value()) >= length) { + context->failure("twin_rom_table: ROM array access out of bounds"); + } + + auto output_indices = context->read_ROM_array_pair(rom_id, index.normalize().get_witness_index()); + return field_pair_pt{ + field_pt::from_witness_index(context, output_indices[0]), + field_pt::from_witness_index(context, output_indices[1]), + }; +} + +INSTANTIATE_STDLIB_ULTRA_TYPE(twin_rom_table); +} // namespace stdlib +} // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/memory/twin_rom_table.hpp b/cpp/src/aztec/stdlib/primitives/memory/twin_rom_table.hpp new file mode 100644 index 0000000000..555fe9efc2 --- /dev/null +++ b/cpp/src/aztec/stdlib/primitives/memory/twin_rom_table.hpp @@ -0,0 +1,49 @@ +#pragma once +#include "../composers/composers_fwd.hpp" +#include "../field/field.hpp" + +namespace plonk { +namespace stdlib { + +// A runtime-defined read-only memory table. Table entries must be initialized in the constructor. +// Each entry contains a pair of values +// N.B. Only works with the UltraComposer at the moment! +template class twin_rom_table { + private: + typedef field_t field_pt; + typedef std::array field_pair_pt; + + public: + twin_rom_table(){}; + twin_rom_table(const std::vector& table_entries); + twin_rom_table(const twin_rom_table& other); + twin_rom_table(twin_rom_table&& other); + + void initialize_table() const; + + twin_rom_table& operator=(const twin_rom_table& other); + twin_rom_table& operator=(twin_rom_table&& other); + + // read from table with a constant index value. Does not add any gates + field_pair_pt operator[](const size_t index) const; + + // read from table with a witness index value. Adds 2 gates + field_pair_pt operator[](const field_pt& index) const; + + size_t size() const { return length; } + + Composer* get_context() const { return context; } + + private: + std::vector raw_entries; + mutable std::vector entries; + size_t length = 0; + mutable size_t rom_id = 0; // Composer identifier for this ROM table + mutable bool initialized = false; + mutable Composer* context = nullptr; +}; + +EXTERN_STDLIB_ULTRA_TYPE(twin_rom_table); + +} // namespace stdlib +} // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/packed_byte_array/packed_byte_array.cpp b/cpp/src/aztec/stdlib/primitives/packed_byte_array/packed_byte_array.cpp index 18dcbd63b7..e2dcc73237 100644 --- a/cpp/src/aztec/stdlib/primitives/packed_byte_array/packed_byte_array.cpp +++ b/cpp/src/aztec/stdlib/primitives/packed_byte_array/packed_byte_array.cpp @@ -35,8 +35,7 @@ packed_byte_array::packed_byte_array(const std::vector& inpu { ASSERT(bytes_per_input <= BYTES_PER_ELEMENT); if (bytes_per_input > BYTES_PER_ELEMENT) { - context->failed = true; - context->err = "called `packed_byte_array` constructor with `bytes_per_input > 16 bytes"; + context->failure("packed_byte_array: called `packed_byte_array` constructor with `bytes_per_input > 16 bytes"); } // TODO HANDLE CASE WHERE bytes_per_input > BYTES_PER_ELEMENT (and not 32) @@ -46,8 +45,7 @@ packed_byte_array::packed_byte_array(const std::vector& inpu for (size_t i = 0; i < num_elements; ++i) { field_pt limb(context, 0); if (uint256_t(limb.get_value()).get_msb() >= 128) { - context->failed = true; - context->err = "input field element to `packed_byte_array` is >16 bytes!"; + context->failure("packed_byte_array: input field element to `packed_byte_array` is >16 bytes!"); } const size_t num_inputs = (i == num_elements - 1) ? (input.size() - (i * inputs_per_limb)) : inputs_per_limb; for (size_t j = 0; j < num_inputs; ++j) { diff --git a/cpp/src/aztec/stdlib/primitives/packed_byte_array/packed_byte_array.test.cpp b/cpp/src/aztec/stdlib/primitives/packed_byte_array/packed_byte_array.test.cpp index 8b2d31c3cb..df802b51b6 100644 --- a/cpp/src/aztec/stdlib/primitives/packed_byte_array/packed_byte_array.test.cpp +++ b/cpp/src/aztec/stdlib/primitives/packed_byte_array/packed_byte_array.test.cpp @@ -2,7 +2,8 @@ #include "../byte_array/byte_array.hpp" #include -#include +#include +// #include #include @@ -13,14 +14,14 @@ using namespace plonk; namespace { auto& engine = numeric::random::get_debug_engine(); } -typedef stdlib::packed_byte_array packed_byte_array; -typedef stdlib::byte_array byte_array; +typedef stdlib::packed_byte_array packed_byte_array; +typedef stdlib::byte_array byte_array; TEST(packed_byte_array, string_constructor_and_get_value_consistency) { std::string input = "the quick brown fox jumped over the lazy dog."; - waffle::TurboComposer composer = waffle::TurboComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); packed_byte_array arr(&composer, input); @@ -33,7 +34,7 @@ TEST(packed_byte_array, byte_array_constructor_consistency) { std::string input = "the quick brown fox jumped over the lazy dog."; - waffle::TurboComposer composer = waffle::TurboComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); byte_array arr(&composer, input); packed_byte_array converted(arr); @@ -46,7 +47,7 @@ TEST(packed_byte_array, byte_array_cast_consistency) { std::string input = "the quick brown fox jumped over the lazy dog."; - waffle::TurboComposer composer = waffle::TurboComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); packed_byte_array arr(&composer, input); byte_array converted(arr); @@ -70,7 +71,7 @@ TEST(packed_byte_array, unverified_byte_slices) uint32s.push_back(result); } - waffle::TurboComposer composer = waffle::TurboComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); packed_byte_array arr(&composer, bytes); @@ -93,7 +94,7 @@ TEST(packed_byte_array, check_append_uint8) bytes.push_back(engine.get_random_uint8()); } - waffle::TurboComposer composer = waffle::TurboComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); packed_byte_array arr(&composer, bytes); // append upto size (16x) @@ -148,7 +149,7 @@ TEST(packed_byte_array, check_append_uint32) uint32s.push_back(result); } - waffle::TurboComposer composer = waffle::TurboComposer(); + waffle::UltraComposer composer = waffle::UltraComposer(); packed_byte_array arr(&composer, bytes); // append over size (16x) (this creates new limb internally) diff --git a/cpp/src/aztec/stdlib/primitives/plookup/plookup.cpp b/cpp/src/aztec/stdlib/primitives/plookup/plookup.cpp index f4cb600e6d..5ed08d4c8a 100644 --- a/cpp/src/aztec/stdlib/primitives/plookup/plookup.cpp +++ b/cpp/src/aztec/stdlib/primitives/plookup/plookup.cpp @@ -1,9 +1,10 @@ #include "./plookup.hpp" -#include +#include #include +#include namespace waffle { -class PlookupComposer; +class UltraComposer; } // namespace waffle namespace plonk { @@ -12,70 +13,82 @@ namespace stdlib { using namespace barretenberg; template -std::array>, 3> plookup_base::read_sequence_from_table( - const waffle::PlookupMultiTableId id, - const field_t& key_a_in, - const field_t& key_b_in, - const bool is_2_to_1_lookup) +plookup::ReadData> plookup_::get_lookup_accumulators(const MultiTableId id, + const field_t& key_a_in, + const field_t& key_b_in, + const bool is_2_to_1_lookup) { auto key_a = key_a_in.normalize(); auto key_b = key_b_in.normalize(); Composer* ctx = key_a.get_context() ? key_a.get_context() : key_b.get_context(); - const auto sequence_data = - waffle::plookup::get_table_values(id, key_a.get_value(), key_b.get_value(), is_2_to_1_lookup); + const plookup::ReadData lookup_data = + plookup::get_lookup_accumulators(id, key_a.get_value(), key_b.get_value(), is_2_to_1_lookup); - std::array>, 3> sequence_values; - if (key_a.witness_index == IS_CONSTANT && key_b.witness_index == IS_CONSTANT) { - for (size_t i = 0; i < sequence_data.column_1_accumulator_values.size(); ++i) { - sequence_values[0].emplace_back(field_t(ctx, sequence_data.column_1_accumulator_values[i])); - sequence_values[1].emplace_back(field_t(ctx, sequence_data.column_2_accumulator_values[i])); - sequence_values[2].emplace_back(field_t(ctx, sequence_data.column_3_accumulator_values[i])); + const bool is_key_a_constant = key_a.is_constant(); + plookup::ReadData> lookup; + if (is_key_a_constant && (key_b.is_constant() || !is_2_to_1_lookup)) { + for (size_t i = 0; i < lookup_data[ColumnIdx::C1].size(); ++i) { + lookup[ColumnIdx::C1].emplace_back(field_t(ctx, lookup_data[ColumnIdx::C1][i])); + lookup[ColumnIdx::C2].emplace_back(field_t(ctx, lookup_data[ColumnIdx::C2][i])); + lookup[ColumnIdx::C3].emplace_back(field_t(ctx, lookup_data[ColumnIdx::C3][i])); } } else { - auto key_b_witness = std::make_optional(key_b.witness_index); - if (key_b.witness_index == IS_CONSTANT) { + uint32_t lhs_index = key_a.witness_index; + uint32_t rhs_index = key_b.witness_index; + // If only one lookup key is constant, we need to instantiate it as a real witness + if (is_key_a_constant) { + lhs_index = ctx->put_constant_variable(key_a.get_value()); + } + if (key_b.is_constant() && is_2_to_1_lookup) { + rhs_index = ctx->put_constant_variable(key_b.get_value()); + } + + auto key_b_witness = std::make_optional(rhs_index); + if (rhs_index == IS_CONSTANT) { key_b_witness = std::nullopt; } + const auto accumulator_witnesses = + ctx->create_gates_from_plookup_accumulators(id, lookup_data, lhs_index, key_b_witness); - const auto sequence_indices = - ctx->read_sequence_from_multi_table(id, sequence_data, key_a.witness_index, key_b_witness); - for (size_t i = 0; i < sequence_data.column_1_accumulator_values.size(); ++i) { - sequence_values[0].emplace_back(field_t::from_witness_index(ctx, sequence_indices[0][i])); - sequence_values[1].emplace_back(field_t::from_witness_index(ctx, sequence_indices[1][i])); - sequence_values[2].emplace_back(field_t::from_witness_index(ctx, sequence_indices[2][i])); + for (size_t i = 0; i < lookup_data[ColumnIdx::C1].size(); ++i) { + lookup[ColumnIdx::C1].emplace_back( + field_t::from_witness_index(ctx, accumulator_witnesses[ColumnIdx::C1][i])); + lookup[ColumnIdx::C2].emplace_back( + field_t::from_witness_index(ctx, accumulator_witnesses[ColumnIdx::C2][i])); + lookup[ColumnIdx::C3].emplace_back( + field_t::from_witness_index(ctx, accumulator_witnesses[ColumnIdx::C3][i])); } } - return sequence_values; + return lookup; } template -std::pair, field_t> plookup_base::read_pair_from_table( - const waffle::PlookupMultiTableId id, const field_t& key) +std::pair, field_t> plookup_::read_pair_from_table(const MultiTableId id, + const field_t& key) { - const auto sequence_elements = read_sequence_from_table(id, key); + const auto lookup = get_lookup_accumulators(id, key); - return { sequence_elements[1][0], sequence_elements[2][0] }; + return { lookup[ColumnIdx::C2][0], lookup[ColumnIdx::C3][0] }; } template -field_t plookup_base::read_from_2_to_1_table(const waffle::PlookupMultiTableId id, - const field_t& key_a, - const field_t& key_b) +field_t plookup_::read_from_2_to_1_table(const MultiTableId id, + const field_t& key_a, + const field_t& key_b) { - const auto sequence_elements = read_sequence_from_table(id, key_a, key_b, true); + const auto lookup = get_lookup_accumulators(id, key_a, key_b, true); - return sequence_elements[1][0]; + return lookup[ColumnIdx::C2][0]; } template -field_t plookup_base::read_from_1_to_2_table(const waffle::PlookupMultiTableId id, - const field_t& key_a) +field_t plookup_::read_from_1_to_2_table(const MultiTableId id, const field_t& key_a) { - const auto sequence_elements = read_sequence_from_table(id, key_a); + const auto lookup = get_lookup_accumulators(id, key_a); - return sequence_elements[1][0]; + return lookup[ColumnIdx::C2][0]; } -template class plookup_base; +template class plookup_; } // namespace stdlib } // namespace plonk diff --git a/cpp/src/aztec/stdlib/primitives/plookup/plookup.hpp b/cpp/src/aztec/stdlib/primitives/plookup/plookup.hpp index 027f2795df..d523a33a93 100644 --- a/cpp/src/aztec/stdlib/primitives/plookup/plookup.hpp +++ b/cpp/src/aztec/stdlib/primitives/plookup/plookup.hpp @@ -2,37 +2,32 @@ #include #include #include -#include - +#include +#include #include namespace plonk { namespace stdlib { -template class plookup_base { +using namespace plookup; + +template class plookup_ { typedef field_t field_pt; public: - static field_pt read_from_table(const waffle::PlookupMultiTableId id, - const field_pt key_a, - const field_pt key_b = 0); - - static std::pair read_pair_from_table(const waffle::PlookupMultiTableId id, - const field_pt& key); - - static field_pt read_from_2_to_1_table(const waffle::PlookupMultiTableId id, - const field_pt& key_a, - const field_pt& key_b); - static field_pt read_from_1_to_2_table(const waffle::PlookupMultiTableId id, const field_pt& key_a); - - static std::array, 3> read_sequence_from_table(const waffle::PlookupMultiTableId id, - const field_pt& key_a, - const field_pt& key_b = 0, - const bool is_2_to_1_lookup = false); + static std::pair read_pair_from_table(const MultiTableId id, const field_pt& key); + + static field_pt read_from_2_to_1_table(const MultiTableId id, const field_pt& key_a, const field_pt& key_b); + static field_pt read_from_1_to_2_table(const MultiTableId id, const field_pt& key_a); + + static ReadData get_lookup_accumulators(const MultiTableId id, + const field_pt& key_a, + const field_pt& key_b = 0, + const bool is_2_to_1_lookup = false); }; -extern template class plookup_base; +extern template class plookup_; -typedef plookup_base plookup; +typedef plookup_ plookup_read; } // namespace stdlib } // namespace plonk diff --git a/cpp/src/aztec/stdlib/primitives/plookup/plookup.test.cpp b/cpp/src/aztec/stdlib/primitives/plookup/plookup.test.cpp index 50a8073e9b..d60d66b71d 100644 --- a/cpp/src/aztec/stdlib/primitives/plookup/plookup.test.cpp +++ b/cpp/src/aztec/stdlib/primitives/plookup/plookup.test.cpp @@ -1,83 +1,100 @@ #include "plookup.hpp" #include "../byte_array/byte_array.hpp" - -#include - #include -#include - +#include #include -#include +#include +#include +#include +#include +#include +#include namespace test_stdlib_plookups { using namespace barretenberg; using namespace plonk; +using namespace plookup; + +// Defining ultra-specific types for local testing. +using Composer = waffle::UltraComposer; +using field_ct = stdlib::field_t; +using witness_ct = stdlib::witness_t; namespace { auto& engine = numeric::random::get_debug_engine(); } -using namespace plonk::stdlib::types::plookup; +using stdlib::plookup_read; TEST(stdlib_plookup, pedersen_lookup_left) { Composer composer = Composer(); - barretenberg::fr input_value = engine.get_random_uint256() & 0xffffffffULL; - field_ct input = witness_ct(&composer, input_value); + barretenberg::fr input_value = fr::random_element(); + field_ct input_hi = witness_ct(&composer, uint256_t(input_value).slice(126, 256)); + field_ct input_lo = witness_ct(&composer, uint256_t(input_value).slice(0, 126)); - const auto sequence = - plonk::stdlib::plookup::read_sequence_from_table(waffle::PlookupMultiTableId::PEDERSEN_LEFT, input); + const auto lookup_hi = plookup_read::get_lookup_accumulators(MultiTableId::PEDERSEN_LEFT_HI, input_hi); + const auto lookup_lo = plookup_read::get_lookup_accumulators(MultiTableId::PEDERSEN_LEFT_LO, input_lo); std::vector expected_x; std::vector expected_y; - const size_t num_lookups = - (256 + crypto::pedersen::sidon::BITS_PER_TABLE - 1) / crypto::pedersen::sidon::BITS_PER_TABLE; + const size_t num_lookups_hi = + (128 + crypto::pedersen::lookup::BITS_PER_TABLE) / crypto::pedersen::lookup::BITS_PER_TABLE; + const size_t num_lookups_lo = 126 / crypto::pedersen::lookup::BITS_PER_TABLE; - EXPECT_EQ(num_lookups, sequence[0].size()); + EXPECT_EQ(num_lookups_hi, lookup_hi[ColumnIdx::C1].size()); + EXPECT_EQ(num_lookups_lo, lookup_lo[ColumnIdx::C1].size()); + const size_t num_lookups = num_lookups_hi + num_lookups_lo; std::vector expected_scalars; expected_x.resize(num_lookups); expected_y.resize(num_lookups); expected_scalars.resize(num_lookups); { - const size_t num_rounds = (num_lookups + 2) / 3; + const size_t num_rounds = (num_lookups + 1) / 2; uint256_t bits(input_value); - const auto mask = crypto::pedersen::sidon::PEDERSEN_TABLE_SIZE - 1; + const auto mask = crypto::pedersen::lookup::PEDERSEN_TABLE_SIZE - 1; for (size_t i = 0; i < num_rounds; ++i) { - const auto& table = crypto::pedersen::sidon::get_table(i); - const size_t index = i * 3; + const auto& table = crypto::pedersen::lookup::get_table(i); + const size_t index = i * 2; - size_t slice_a = static_cast(((bits >> (index * 10)) & mask).data[0]); + size_t slice_a = + static_cast(((bits >> (index * crypto::pedersen::lookup::BITS_PER_TABLE)) & mask).data[0]); expected_x[index] = (table[slice_a].x); expected_y[index] = (table[slice_a].y); expected_scalars[index] = slice_a; - size_t slice_b = static_cast(((bits >> ((index + 1) * 10)) & mask).data[0]); - expected_x[index + 1] = (table[slice_b].x); - expected_y[index + 1] = (table[slice_b].y); - expected_scalars[index + 1] = slice_b; - - if (i < 8) { - size_t slice_c = static_cast(((bits >> ((index + 2) * 10)) & mask).data[0]); - expected_x[index + 2] = (table[slice_c].x); - expected_y[index + 2] = (table[slice_c].y); - expected_scalars[index + 2] = slice_c; + if (i < 14) { + size_t slice_b = static_cast( + ((bits >> ((index + 1) * crypto::pedersen::lookup::BITS_PER_TABLE)) & mask).data[0]); + expected_x[index + 1] = (table[slice_b].x); + expected_y[index + 1] = (table[slice_b].y); + expected_scalars[index + 1] = slice_b; } } } for (size_t i = num_lookups - 2; i < num_lookups; --i) { - expected_scalars[i] += (expected_scalars[i + 1] * crypto::pedersen::sidon::PEDERSEN_TABLE_SIZE); + expected_scalars[i] += (expected_scalars[i + 1] * crypto::pedersen::lookup::PEDERSEN_TABLE_SIZE); } - for (size_t i = 0; i < num_lookups; ++i) { - EXPECT_EQ(sequence[0][i].get_value(), expected_scalars[i]); - EXPECT_EQ(sequence[1][i].get_value(), expected_x[i]); - EXPECT_EQ(sequence[2][i].get_value(), expected_y[i]); + size_t hi_shift = 126; + const fr hi_cumulative = lookup_hi[ColumnIdx::C1][0].get_value(); + for (size_t i = 0; i < num_lookups_lo; ++i) { + const fr hi_mult = fr(uint256_t(1) << hi_shift); + EXPECT_EQ(lookup_lo[ColumnIdx::C1][i].get_value() + (hi_cumulative * hi_mult), expected_scalars[i]); + EXPECT_EQ(lookup_lo[ColumnIdx::C2][i].get_value(), expected_x[i]); + EXPECT_EQ(lookup_lo[ColumnIdx::C3][i].get_value(), expected_y[i]); + hi_shift -= crypto::pedersen::lookup::BITS_PER_TABLE; + } + for (size_t i = 0; i < num_lookups_hi; ++i) { + EXPECT_EQ(lookup_hi[ColumnIdx::C1][i].get_value(), expected_scalars[i + num_lookups_lo]); + EXPECT_EQ(lookup_hi[ColumnIdx::C2][i].get_value(), expected_x[i + num_lookups_lo]); + EXPECT_EQ(lookup_hi[ColumnIdx::C3][i].get_value(), expected_y[i + num_lookups_lo]); } auto prover = composer.create_prover(); @@ -93,61 +110,71 @@ TEST(stdlib_plookup, pedersen_lookup_right) { Composer composer = Composer(); - barretenberg::fr input_value = engine.get_random_uint256() & 0xffffffffULL; - field_ct input = witness_ct(&composer, input_value); + barretenberg::fr input_value = fr::random_element(); + field_ct input_hi = witness_ct(&composer, uint256_t(input_value).slice(126, 256)); + field_ct input_lo = witness_ct(&composer, uint256_t(input_value).slice(0, 126)); - const auto sequence = - plonk::stdlib::plookup::read_sequence_from_table(waffle::PlookupMultiTableId::PEDERSEN_RIGHT, input); + const auto lookup_hi = plookup_read::get_lookup_accumulators(MultiTableId::PEDERSEN_RIGHT_HI, input_hi); + const auto lookup_lo = plookup_read::get_lookup_accumulators(MultiTableId::PEDERSEN_RIGHT_LO, input_lo); std::vector expected_x; std::vector expected_y; - const size_t num_lookups = - (256 + crypto::pedersen::sidon::BITS_PER_TABLE - 1) / crypto::pedersen::sidon::BITS_PER_TABLE; + const size_t num_lookups_hi = + (128 + crypto::pedersen::lookup::BITS_PER_TABLE) / crypto::pedersen::lookup::BITS_PER_TABLE; + const size_t num_lookups_lo = 126 / crypto::pedersen::lookup::BITS_PER_TABLE; - EXPECT_EQ(num_lookups, sequence[0].size()); + EXPECT_EQ(num_lookups_hi, lookup_hi[ColumnIdx::C1].size()); + EXPECT_EQ(num_lookups_lo, lookup_lo[ColumnIdx::C1].size()); + const size_t num_lookups = num_lookups_hi + num_lookups_lo; std::vector expected_scalars; expected_x.resize(num_lookups); expected_y.resize(num_lookups); expected_scalars.resize(num_lookups); { - const size_t num_rounds = (num_lookups + 2) / 3; + const size_t num_rounds = (num_lookups + 1) / 2; uint256_t bits(input_value); - const auto mask = crypto::pedersen::sidon::PEDERSEN_TABLE_SIZE - 1; + const auto mask = crypto::pedersen::lookup::PEDERSEN_TABLE_SIZE - 1; for (size_t i = 0; i < num_rounds; ++i) { - const auto& table = crypto::pedersen::sidon::get_table(i + num_rounds); - const size_t index = i * 3; + const auto& table = crypto::pedersen::lookup::get_table(i + num_rounds); + const size_t index = i * 2; - size_t slice_a = static_cast(((bits >> (index * 10)) & mask).data[0]); + size_t slice_a = + static_cast(((bits >> (index * crypto::pedersen::lookup::BITS_PER_TABLE)) & mask).data[0]); expected_x[index] = (table[slice_a].x); expected_y[index] = (table[slice_a].y); expected_scalars[index] = slice_a; - size_t slice_b = static_cast(((bits >> ((index + 1) * 10)) & mask).data[0]); - expected_x[index + 1] = (table[slice_b].x); - expected_y[index + 1] = (table[slice_b].y); - expected_scalars[index + 1] = slice_b; - - if (i < 8) { - size_t slice_c = static_cast(((bits >> ((index + 2) * 10)) & mask).data[0]); - expected_x[index + 2] = (table[slice_c].x); - expected_y[index + 2] = (table[slice_c].y); - expected_scalars[index + 2] = slice_c; + if (i < 14) { + size_t slice_b = static_cast( + ((bits >> ((index + 1) * crypto::pedersen::lookup::BITS_PER_TABLE)) & mask).data[0]); + expected_x[index + 1] = (table[slice_b].x); + expected_y[index + 1] = (table[slice_b].y); + expected_scalars[index + 1] = slice_b; } } } for (size_t i = num_lookups - 2; i < num_lookups; --i) { - expected_scalars[i] += (expected_scalars[i + 1] * crypto::pedersen::sidon::PEDERSEN_TABLE_SIZE); + expected_scalars[i] += (expected_scalars[i + 1] * crypto::pedersen::lookup::PEDERSEN_TABLE_SIZE); } - for (size_t i = 0; i < num_lookups; ++i) { - EXPECT_EQ(sequence[0][i].get_value(), expected_scalars[i]); - EXPECT_EQ(sequence[1][i].get_value(), expected_x[i]); - EXPECT_EQ(sequence[2][i].get_value(), expected_y[i]); + size_t hi_shift = 126; + const fr hi_cumulative = lookup_hi[ColumnIdx::C1][0].get_value(); + for (size_t i = 0; i < num_lookups_lo; ++i) { + const fr hi_mult = fr(uint256_t(1) << hi_shift); + EXPECT_EQ(lookup_lo[ColumnIdx::C1][i].get_value() + (hi_cumulative * hi_mult), expected_scalars[i]); + EXPECT_EQ(lookup_lo[ColumnIdx::C2][i].get_value(), expected_x[i]); + EXPECT_EQ(lookup_lo[ColumnIdx::C3][i].get_value(), expected_y[i]); + hi_shift -= crypto::pedersen::lookup::BITS_PER_TABLE; + } + for (size_t i = 0; i < num_lookups_hi; ++i) { + EXPECT_EQ(lookup_hi[ColumnIdx::C1][i].get_value(), expected_scalars[i + num_lookups_lo]); + EXPECT_EQ(lookup_hi[ColumnIdx::C2][i].get_value(), expected_x[i + num_lookups_lo]); + EXPECT_EQ(lookup_hi[ColumnIdx::C3][i].get_value(), expected_y[i + num_lookups_lo]); } auto prover = composer.create_prover(); @@ -171,8 +198,266 @@ TEST(stdlib_plookup, uint32_xor) field_ct left = witness_ct(&composer, barretenberg::fr(left_value)); field_ct right = witness_ct(&composer, barretenberg::fr(right_value)); - const auto sequence = - plonk::stdlib::plookup::read_sequence_from_table(waffle::PlookupMultiTableId::UINT32_XOR, left, right, true); + const auto lookup = plookup_read::get_lookup_accumulators(MultiTableId::UINT32_XOR, left, right, true); + + const auto left_slices = numeric::slice_input(left_value, 1 << 6, num_lookups); + const auto right_slices = numeric::slice_input(right_value, 1 << 6, num_lookups); + + std::vector out_expected(num_lookups); + std::vector left_expected(num_lookups); + std::vector right_expected(num_lookups); + + for (size_t i = 0; i < left_slices.size(); ++i) { + out_expected[i] = left_slices[i] ^ right_slices[i]; + left_expected[i] = left_slices[i]; + right_expected[i] = right_slices[i]; + } + + for (size_t i = num_lookups - 2; i < num_lookups; --i) { + out_expected[i] += out_expected[i + 1] * (1 << 6); + left_expected[i] += left_expected[i + 1] * (1 << 6); + right_expected[i] += right_expected[i + 1] * (1 << 6); + } + + for (size_t i = 0; i < num_lookups; ++i) { + EXPECT_EQ(lookup[ColumnIdx::C1][i].get_value(), barretenberg::fr(left_expected[i])); + EXPECT_EQ(lookup[ColumnIdx::C2][i].get_value(), barretenberg::fr(right_expected[i])); + EXPECT_EQ(lookup[ColumnIdx::C3][i].get_value(), barretenberg::fr(out_expected[i])); + } + + auto prover = composer.create_prover(); + + auto verifier = composer.create_verifier(); + + auto proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + + EXPECT_EQ(result, true); +} + +TEST(stdlib_plookup, blake2s_xor_rotate_16) +{ + Composer composer = Composer(); + + const size_t num_lookups = 6; + + uint256_t left_value = (engine.get_random_uint256() & 0xffffffffULL); + uint256_t right_value = (engine.get_random_uint256() & 0xffffffffULL); + + field_ct left = witness_ct(&composer, barretenberg::fr(left_value)); + field_ct right = witness_ct(&composer, barretenberg::fr(right_value)); + + const auto lookup = plookup_read::get_lookup_accumulators(MultiTableId::BLAKE_XOR_ROTATE_16, left, right, true); + + const auto left_slices = numeric::slice_input(left_value, 1 << 6, num_lookups); + const auto right_slices = numeric::slice_input(right_value, 1 << 6, num_lookups); + + std::vector out_expected(num_lookups); + std::vector left_expected(num_lookups); + std::vector right_expected(num_lookups); + + for (size_t i = 0; i < left_slices.size(); ++i) { + if (i == 2) { + uint32_t a = static_cast(left_slices[i]); + uint32_t b = static_cast(right_slices[i]); + uint32_t c = numeric::rotate32(a ^ b, 4); + out_expected[i] = uint256_t(c); + } else { + out_expected[i] = uint256_t(left_slices[i]) ^ uint256_t(right_slices[i]); + } + left_expected[i] = left_slices[i]; + right_expected[i] = right_slices[i]; + } + + /* + * The following out coefficients are the the ones multiplied for computing the cumulative intermediate terms + * in the expected output. If the column_3_coefficients for this table are (a0, a1, ..., a5), then the + * out_coefficients must be (a5/a4, a4/a3, a3/a2, a2/a1, a1/a0). Note that these are stored in reverse orde + * for simplicity. + */ + std::vector out_coefficients{ + (1 << 6), (barretenberg::fr(1) / barretenberg::fr(1 << 22)), (1 << 2), (1 << 6), (1 << 6) + }; + + for (size_t i = num_lookups - 2; i < num_lookups; --i) { + out_expected[i] += out_expected[i + 1] * out_coefficients[i]; + left_expected[i] += left_expected[i + 1] * (1 << 6); + right_expected[i] += right_expected[i + 1] * (1 << 6); + } + + for (size_t i = 0; i < num_lookups; ++i) { + EXPECT_EQ(lookup[ColumnIdx::C1][i].get_value(), left_expected[i]); + EXPECT_EQ(lookup[ColumnIdx::C2][i].get_value(), right_expected[i]); + EXPECT_EQ(lookup[ColumnIdx::C3][i].get_value(), out_expected[i]); + } + + /* + * Note that we multiply the output of the lookup table (lookup[Column::Idx}0]) by 2^{16} because + * while defining the table we had set the coefficient of s0 to 1, so to correct that, we need to multiply by a + * constant. + */ + auto mul_constant = barretenberg::fr(1 << 16); + barretenberg::fr lookup_output = lookup[ColumnIdx::C3][0].get_value() * mul_constant; + uint32_t xor_rotate_output = numeric::rotate32(uint32_t(left_value) ^ uint32_t(right_value), 16); + EXPECT_EQ(barretenberg::fr(uint256_t(xor_rotate_output)), lookup_output); + + auto prover = composer.create_prover(); + + auto verifier = composer.create_verifier(); + + auto proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + + EXPECT_EQ(result, true); +} + +TEST(stdlib_plookup, blake2s_xor_rotate_8) +{ + Composer composer = Composer(); + + const size_t num_lookups = 6; + + uint256_t left_value = (engine.get_random_uint256() & 0xffffffffULL); + uint256_t right_value = (engine.get_random_uint256() & 0xffffffffULL); + + field_ct left = witness_ct(&composer, barretenberg::fr(left_value)); + field_ct right = witness_ct(&composer, barretenberg::fr(right_value)); + + const auto lookup = plookup_read::get_lookup_accumulators(MultiTableId::BLAKE_XOR_ROTATE_8, left, right, true); + + const auto left_slices = numeric::slice_input(left_value, 1 << 6, num_lookups); + const auto right_slices = numeric::slice_input(right_value, 1 << 6, num_lookups); + + std::vector out_expected(num_lookups); + std::vector left_expected(num_lookups); + std::vector right_expected(num_lookups); + + for (size_t i = 0; i < left_slices.size(); ++i) { + if (i == 1) { + uint32_t a = static_cast(left_slices[i]); + uint32_t b = static_cast(right_slices[i]); + uint32_t c = numeric::rotate32(a ^ b, 2); + out_expected[i] = uint256_t(c); + } else { + out_expected[i] = uint256_t(left_slices[i]) ^ uint256_t(right_slices[i]); + } + left_expected[i] = left_slices[i]; + right_expected[i] = right_slices[i]; + } + + auto mul_constant = barretenberg::fr(1 << 24); + std::vector out_coefficients{ + (barretenberg::fr(1) / mul_constant), (1 << 4), (1 << 6), (1 << 6), (1 << 6) + }; + + for (size_t i = num_lookups - 2; i < num_lookups; --i) { + out_expected[i] += out_expected[i + 1] * out_coefficients[i]; + left_expected[i] += left_expected[i + 1] * (1 << 6); + right_expected[i] += right_expected[i + 1] * (1 << 6); + } + + for (size_t i = 0; i < num_lookups; ++i) { + EXPECT_EQ(lookup[ColumnIdx::C1][i].get_value(), left_expected[i]); + EXPECT_EQ(lookup[ColumnIdx::C2][i].get_value(), right_expected[i]); + EXPECT_EQ(lookup[ColumnIdx::C3][i].get_value(), out_expected[i]); + } + + barretenberg::fr lookup_output = lookup[ColumnIdx::C3][0].get_value() * mul_constant; + uint32_t xor_rotate_output = numeric::rotate32(uint32_t(left_value) ^ uint32_t(right_value), 8); + EXPECT_EQ(barretenberg::fr(uint256_t(xor_rotate_output)), lookup_output); + + auto prover = composer.create_prover(); + + auto verifier = composer.create_verifier(); + + auto proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + + EXPECT_EQ(result, true); +} + +TEST(stdlib_plookup, blake2s_xor_rotate_7) +{ + Composer composer = Composer(); + + const size_t num_lookups = 6; + + uint256_t left_value = (engine.get_random_uint256() & 0xffffffffULL); + uint256_t right_value = (engine.get_random_uint256() & 0xffffffffULL); + + field_ct left = witness_ct(&composer, barretenberg::fr(left_value)); + field_ct right = witness_ct(&composer, barretenberg::fr(right_value)); + + const auto lookup = plookup_read::get_lookup_accumulators(MultiTableId::BLAKE_XOR_ROTATE_7, left, right, true); + + const auto left_slices = numeric::slice_input(left_value, 1 << 6, num_lookups); + const auto right_slices = numeric::slice_input(right_value, 1 << 6, num_lookups); + + std::vector out_expected(num_lookups); + std::vector left_expected(num_lookups); + std::vector right_expected(num_lookups); + + for (size_t i = 0; i < left_slices.size(); ++i) { + if (i == 1) { + uint32_t a = static_cast(left_slices[i]); + uint32_t b = static_cast(right_slices[i]); + uint32_t c = numeric::rotate32(a ^ b, 1); + out_expected[i] = uint256_t(c); + } else { + out_expected[i] = uint256_t(left_slices[i]) ^ uint256_t(right_slices[i]); + } + left_expected[i] = left_slices[i]; + right_expected[i] = right_slices[i]; + } + + auto mul_constant = barretenberg::fr(1 << 25); + std::vector out_coefficients{ + (barretenberg::fr(1) / mul_constant), (1 << 5), (1 << 6), (1 << 6), (1 << 6) + }; + + for (size_t i = num_lookups - 2; i < num_lookups; --i) { + out_expected[i] += out_expected[i + 1] * out_coefficients[i]; + left_expected[i] += left_expected[i + 1] * (1 << 6); + right_expected[i] += right_expected[i + 1] * (1 << 6); + } + + for (size_t i = 0; i < num_lookups; ++i) { + EXPECT_EQ(lookup[ColumnIdx::C1][i].get_value(), left_expected[i]); + EXPECT_EQ(lookup[ColumnIdx::C2][i].get_value(), right_expected[i]); + EXPECT_EQ(lookup[ColumnIdx::C3][i].get_value(), out_expected[i]); + } + + barretenberg::fr lookup_output = lookup[ColumnIdx::C3][0].get_value() * mul_constant; + uint32_t xor_rotate_output = numeric::rotate32(uint32_t(left_value) ^ uint32_t(right_value), 7); + EXPECT_EQ(barretenberg::fr(uint256_t(xor_rotate_output)), lookup_output); + + auto prover = composer.create_prover(); + + auto verifier = composer.create_verifier(); + + auto proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + + EXPECT_EQ(result, true); +} + +TEST(stdlib_plookup, blake2s_xor) +{ + Composer composer = Composer(); + + const size_t num_lookups = 6; + + uint256_t left_value = (engine.get_random_uint256() & 0xffffffffULL); + uint256_t right_value = (engine.get_random_uint256() & 0xffffffffULL); + + field_ct left = witness_ct(&composer, barretenberg::fr(left_value)); + field_ct right = witness_ct(&composer, barretenberg::fr(right_value)); + + const auto lookup = plookup_read::get_lookup_accumulators(MultiTableId::BLAKE_XOR, left, right, true); const auto left_slices = numeric::slice_input(left_value, 1 << 6, num_lookups); const auto right_slices = numeric::slice_input(right_value, 1 << 6, num_lookups); @@ -187,16 +472,35 @@ TEST(stdlib_plookup, uint32_xor) right_expected[i] = right_slices[i]; } + // Compute ror(a ^ b, 12) from lookup table. + // t0 = 2^30 a5 + 2^24 a4 + 2^18 a3 + 2^12 a2 + 2^6 a1 + a0 + // t1 = 2^24 a5 + 2^18 a4 + 2^12 a3 + 2^6 a2 + a1 + // t2 = 2^18 a5 + 2^12 a4 + 2^6 a3 + a2 + // t3 = 2^12 a5 + 2^6 a4 + a3 + // t4 = 2^6 a5 + a4 + // t5 = a5 + // + // output = (t0 - 2^12 t2) * 2^{32 - 12} + t2 + barretenberg::fr lookup_output = lookup[ColumnIdx::C3][2].get_value(); + barretenberg::fr t2_term = barretenberg::fr(1 << 12) * lookup[ColumnIdx::C3][2].get_value(); + lookup_output += barretenberg::fr(1 << 20) * (lookup[ColumnIdx::C3][0].get_value() - t2_term); + for (size_t i = num_lookups - 2; i < num_lookups; --i) { out_expected[i] += out_expected[i + 1] * (1 << 6); left_expected[i] += left_expected[i + 1] * (1 << 6); right_expected[i] += right_expected[i + 1] * (1 << 6); } + // + // The following checks if the xor output rotated by 12 can be computed correctly from basic blake2s_xor. + // + auto xor_rotate_output = numeric::rotate32(uint32_t(left_value) ^ uint32_t(right_value), 12); + EXPECT_EQ(barretenberg::fr(uint256_t(xor_rotate_output)), lookup_output); + for (size_t i = 0; i < num_lookups; ++i) { - EXPECT_EQ(sequence[0][i].get_value(), barretenberg::fr(left_expected[i])); - EXPECT_EQ(sequence[1][i].get_value(), barretenberg::fr(right_expected[i])); - EXPECT_EQ(sequence[2][i].get_value(), barretenberg::fr(out_expected[i])); + EXPECT_EQ(lookup[ColumnIdx::C1][i].get_value(), barretenberg::fr(left_expected[i])); + EXPECT_EQ(lookup[ColumnIdx::C2][i].get_value(), barretenberg::fr(right_expected[i])); + EXPECT_EQ(lookup[ColumnIdx::C3][i].get_value(), barretenberg::fr(out_expected[i])); } auto prover = composer.create_prover(); @@ -222,8 +526,7 @@ TEST(stdlib_plookup, uint32_and) field_ct left = witness_ct(&composer, barretenberg::fr(left_value)); field_ct right = witness_ct(&composer, barretenberg::fr(right_value)); - const auto sequence = - plonk::stdlib::plookup::read_sequence_from_table(waffle::PlookupMultiTableId::UINT32_AND, left, right, true); + const auto lookup = plookup_read::get_lookup_accumulators(MultiTableId::UINT32_AND, left, right, true); const auto left_slices = numeric::slice_input(left_value, 1 << 6, num_lookups); const auto right_slices = numeric::slice_input(right_value, 1 << 6, num_lookups); std::vector out_expected(num_lookups); @@ -243,9 +546,9 @@ TEST(stdlib_plookup, uint32_and) } for (size_t i = 0; i < num_lookups; ++i) { - EXPECT_EQ(sequence[0][i].get_value(), barretenberg::fr(left_expected[i])); - EXPECT_EQ(sequence[1][i].get_value(), barretenberg::fr(right_expected[i])); - EXPECT_EQ(sequence[2][i].get_value(), barretenberg::fr(out_expected[i])); + EXPECT_EQ(lookup[ColumnIdx::C1][i].get_value(), barretenberg::fr(left_expected[i])); + EXPECT_EQ(lookup[ColumnIdx::C2][i].get_value(), barretenberg::fr(right_expected[i])); + EXPECT_EQ(lookup[ColumnIdx::C3][i].get_value(), barretenberg::fr(out_expected[i])); } auto prover = composer.create_prover(); @@ -259,4 +562,102 @@ TEST(stdlib_plookup, uint32_and) EXPECT_EQ(result, true); } +TEST(stdlib_plookup, secp256k1_generator) +{ + using curve = stdlib::secp256k1; + Composer composer = Composer(); + + uint256_t input_value = (engine.get_random_uint256() >> 128); + + uint64_t wnaf_entries[18] = { 0 }; + bool skew = false; + barretenberg::wnaf::fixed_wnaf<129, 1, 8>(&input_value.data[0], &wnaf_entries[0], skew, 0); + + std::vector naf_values; + for (size_t i = 0; i < 17; ++i) { + bool predicate = bool((wnaf_entries[i] >> 31U) & 1U); + uint64_t offset_entry; + if (predicate) { + offset_entry = (127 - (wnaf_entries[i] & 0xffffff)); + } else { + offset_entry = (128 + (wnaf_entries[i] & 0xffffff)); + } + naf_values.emplace_back(offset_entry); + } + + std::vector circuit_naf_values; + for (size_t i = 0; i < naf_values.size(); ++i) { + circuit_naf_values.emplace_back(witness_ct(&composer, naf_values[i])); + } + + std::vector accumulators; + for (size_t i = 0; i < naf_values.size(); ++i) { + field_ct t1 = (circuit_naf_values[naf_values.size() - 1 - i]) * field_ct(uint256_t(1) << (i * 8 + 1)); + field_ct t2 = field_ct(255) * field_ct(uint256_t(1) << (i * 8)); + accumulators.emplace_back(t1 - t2); + } + field_ct accumulator_field = field_ct::accumulate(accumulators); + EXPECT_EQ(accumulator_field.get_value(), barretenberg::fr(input_value) + barretenberg::fr(skew)); + + for (size_t i = 0; i < 256; ++i) { + field_ct index(witness_ct(&composer, barretenberg::fr(i))); + const auto xlo = plookup_read::read_pair_from_table(MultiTableId::SECP256K1_XLO, index); + 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); + + const auto res = curve::g1_ct(x, y).get_value(); + curve::fr scalar(i); + scalar = scalar + scalar; + scalar = scalar - 255; + curve::g1::affine_element expec(curve::g1::one * scalar); + + EXPECT_EQ(res, expec); + } + curve::g1_ct accumulator; + { + const auto xlo = plookup_read::read_pair_from_table(MultiTableId::SECP256K1_XLO, circuit_naf_values[0]); + const auto xhi = plookup_read::read_pair_from_table(MultiTableId::SECP256K1_XHI, circuit_naf_values[0]); + 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); + accumulator = curve::g1_ct(x, y); + } + for (size_t i = 1; i < circuit_naf_values.size(); ++i) { + accumulator = accumulator.dbl(); + accumulator = accumulator.dbl(); + accumulator = accumulator.dbl(); + accumulator = accumulator.dbl(); + accumulator = accumulator.dbl(); + accumulator = accumulator.dbl(); + accumulator = accumulator.dbl(); + + const auto xlo = plookup_read::read_pair_from_table(MultiTableId::SECP256K1_XLO, circuit_naf_values[i]); + 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); + accumulator = accumulator.montgomery_ladder(curve::g1_ct(x, y)); + } + + if (skew) { + accumulator = accumulator - curve::g1_ct::one(&composer); + } + + curve::g1::affine_element result = accumulator.get_value(); + curve::g1::affine_element expected(curve::g1::one * input_value); + EXPECT_EQ(result, expected); + + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); +} + } // namespace test_stdlib_plookups diff --git a/cpp/src/aztec/stdlib/primitives/safe_uint/safe_uint.hpp b/cpp/src/aztec/stdlib/primitives/safe_uint/safe_uint.hpp index 93864fef36..309c751198 100644 --- a/cpp/src/aztec/stdlib/primitives/safe_uint/safe_uint.hpp +++ b/cpp/src/aztec/stdlib/primitives/safe_uint/safe_uint.hpp @@ -32,8 +32,8 @@ template class safe_uint_t { public: // The following constant should be small enough that any thing with this bitnum is smaller than the modulus - static constexpr size_t MAX_BIT_NUM = fr::modulus.get_msb(); - static constexpr uint256_t MAX_VALUE = fr::modulus - 1; + static constexpr size_t MAX_BIT_NUM = barretenberg::fr::modulus.get_msb(); + static constexpr uint256_t MAX_VALUE = barretenberg::fr::modulus - 1; static constexpr size_t IS_UNSAFE = 143; // weird constant to make it hard to use accidentally // Make sure our uint256 values don't wrap - add_two function sums three of these static_assert((uint512_t)MAX_VALUE * 3 < (uint512_t)1 << 256); @@ -76,7 +76,8 @@ template class safe_uint_t { , current_max(other.current_max) {} - static safe_uint_t create_constant_witness(ComposerContext* parent_context, fr const& value) + static safe_uint_t create_constant_witness(ComposerContext* parent_context, + barretenberg::fr const& value) { witness_t out(parent_context, value); diff --git a/cpp/src/aztec/stdlib/primitives/safe_uint/safe_uint.test.cpp b/cpp/src/aztec/stdlib/primitives/safe_uint/safe_uint.test.cpp index bb5a0b81b8..bd68ce40f6 100644 --- a/cpp/src/aztec/stdlib/primitives/safe_uint/safe_uint.test.cpp +++ b/cpp/src/aztec/stdlib/primitives/safe_uint/safe_uint.test.cpp @@ -4,34 +4,37 @@ #include #include "../byte_array/byte_array.hpp" #include -#include +#include #include +#include namespace { auto& engine = numeric::random::get_debug_engine(); } +using namespace plonk::stdlib::types; + namespace test_stdlib_safe_uint { template void ignore_unused(T&) {} // use to ignore unused variables in lambdas -typedef plonk::stdlib::bool_t bool_t; -typedef plonk::stdlib::field_t field_t; -typedef plonk::stdlib::safe_uint_t suint_t; -typedef plonk::stdlib::witness_t witness_t; -typedef plonk::stdlib::public_witness_t public_witness_t; -typedef plonk::stdlib::byte_array byte_array_t; +typedef plonk::stdlib::bool_t bool_t; +typedef plonk::stdlib::field_t field_t; +typedef plonk::stdlib::safe_uint_t suint_t; +typedef plonk::stdlib::witness_t witness_t; +typedef plonk::stdlib::public_witness_t public_witness_t; +typedef plonk::stdlib::byte_array byte_array_t; struct verify_logic_result { bool valid; std::string err; }; -verify_logic_result verify_logic(waffle::TurboComposer& composer) +verify_logic_result verify_logic(Composer& composer) { - if (composer.failed) { - info("Circuit logic failed: " + composer.err); + if (composer.failed()) { + info("Circuit logic failed: " + composer.err()); } - return { !composer.failed, composer.err }; + return { !composer.failed(), composer.err() }; } // CONSTRUCTOR @@ -40,7 +43,7 @@ TEST(stdlib_safeuint, test_constructor_with_value_out_of_range_fails) { // check incorrect range init causes failure - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 100)); suint_t b(a, 2, "b"); @@ -51,12 +54,12 @@ TEST(stdlib_safeuint, test_constructor_with_value_out_of_range_fails) TEST(stdlib_safeuint, test_constructor_with_value_in_range) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 100)); suint_t b(a, 7); - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); EXPECT_TRUE(verifier.verify_proof(proof)); } @@ -69,7 +72,7 @@ TEST(stdlib_safeuint, test_multiply_operation_out_of_range_fails) // should allow largest power of 3 smaller than r iterations, which is 159. Hence below we should exceed r, and // expect a throw try { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 2)); suint_t c(a, 2); suint_t d(a, 2); @@ -88,7 +91,7 @@ TEST(stdlib_safeuint, test_multiply_operation_on_constants_out_of_range_fails) { // Now we check that when using constants the maximum grows more slowly - since they are bounded by themselves // rather than the next 2^n-1 - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 2)); suint_t c(a, 2); suint_t d(fr(2)); @@ -99,7 +102,7 @@ TEST(stdlib_safeuint, test_multiply_operation_on_constants_out_of_range_fails) // Below we should exceed r, and expect a throw try { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 2)); suint_t c(a, 2); suint_t d(fr(2)); @@ -120,7 +123,7 @@ TEST(stdlib_safeuint, test_add_operation_out_of_range_fails) { // Here we test the addition operator also causes a throw when exceeding r try { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 2)); suint_t c(a, 2); suint_t d(a, 2); @@ -141,15 +144,15 @@ TEST(stdlib_safeuint, test_add_operation_out_of_range_fails) TEST(stdlib_safeuint, test_subtract_method) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 2)); field_t b(witness_t(&composer, 9)); suint_t c(a, 2); suint_t d(b, 4); c = d.subtract(c, 3); - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); EXPECT_TRUE(verifier.verify_proof(proof)); } @@ -157,7 +160,7 @@ TEST(stdlib_safeuint, test_subtract_method) TEST(stdlib_safeuint, test_subtract_method_minued_gt_lhs_fails) { // test failure when range for difference too small - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 2)); field_t b(witness_t(&composer, 9)); suint_t c(a, 2, "c"); @@ -172,7 +175,7 @@ TEST(stdlib_safeuint, test_subtract_method_minued_gt_lhs_fails) TEST(stdlib_safeuint, test_subtract_method_underflow_fails) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 2)); field_t b(witness_t(&composer, field_t::modulus / 2)); suint_t c(a, 2); @@ -191,15 +194,15 @@ TEST(stdlib_safeuint, test_subtract_method_underflow_fails) TEST(stdlib_safeuint, test_minus_operator) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 2)); field_t b(witness_t(&composer, 9)); suint_t c(a, 2); suint_t d(b, 4); c = d - c; - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); EXPECT_TRUE(verifier.verify_proof(proof)); } @@ -207,7 +210,7 @@ TEST(stdlib_safeuint, test_minus_operator) TEST(stdlib_safeuint, test_minus_operator_underflow_fails) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 2)); field_t b(witness_t(&composer, field_t::modulus / 2)); suint_t c(a, 2); @@ -226,7 +229,7 @@ TEST(stdlib_safeuint, test_minus_operator_underflow_fails) TEST(stdlib_safeuint, test_divide_method) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a1(witness_t(&composer, 2)); field_t b1(witness_t(&composer, 9)); @@ -240,19 +243,19 @@ TEST(stdlib_safeuint, test_divide_method) suint_t d2(b2, 32); c2 = d2.divide(c2, 32, 8); - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); EXPECT_TRUE(verifier.verify_proof(proof)); } TEST(stdlib_safeuint, test_divide_method_quotient_range_too_small_fails) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 2)); field_t b(witness_t(&composer, 32)); suint_t c(a, 2); - suint_t d(b, 5); + suint_t d(b, 6); d = d.divide(c, 4, 1, "d/c"); auto result = verify_logic(composer); @@ -263,7 +266,7 @@ TEST(stdlib_safeuint, test_divide_method_quotient_range_too_small_fails) TEST(stdlib_safeuint, test_divide_remainder_too_large) { // test failure when range for remainder too small - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 5)); suint_t c(a, 3); suint_t d((fr::modulus - 1) / 3); @@ -274,12 +277,12 @@ TEST(stdlib_safeuint, test_divide_remainder_too_large) TEST(stdlib_safeuint, test_divide_method_quotient_remainder_incorrect_fails) { // test failure when quotient and remainder values are wrong - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 5)); field_t b(witness_t(&composer, 19)); suint_t c(a, 3); suint_t d(b, 5); - d = d.divide(c, 3, 1, "d/c", [](uint256_t, uint256_t) { return std::make_pair(2, 3); }); + d = d.divide(c, 3, 2, "d/c", [](uint256_t, uint256_t) { return std::make_pair(2, 3); }); auto result = verify_logic(composer); EXPECT_FALSE(result.valid); @@ -289,7 +292,7 @@ TEST(stdlib_safeuint, test_divide_method_quotient_remainder_incorrect_fails) TEST(stdlib_safeuint, test_divide_method_quotient_remainder_mod_r_fails) { // test failure when quotient and remainder are only correct mod r - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 5)); field_t b(witness_t(&composer, 19)); suint_t c(a, 3); @@ -305,15 +308,15 @@ TEST(stdlib_safeuint, test_divide_method_quotient_remainder_mod_r_fails) TEST(stdlib_safeuint, test_div_operator) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); suint_t a(witness_t(&composer, 1000), 10, "a"); suint_t b(2, 2, "b"); a = a / b; - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); EXPECT_TRUE(verifier.verify_proof(proof)); } @@ -324,7 +327,7 @@ TEST(stdlib_safeuint, test_divide_operator) { // test success cases { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a1(witness_t(&composer, 2)); field_t b1(witness_t(&composer, 9)); @@ -338,64 +341,64 @@ TEST(stdlib_safeuint, test_divide_operator) suint_t d2(b2, 32); d2 / c2; - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool result = verifier.verify_proof(proof); EXPECT_EQ(result, true); } // test failure when range for quotient too small { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 2)); field_t b(witness_t(&composer, 32)); suint_t c(a, 2); suint_t d(b, 5); d = d / c; - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool result = verifier.verify_proof(proof); EXPECT_EQ(result, false); } // test failure when range for remainder too small { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 5)); field_t b(witness_t(&composer, 19)); suint_t c(a, 2); suint_t d(b, 5); d = d / c; - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool result = verifier.verify_proof(proof); EXPECT_EQ(result, false); } // test failure when quotient and remainder values are wrong { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 5)); field_t b(witness_t(&composer, 19)); suint_t c(a, 2); suint_t d(b, 5); d = d / c; - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool result = verifier.verify_proof(proof); EXPECT_EQ(result, false); } // test failure when quotient and remainder are only correct mod r { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t a(witness_t(&composer, 5)); field_t b(witness_t(&composer, 19)); suint_t c(a, 2); suint_t d(b, 5); d = d / c; - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool result = verifier.verify_proof(proof); EXPECT_EQ(result, false); @@ -406,7 +409,7 @@ TEST(stdlib_safeuint, test_divide_operator) TEST(stdlib_safeuint, test_slice) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); // 0b11110110101001011 // ^ ^ // msb lsb @@ -420,8 +423,8 @@ TEST(stdlib_safeuint, test_slice) EXPECT_EQ(slice_data[1].get_value(), fr(169)); EXPECT_EQ(slice_data[2].get_value(), fr(61)); - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool result = verifier.verify_proof(proof); @@ -430,7 +433,7 @@ TEST(stdlib_safeuint, test_slice) TEST(stdlib_safeuint, test_slice_equal_msb_lsb) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); // 0b11110110101001011 // ^ // msb = lsb @@ -444,8 +447,8 @@ TEST(stdlib_safeuint, test_slice_equal_msb_lsb) EXPECT_EQ(slice_data[1].get_value(), fr(1)); EXPECT_EQ(slice_data[2].get_value(), fr(986)); - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool result = verifier.verify_proof(proof); @@ -454,7 +457,7 @@ TEST(stdlib_safeuint, test_slice_equal_msb_lsb) TEST(stdlib_safeuint, test_slice_random) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); uint8_t lsb = 106; uint8_t msb = 189; @@ -470,8 +473,8 @@ TEST(stdlib_safeuint, test_slice_random) EXPECT_EQ(slice[1].get_value(), fr(expected1)); EXPECT_EQ(slice[2].get_value(), fr(expected2)); - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool result = verifier.verify_proof(proof); @@ -484,7 +487,7 @@ TEST(stdlib_safeuint, test_slice_random) TEST(stdlib_safeuint, operator_div_remainder_constraint) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); uint256_t val = 5; @@ -527,8 +530,8 @@ TEST(stdlib_safeuint, operator_div_remainder_constraint) a.assert_equal(int_val); - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool result = verifier.verify_proof(proof); EXPECT_EQ(result, false); @@ -540,7 +543,7 @@ TEST(stdlib_safeuint, operator_div_remainder_constraint) TEST(stdlib_safeuint, div_remainder_constraint) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); uint256_t val = 5; @@ -555,8 +558,8 @@ TEST(stdlib_safeuint, div_remainder_constraint) a.divide(b, 32, 32, "", supply_bad_witnesses); - waffle::TurboProver prover = composer.create_prover(); - waffle::TurboVerifier verifier = composer.create_verifier(); + Prover prover = composer.create_prover(); + Verifier verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool result = verifier.verify_proof(proof); EXPECT_EQ(result, false); @@ -564,7 +567,7 @@ TEST(stdlib_safeuint, div_remainder_constraint) TEST(stdlib_safeuint, test_byte_array_conversion) { - waffle::TurboComposer composer = waffle::TurboComposer(); + Composer composer = Composer(); field_t elt = witness_t(&composer, 0x7f6f5f4f00010203); suint_t safe(elt, 63); // safe.value is a uint256_t, so we serialize to a 32-byte array diff --git a/cpp/src/aztec/stdlib/primitives/uint/arithmetic.cpp b/cpp/src/aztec/stdlib/primitives/uint/arithmetic.cpp index 32d93b91ee..2fa05e7df6 100644 --- a/cpp/src/aztec/stdlib/primitives/uint/arithmetic.cpp +++ b/cpp/src/aztec/stdlib/primitives/uint/arithmetic.cpp @@ -248,14 +248,14 @@ uint uint::operator*(const uint& other) cons }; ctx->create_big_mul_gate(gate); - constrain_accumulators(context, gate.d, width + 2); + constrain_accumulators(context, gate.d, width + 2, "arithmetic: uint mul overflow too large."); uint result(ctx); // Manually normalize the result. We do this here, and not for operator+, because // it is much easier to overflow the 252-bit modulus using multiplications. It is // left to the circuit writer keep track of overflows when using addition. - result.accumulators = constrain_accumulators(ctx, gate.c); + result.accumulators = constrain_accumulators(ctx, gate.c, width, "arithmetic: uint mul remainder too large."); result.witness_index = result.accumulators[num_accumulators() - 1]; result.witness_status = WitnessStatus::OK; @@ -374,29 +374,26 @@ std::pair, uint> uint ctx->create_add_gate(delta_gate); // validate delta is in the correct range - field_t::from_witness_index(ctx, delta_idx).create_range_constraint(width); + ctx->decompose_into_base4_accumulators(delta_idx, width, "arithmetic: divmod delta range constraint fails."); // normalize witness quotient and remainder // minimal bit range for quotient: from 0 (in case a = b-1) to width (when b = 1). uint quotient(ctx); - quotient.accumulators = ctx->decompose_into_base4_accumulators(quotient_idx, width); + quotient.accumulators = ctx->decompose_into_base4_accumulators( + quotient_idx, width, "arithmetic: divmod quotient range constraint fails."); quotient.witness_index = quotient.accumulators[(width >> 1) - 1]; quotient.witness_status = WitnessStatus::OK; // constrain remainder to lie in [0, 2^width-1] uint remainder(ctx); - remainder.accumulators = ctx->decompose_into_base4_accumulators(remainder_idx, width); + remainder.accumulators = ctx->decompose_into_base4_accumulators( + remainder_idx, width, "arithmetic: divmod remaidner range constraint fails."); remainder.witness_index = remainder.accumulators[(width >> 1) - 1]; remainder.witness_status = WitnessStatus::OK; return std::make_pair(quotient, remainder); } -template class uint; -template class uint; -template class uint; -template class uint; - template class uint; template class uint; template class uint; diff --git a/cpp/src/aztec/stdlib/primitives/uint/comparison.cpp b/cpp/src/aztec/stdlib/primitives/uint/comparison.cpp index f27c17cbcb..19870f88ad 100644 --- a/cpp/src/aztec/stdlib/primitives/uint/comparison.cpp +++ b/cpp/src/aztec/stdlib/primitives/uint/comparison.cpp @@ -55,7 +55,9 @@ bool_t uint::operator>(const uint& other) const // diff.result - result + diff.result - diff = diff.(2.result - 1) - result const auto comparison_check = diff.madd(field_t(result) * 2 - field_t(1), -field_t(result)); - comparison_check.create_range_constraint(width); + + ctx->decompose_into_base4_accumulators( + comparison_check.witness_index, width, "comparison: uint comparison range constraint fails."); return result; } @@ -99,12 +101,6 @@ template bool_t uint(*this).is_zero()).normalize(); } - -template class uint; -template class uint; -template class uint; -template class uint; - template class uint; template class uint; template class uint; diff --git a/cpp/src/aztec/stdlib/primitives/uint/logic.cpp b/cpp/src/aztec/stdlib/primitives/uint/logic.cpp index f0705f9057..704fc49f9f 100644 --- a/cpp/src/aztec/stdlib/primitives/uint/logic.cpp +++ b/cpp/src/aztec/stdlib/primitives/uint/logic.cpp @@ -479,25 +479,6 @@ uint uint::logic_operator(const uint& other, return uint(ctx, out); } - // // PLOOKUP implementation is not being audited. - // if constexpr (Composer::type == waffle::PLOOKUP) { - // std::array>, 3> sequence; - // if (op_type == XOR) { - // sequence = plookup::read_sequence_from_table( - // waffle::PlookupMultiTableId::UINT32_XOR, field_t(*this), field_t(other), true); - // } else { - // sequence = plookup::read_sequence_from_table( - // waffle::PlookupMultiTableId::UINT32_AND, field_t(*this), field_t(other), true); - // } - // uint result(ctx); - // for (size_t i = 0; i < num_accumulators(); ++i) { - // result.accumulators.emplace_back(sequence[2][num_accumulators() - 1 - i].witness_index); - // } - // result.witness_index = result.accumulators[num_accumulators() - 1]; - // result.witness_status = WitnessStatus::OK; - // return result; - // } - const uint32_t lhs_idx = is_constant() ? ctx->add_variable(lhs) : witness_index; const uint32_t rhs_idx = other.is_constant() ? ctx->add_variable(rhs) : other.witness_index; @@ -541,11 +522,6 @@ uint uint::logic_operator(const uint& other, return result; } -template class uint; -template class uint; -template class uint; -template class uint; - template class uint; template class uint; template class uint; diff --git a/cpp/src/aztec/stdlib/primitives/uint/plookup/arithmetic.cpp b/cpp/src/aztec/stdlib/primitives/uint/plookup/arithmetic.cpp new file mode 100644 index 0000000000..a8856d2b15 --- /dev/null +++ b/cpp/src/aztec/stdlib/primitives/uint/plookup/arithmetic.cpp @@ -0,0 +1,260 @@ +#include "../../composers/composers.hpp" +#include "uint.hpp" + +using namespace barretenberg; + +namespace plonk { +namespace stdlib { + +template +uint_plookup uint_plookup::operator+(const uint_plookup& other) const +{ + ASSERT(context == other.context || (context != nullptr && other.context == nullptr) || + (context == nullptr && other.context != nullptr)); + Composer* ctx = (context == nullptr) ? other.context : context; + + if (is_constant() && other.is_constant()) { + return uint_plookup(context, (additive_constant + other.additive_constant) & MASK); + } + + // N.B. We assume that additive_constant is nonzero ONLY if value is constant + const uint256_t lhs = get_value(); + const uint256_t rhs = other.get_value(); + const uint256_t constants = (additive_constant + other.additive_constant) & MASK; + const uint256_t sum = lhs + rhs; + const uint256_t overflow = sum >> width; + const uint256_t remainder = sum & MASK; + + const waffle::add_quad gate{ + is_constant() ? ctx->zero_idx : witness_index, + other.is_constant() ? ctx->zero_idx : other.witness_index, + ctx->add_variable(remainder), + ctx->add_variable(overflow), + fr::one(), + fr::one(), + fr::neg_one(), + -fr(CIRCUIT_UINT_MAX_PLUS_ONE), + constants, + }; + + ctx->create_balanced_add_gate(gate); + + uint_plookup result(ctx); + result.witness_index = gate.c; + result.witness_status = WitnessStatus::WEAK_NORMALIZED; + + return result; +} + +template +uint_plookup uint_plookup::operator-(const uint_plookup& other) const +{ + ASSERT(context == other.context || (context != nullptr && other.context == nullptr) || + (context == nullptr && other.context != nullptr)); + + Composer* ctx = (context == nullptr) ? other.context : context; + + if (is_constant() && other.is_constant()) { + return uint_plookup(context, (additive_constant - other.additive_constant) & MASK); + } + + // N.B. We assume that additive_constant is nonzero ONLY if value is constant + const uint32_t lhs_idx = is_constant() ? ctx->zero_idx : witness_index; + const uint32_t rhs_idx = other.is_constant() ? ctx->zero_idx : other.witness_index; + + const uint256_t lhs = get_value(); + const uint256_t rhs = other.get_value(); + const uint256_t constant_term = (additive_constant - other.additive_constant); + + const uint256_t difference = CIRCUIT_UINT_MAX_PLUS_ONE + lhs - rhs; + const uint256_t overflow = difference >> width; + const uint256_t remainder = difference & MASK; + + const waffle::add_quad gate{ + lhs_idx, + rhs_idx, + ctx->add_variable(remainder), + ctx->add_variable(overflow), + fr::one(), + fr::neg_one(), + fr::neg_one(), + -fr(CIRCUIT_UINT_MAX_PLUS_ONE), + CIRCUIT_UINT_MAX_PLUS_ONE + constant_term, + }; + + ctx->create_balanced_add_gate(gate); + + uint_plookup result(ctx); + result.witness_index = gate.c; + result.witness_status = WitnessStatus::WEAK_NORMALIZED; + + return result; +} + +template +uint_plookup uint_plookup::operator*(const uint_plookup& other) const +{ + Composer* ctx = (context == nullptr) ? other.context : context; + + if (is_constant() && other.is_constant()) { + return uint_plookup(context, (additive_constant * other.additive_constant) & MASK); + } + if (is_constant() && !other.is_constant()) { + return other * (*this); + } + + const uint32_t rhs_idx = other.is_constant() ? ctx->zero_idx : other.witness_index; + + const uint256_t lhs = ctx->variables[witness_index]; + const uint256_t rhs = ctx->variables[rhs_idx]; + + const uint256_t product = (lhs * rhs) + (lhs * other.additive_constant) + (rhs * additive_constant); + const uint256_t overflow = product >> width; + const uint256_t remainder = product & MASK; + + const waffle::mul_quad gate{ + witness_index, + rhs_idx, + ctx->add_variable(remainder), + ctx->add_variable(overflow), + fr::one(), + other.additive_constant, + additive_constant, + fr::neg_one(), + -fr(CIRCUIT_UINT_MAX_PLUS_ONE), + 0, + }; + + ctx->create_big_mul_gate(gate); + + // discard the high bits + ctx->decompose_into_default_range(gate.d, width); + + uint_plookup result(ctx); + result.accumulators = constrain_accumulators(ctx, gate.c); + result.witness_index = gate.c; + result.witness_status = WitnessStatus::OK; + + return result; +} + +template +uint_plookup uint_plookup::operator/(const uint_plookup& other) const +{ + return divmod(other).first; +} + +template +uint_plookup uint_plookup::operator%(const uint_plookup& other) const +{ + return divmod(other).second; +} + +template +std::pair, uint_plookup> uint_plookup::divmod( + const uint_plookup& other) const +{ + /** + * divmod: returns (a / b) and (a % b) + * + * We want to validate the following: + * + * a = b.q + r + * + * Where: + * + * a = dividend witness + * b = divisor witness + * q = quotient + * r = remainder + * (b - r) is in the range [0, 2**{width}] + * + * The final check validates that r is a geuine remainder term, that does not contain multiples of b + * + * We normalize a and b, as we need to be certain these values are within the range [0, 2**{width}] + **/ + + Composer* ctx = (context == nullptr) ? other.context : context; + + // We want to force the divisor to be non-zero, as this is an error state + if (other.is_constant() && other.get_value() == 0) { + // TODO: should have an actual error handler! + const uint32_t one = ctx->add_variable(fr::one()); + ctx->assert_equal_constant(one, fr::zero()); + ctx->failure("plookup_arithmetic: divide by zero!"); + } else if (!other.is_constant()) { + const bool_t is_divisor_zero = field_t(other).is_zero(); + ctx->assert_equal_constant(is_divisor_zero.witness_index, fr::zero(), "plookup_arithmetic: divide by zero!"); + } + + if (is_constant() && other.is_constant()) { + const uint_plookup remainder(ctx, additive_constant % other.additive_constant); + const uint_plookup quotient(ctx, additive_constant / other.additive_constant); + return std::make_pair(quotient, remainder); + } else if (witness_index == other.witness_index) { + const uint_plookup remainder(context, 0); + const uint_plookup quotient(context, 1); + return std::make_pair(quotient, remainder); + } + + const uint32_t dividend_idx = is_constant() ? ctx->zero_idx : witness_index; + const uint32_t divisor_idx = other.is_constant() ? ctx->zero_idx : other.witness_index; + + const uint256_t dividend = get_value(); + const uint256_t divisor = other.get_value(); + + const uint256_t q = dividend / divisor; + const uint256_t r = dividend % divisor; + + const uint32_t quotient_idx = ctx->add_variable(q); + const uint32_t remainder_idx = ctx->add_variable(r); + + const waffle::mul_quad division_gate{ + quotient_idx, // q + divisor_idx, // b + dividend_idx, // a + remainder_idx, // r + fr::one(), // q_m.w_1.w_2 = q.b + other.additive_constant, // q_l.w_1 = q.b if b const + fr::zero(), // q_2.w_2 = 0 + fr::neg_one(), // q_3.w_3 = -a + fr::one(), // q_4.w_4 = r + -fr(additive_constant) // q_c = -a if a const + }; + ctx->create_big_mul_gate(division_gate); + + // (b + c_b - r) = d + const uint256_t delta = divisor - r; + + const uint32_t delta_idx = ctx->add_variable(delta); + const waffle::add_triple delta_gate{ + divisor_idx, // b + remainder_idx, // r + delta_idx, // d + fr::one(), // q_l = 1 + fr::neg_one(), // q_r = -1 + fr::neg_one(), // q_o = -1 + other.additive_constant, // q_c = d if const + }; + ctx->create_add_gate(delta_gate); + + // validate delta is in the correct range + ctx->decompose_into_default_range(delta_idx, width); + uint_plookup quotient(ctx); + quotient.witness_index = quotient_idx; + quotient.accumulators = constrain_accumulators(ctx, quotient.witness_index); + quotient.witness_status = WitnessStatus::OK; + + uint_plookup remainder(ctx); + remainder.witness_index = remainder_idx; + remainder.accumulators = constrain_accumulators(ctx, remainder.witness_index); + remainder.witness_status = WitnessStatus::OK; + + return std::make_pair(quotient, remainder); +} +template class uint_plookup; +template class uint_plookup; +template class uint_plookup; +template class uint_plookup; +} // namespace stdlib +} // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/uint/plookup/comparison.cpp b/cpp/src/aztec/stdlib/primitives/uint/plookup/comparison.cpp new file mode 100644 index 0000000000..30db5cbeba --- /dev/null +++ b/cpp/src/aztec/stdlib/primitives/uint/plookup/comparison.cpp @@ -0,0 +1,81 @@ +#include "../../composers/composers.hpp" +#include "uint.hpp" + +using namespace barretenberg; + +namespace plonk { +namespace stdlib { + +template +bool_t uint_plookup::operator>(const uint_plookup& other) const +{ + Composer* ctx = (context == nullptr) ? other.context : context; + + field_t a(*this); + field_t b(other); + bool result_witness = uint256_t(a.get_value()) > uint256_t(b.get_value()); + + if (is_constant() && other.is_constant()) { + return bool_t(ctx, result_witness); + } + + const bool_t result = witness_t(ctx, result_witness); + + /** + * if (a > b), then (a - b - 1) will be in the range [0, 2**{width}] + * if !(a > b), then (b - a) will be in the range [0, 2**{width}] + * i.e. (a - b - 1)result + (b - a)(1 - result) should be positive + **/ + const auto diff = a - b; + const auto comparison_check = + diff.madd(field_t(result) * 2 - field_t(1), -field_t(result)); + + comparison_check.create_range_constraint(width); + + return result; +} + +template +bool_t uint_plookup::operator<(const uint_plookup& other) const +{ + return other > *this; +} + +template +bool_t uint_plookup::operator>=(const uint_plookup& other) const +{ + return (!(other > *this)).normalize(); +} + +template +bool_t uint_plookup::operator<=(const uint_plookup& other) const +{ + return (!(*this > other)).normalize(); +} + +template +bool_t uint_plookup::operator==(const uint_plookup& other) const +{ + // casting to a field type will ensure that lhs / rhs are both normalized + const field_t lhs = static_cast>(*this); + const field_t rhs = static_cast>(other); + + return (lhs == rhs).normalize(); +} + +template +bool_t uint_plookup::operator!=(const uint_plookup& other) const +{ + return (!(*this == other)).normalize(); +} + +template bool_t uint_plookup::operator!() const +{ + return (field_t(*this).is_zero()).normalize(); +} +template class uint_plookup; +template class uint_plookup; +template class uint_plookup; +template class uint_plookup; +} // namespace stdlib +} // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/uint/plookup/logic.cpp b/cpp/src/aztec/stdlib/primitives/uint/plookup/logic.cpp new file mode 100644 index 0000000000..7349c19e22 --- /dev/null +++ b/cpp/src/aztec/stdlib/primitives/uint/plookup/logic.cpp @@ -0,0 +1,334 @@ +#include "../../composers/composers.hpp" +#include "uint.hpp" + +using namespace barretenberg; + +namespace plonk { +namespace stdlib { + +template +uint_plookup uint_plookup::operator&(const uint_plookup& other) const +{ + return logic_operator(other, LogicOp::AND); +} + +template +uint_plookup uint_plookup::operator^(const uint_plookup& other) const +{ + return logic_operator(other, LogicOp::XOR); +} + +template +uint_plookup uint_plookup::operator|(const uint_plookup& other) const +{ + return (*this + other) - (*this & other); +} + +template +uint_plookup uint_plookup::operator~() const +{ + return uint_plookup(context, MASK) - *this; +} + +template +uint_plookup uint_plookup::operator>>(const size_t shift) const +{ + if (shift >= width) { + return uint_plookup(context, 0); + } + if (is_constant()) { + return uint_plookup(context, (additive_constant >> shift) & MASK); + } + + if (witness_status != WitnessStatus::OK) { + normalize(); + } + + if (shift == 0) { + return *this; + } + + uint64_t bits_per_hi_limb; + // last limb will not likely bit `bits_per_limb`. Need to be careful with our range check + if (shift >= ((width / bits_per_limb) * bits_per_limb)) { + bits_per_hi_limb = width % bits_per_limb; + } else { + bits_per_hi_limb = bits_per_limb; + } + const uint64_t slice_bit_position = shift % bits_per_limb; + const size_t accumulator_index = shift / bits_per_limb; + const uint32_t slice_index = accumulators[accumulator_index]; + const uint64_t slice_value = uint256_t(context->get_variable(slice_index)).data[0]; + + const uint64_t slice_lo = slice_value % (1ULL << slice_bit_position); + const uint64_t slice_hi = slice_value >> slice_bit_position; + const uint32_t slice_lo_idx = slice_bit_position ? context->add_variable(slice_lo) : context->zero_idx; + const uint32_t slice_hi_idx = + (slice_bit_position != bits_per_limb) ? context->add_variable(slice_hi) : context->zero_idx; + + context->create_big_add_gate( + { slice_index, slice_lo_idx, context->zero_idx, slice_hi_idx, -1, 1, 0, (1 << slice_bit_position), 0 }); + + if (slice_bit_position != 0) { + context->create_new_range_constraint(slice_lo_idx, (1ULL << slice_bit_position) - 1); + } + context->create_new_range_constraint(slice_hi_idx, (1ULL << (bits_per_hi_limb - slice_bit_position)) - 1); + std::vector> sublimbs; + sublimbs.emplace_back(field_t::from_witness_index(context, slice_hi_idx)); + + const size_t start = accumulator_index + 1; + field_t coefficient(context, uint64_t(1ULL << (start * bits_per_limb - shift))); + field_t shifter(context, uint64_t(1ULL << bits_per_limb)); + for (size_t i = accumulator_index + 1; i < num_accumulators(); ++i) { + sublimbs.emplace_back(field_t::from_witness_index(context, accumulators[i]) * + field_t(coefficient)); + coefficient *= shifter; + } + + uint32_t result_index = field_t::accumulate(sublimbs).normalize().get_witness_index(); + uint_plookup result(context); + result.witness_index = result_index; + result.witness_status = WitnessStatus::WEAK_NORMALIZED; + return result; +} + +template +uint_plookup uint_plookup::operator<<(const size_t shift) const +{ + if (shift >= width) { + return uint_plookup(context, 0); + } + if (is_constant()) { + return uint_plookup(context, (additive_constant << shift) & MASK); + } + + if (witness_status != WitnessStatus::OK) { + normalize(); + } + + if (shift == 0) { + return *this; + } + + uint64_t slice_bit_position; + size_t accumulator_index; + size_t bits_per_hi_limb; + // most significant limb is only 2 bits long (for u32), need to be careful about which slice we index, + // and how large the range check is on our hi limb + if (shift < (width - ((width / bits_per_limb) * bits_per_limb))) { + bits_per_hi_limb = width % bits_per_limb; + slice_bit_position = bits_per_hi_limb - (shift % bits_per_hi_limb); + accumulator_index = num_accumulators() - 1; + } else { + const size_t offset = width % bits_per_limb; + slice_bit_position = bits_per_limb - ((shift - offset) % bits_per_limb); + accumulator_index = num_accumulators() - 2 - ((shift - offset) / bits_per_limb); + bits_per_hi_limb = bits_per_limb; + } + + const uint32_t slice_index = accumulators[accumulator_index]; + const uint64_t slice_value = uint256_t(context->get_variable(slice_index)).data[0]; + + const uint64_t slice_lo = slice_value % (1ULL << slice_bit_position); + const uint64_t slice_hi = slice_value >> slice_bit_position; + const uint32_t slice_lo_idx = slice_bit_position ? context->add_variable(slice_lo) : context->zero_idx; + const uint32_t slice_hi_idx = + (slice_bit_position != bits_per_hi_limb) ? context->add_variable(slice_hi) : context->zero_idx; + + context->create_big_add_gate( + { slice_index, slice_lo_idx, context->zero_idx, slice_hi_idx, -1, 1, 0, (1 << slice_bit_position), 0 }); + + context->create_new_range_constraint(slice_lo_idx, (1ULL << slice_bit_position) - 1); + + if (slice_bit_position != bits_per_limb) { + context->create_new_range_constraint(slice_hi_idx, (1ULL << (bits_per_hi_limb - slice_bit_position)) - 1); + } + + std::vector> sublimbs; + sublimbs.emplace_back(field_t::from_witness_index(context, slice_lo_idx) * + field_t(context, 1ULL << ((accumulator_index)*bits_per_limb + shift))); + + field_t coefficient(context, uint64_t(1ULL << shift)); + field_t shifter(context, uint64_t(1ULL << bits_per_limb)); + for (size_t i = 0; i < accumulator_index; ++i) { + sublimbs.emplace_back(field_t::from_witness_index(context, accumulators[i]) * + field_t(coefficient)); + coefficient *= shifter; + } + + uint32_t result_index = field_t::accumulate(sublimbs).normalize().get_witness_index(); + uint_plookup result(context); + result.witness_index = result_index; + result.witness_status = WitnessStatus::WEAK_NORMALIZED; + return result; +} + +template +uint_plookup uint_plookup::ror(const size_t target_rotation) const +{ + const size_t rotation = target_rotation & (width - 1); + + const auto rotate = [](const uint256_t input, const uint64_t rot) { + uint256_t r0 = (input >> rot); + uint256_t r1 = (input << (width - rot)) & MASK; + return (rot > 0) ? (r0 + r1) : input; + }; + + if (is_constant()) { + return uint_plookup(context, rotate(additive_constant, rotation)); + } + + if (witness_status != WitnessStatus::OK) { + normalize(); + } + + if (rotation == 0) { + return *this; + } + + const size_t shift = rotation; + uint64_t bits_per_hi_limb; + // last limb will not likely bit `bits_per_limb`. Need to be careful with our range check + if (shift >= ((width / bits_per_limb) * bits_per_limb)) { + bits_per_hi_limb = width % bits_per_limb; + } else { + bits_per_hi_limb = bits_per_limb; + } + const uint64_t slice_bit_position = shift % bits_per_limb; + const size_t accumulator_index = shift / bits_per_limb; + const uint32_t slice_index = accumulators[accumulator_index]; + const uint64_t slice_value = uint256_t(context->get_variable(slice_index)).data[0]; + + const uint64_t slice_lo = slice_value % (1ULL << slice_bit_position); + const uint64_t slice_hi = slice_value >> slice_bit_position; + const uint32_t slice_lo_idx = slice_bit_position ? context->add_variable(slice_lo) : context->zero_idx; + const uint32_t slice_hi_idx = + (slice_bit_position != bits_per_limb) ? context->add_variable(slice_hi) : context->zero_idx; + + context->create_big_add_gate( + { slice_index, slice_lo_idx, context->zero_idx, slice_hi_idx, -1, 1, 0, (1 << slice_bit_position), 0 }); + + if (slice_bit_position != 0) { + context->create_new_range_constraint(slice_lo_idx, (1ULL << slice_bit_position) - 1); + } + context->create_new_range_constraint(slice_hi_idx, (1ULL << (bits_per_hi_limb - slice_bit_position)) - 1); + std::vector> sublimbs; + sublimbs.emplace_back(field_t::from_witness_index(context, slice_hi_idx)); + + const size_t start = accumulator_index + 1; + field_t coefficient(context, uint64_t(1ULL << (start * bits_per_limb - shift))); + field_t shifter(context, uint64_t(1ULL << bits_per_limb)); + for (size_t i = accumulator_index + 1; i < num_accumulators(); ++i) { + sublimbs.emplace_back(field_t::from_witness_index(context, accumulators[i]) * + field_t(coefficient)); + coefficient *= shifter; + } + + coefficient = field_t(context, uint64_t(1ULL << (width - shift))); + for (size_t i = 0; i < accumulator_index; ++i) { + sublimbs.emplace_back(field_t::from_witness_index(context, accumulators[i]) * + field_t(coefficient)); + coefficient *= shifter; + } + sublimbs.emplace_back(field_t::from_witness_index(context, slice_lo_idx) * + field_t(coefficient)); + + uint32_t result_index = field_t::accumulate(sublimbs).normalize().get_witness_index(); + uint_plookup result(context); + result.witness_index = result_index; + result.witness_status = WitnessStatus::WEAK_NORMALIZED; + return result; +} + +template +uint_plookup uint_plookup::rol(const size_t target_rotation) const +{ + return ror(width - (target_rotation & (width - 1))); +} + +template +uint_plookup uint_plookup::logic_operator(const uint_plookup& other, + const LogicOp op_type) const +{ + Composer* ctx = (context == nullptr) ? other.context : context; + + // we need to ensure that we can decompose our integers into (width / 2) quads + // we don't need to completely normalize, however, as our quaternary decomposition will do that by default + const uint256_t lhs = get_value(); + const uint256_t rhs = other.get_value(); + uint256_t out = 0; + + switch (op_type) { + case AND: { + out = lhs & rhs; + break; + } + case XOR: { + out = lhs ^ rhs; + break; + } + default: { + } + } + + if (is_constant() && other.is_constant()) { + return uint_plookup(ctx, out); + } + + ReadData> lookup; + if (op_type == XOR) { + lookup = plookup_read::get_lookup_accumulators( + MultiTableId::UINT32_XOR, field_t(*this), field_t(other), true); + } else { + lookup = plookup_read::get_lookup_accumulators( + MultiTableId::UINT32_AND, field_t(*this), field_t(other), true); + } + uint_plookup result(ctx); + // result.accumulators.resize(num_accumulators()); + field_t scaling_factor(context, barretenberg::fr(1ULL << bits_per_limb)); + + // N.B. THIS LOOP ONLY WORKS IF THE LOGIC TABLE SLICE SIZE IS HALF THAT OF `bits_per_limb` + for (size_t i = 0; i < num_accumulators(); ++i) { + + /** + * we can extract a slice value, by taking the relative difference between accumulating sums. + * each table row sums a 6-bit slice into an accumulator, we need to take the difference between slices in jumps + *of 2, to get a 12-bit slice + * + * If our output limbs are b0, b1, b2, b3, b4, b5, our lookup[ColumnIdx::C3] values represent: + * (where X = 2^6) + * | c0 | b0 + X.b1 + X.X.b2 + X.X.X.b3 + X.X.X.X.b4 + X.X.X.X.X.b5 + * | c1 | b1 + X.b2 + X.X.b3 + X.X.X.b4 + X.X.X.X.b5 + * | c2 | b2 + X. b3 + X.X.b4 + X.X.X.b5 + * | c3 | b3 + X.b4 + X.X.b5 + * | c4 | b4 + X.b5 + * | c5 | b5 + * + * + * We want in our accumulators: + * + * | acc[0] | c0 - X.X.c2 | + * | acc[1] | c2 - X.X.c4 | + * | acc[2] | c4 | + **/ + + if (i != (num_accumulators() - 1)) { + result.accumulators.emplace_back( + (lookup[ColumnIdx::C3][2 * i] - (lookup[ColumnIdx::C3][2 * (i + 1)] * scaling_factor)).witness_index); + } else { + result.accumulators.emplace_back(lookup[ColumnIdx::C3][2 * (num_accumulators() - 1)].witness_index); + } + } + + result.witness_index = lookup[ColumnIdx::C3][0].get_witness_index(); + result.witness_status = WitnessStatus::OK; + return result; +} + +template class uint_plookup; +template class uint_plookup; +template class uint_plookup; +template class uint_plookup; + +} // namespace stdlib +} // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/uint/plookup/uint.cpp b/cpp/src/aztec/stdlib/primitives/uint/plookup/uint.cpp new file mode 100644 index 0000000000..6dba609b4e --- /dev/null +++ b/cpp/src/aztec/stdlib/primitives/uint/plookup/uint.cpp @@ -0,0 +1,249 @@ +#include "uint.hpp" +#include "../../composers/composers.hpp" + +using namespace barretenberg; + +namespace plonk { +namespace stdlib { + +template +std::vector uint_plookup::constrain_accumulators(Composer* context, + const uint32_t witness_index) const +{ + const auto res = context->decompose_into_default_range(witness_index, width, bits_per_limb); + return res; +} + +template +uint_plookup::uint_plookup(const witness_t& witness) + : context(witness.context) + , additive_constant(0) + , witness_status(WitnessStatus::OK) + , accumulators(constrain_accumulators(context, witness.witness_index)) + , witness_index(witness.witness_index) +{} + +template +uint_plookup::uint_plookup(const field_t& value) + : context(value.context) + , additive_constant(0) + , witness_status(WitnessStatus::OK) + , accumulators() + , witness_index(IS_CONSTANT) +{ + if (value.witness_index == IS_CONSTANT) { + additive_constant = value.additive_constant; + } else { + field_t norm = value.normalize(); + accumulators = constrain_accumulators(context, norm.get_witness_index()); + witness_index = norm.get_witness_index(); + } +} + +template +uint_plookup::uint_plookup(Composer* composer, const uint256_t& value) + : context(composer) + , additive_constant(value) + , witness_status(WitnessStatus::OK) + , accumulators() + , witness_index(IS_CONSTANT) +{} + +template +uint_plookup::uint_plookup(const uint256_t& value) + : context(nullptr) + , additive_constant(value) + , witness_status(WitnessStatus::OK) + , accumulators() + , witness_index(IS_CONSTANT) +{} + +template +uint_plookup::uint_plookup(const byte_array& other) + : context(other.get_context()) + , additive_constant(0) + , witness_status(WitnessStatus::WEAK_NORMALIZED) + , accumulators() + , witness_index(IS_CONSTANT) +{ + field_t accumulator(context, fr::zero()); + field_t scaling_factor(context, fr::one()); + const auto bytes = other.bytes(); + + // TODO JUMP IN STEPS OF TWO + for (size_t i = 0; i < bytes.size(); ++i) { + accumulator = accumulator + scaling_factor * bytes[bytes.size() - 1 - i]; + scaling_factor = scaling_factor * fr(256); + } + accumulator = accumulator.normalize(); + if (accumulator.witness_index == IS_CONSTANT) { + additive_constant = uint256_t(accumulator.additive_constant); + } else { + witness_index = accumulator.witness_index; + } +} + +template +uint_plookup::uint_plookup(Composer* parent_context, const std::array, width>& wires) + : uint_plookup(parent_context, std::vector>(wires.begin(), wires.end())) +{} + +template +uint_plookup::uint_plookup(Composer* parent_context, const std::vector>& wires) + : context(parent_context) + , additive_constant(0) + , witness_status(WitnessStatus::WEAK_NORMALIZED) + , accumulators() + , witness_index(IS_CONSTANT) +{ + field_t accumulator(context, fr::zero()); + field_t scaling_factor(context, fr::one()); + + // TODO JUMP IN STEPS OF TWO + for (size_t i = 0; i < wires.size(); ++i) { + accumulator = accumulator + scaling_factor * field_t(wires[i]); + scaling_factor = scaling_factor + scaling_factor; + } + accumulator = accumulator.normalize(); + if (accumulator.witness_index == IS_CONSTANT) { + additive_constant = uint256_t(accumulator.additive_constant); + } else { + witness_index = accumulator.witness_index; + } +} + +template +uint_plookup::uint_plookup(const uint_plookup& other) + : context(other.context) + , additive_constant(other.additive_constant) + , witness_status(other.witness_status) + , accumulators(other.accumulators) + , witness_index(other.witness_index) +{} + +template +uint_plookup::uint_plookup(uint_plookup&& other) + : context(other.context) + , additive_constant(other.additive_constant) + , witness_status(other.witness_status) + , accumulators(other.accumulators) + , witness_index(other.witness_index) +{} + +template +uint_plookup& uint_plookup::operator=(const uint_plookup& other) +{ + context = other.context; + additive_constant = other.additive_constant; + witness_status = other.witness_status; + accumulators = other.accumulators; + witness_index = other.witness_index; + return *this; +} + +template +uint_plookup& uint_plookup::operator=(uint_plookup&& other) +{ + context = other.context; + additive_constant = other.additive_constant; + witness_status = other.witness_status; + accumulators = other.accumulators; + witness_index = other.witness_index; + return *this; +} + +template uint_plookup::operator field_t() const +{ + normalize(); + field_t target(context); + target.witness_index = witness_index; + target.additive_constant = is_constant() ? fr(additive_constant) : fr::zero(); + return target; +} + +template uint_plookup::operator byte_array() const +{ + return byte_array(static_cast>(*this), width / 8); +} + +template +uint_plookup uint_plookup::normalize() const +{ + if (!context || is_constant()) { + return *this; + } + + if (witness_status == WitnessStatus::WEAK_NORMALIZED) { + accumulators = constrain_accumulators(context, witness_index); + witness_status = WitnessStatus::OK; + } + return *this; +} + +template uint256_t uint_plookup::get_value() const +{ + if (!context || is_constant()) { + return additive_constant; + } + return (uint256_t(context->get_variable(witness_index))) & MASK; +} + +template uint256_t uint_plookup::get_unbounded_value() const +{ + if (!context || is_constant()) { + return additive_constant; + } + return (uint256_t(context->get_variable(witness_index))); +} + +template +bool_t uint_plookup::at(const size_t bit_index) const +{ + if (is_constant()) { + return bool_t(context, get_value().get_bit(bit_index)); + } + if (witness_status != WitnessStatus::OK) { + normalize(); + } + + const uint64_t slice_bit_position = bit_index % bits_per_limb; + + const uint32_t slice_index = accumulators[bit_index / bits_per_limb]; + const uint64_t slice_value = uint256_t(context->get_variable(slice_index)).data[0]; + + const uint64_t slice_lo = slice_value % (1ULL << slice_bit_position); + const uint64_t bit_value = (slice_value >> slice_bit_position) & 1ULL; + const uint64_t slice_hi = slice_value >> (slice_bit_position + 1); + + const uint32_t slice_lo_idx = slice_bit_position ? context->add_variable(slice_lo) : context->zero_idx; + const uint32_t bit_idx = context->add_variable(bit_value); + const uint32_t slice_hi_idx = + (slice_bit_position + 1 != bits_per_limb) ? context->add_variable(slice_hi) : context->zero_idx; + + context->create_big_add_gate({ slice_index, + slice_lo_idx, + bit_idx, + slice_hi_idx, + -1, + 1, + (1 << slice_bit_position), + (1 << (slice_bit_position + 1)), + 0 }); + + if (slice_bit_position != 0) { + context->create_new_range_constraint(slice_lo_idx, (1ULL << slice_bit_position) - 1); + } + if (slice_bit_position + 1 != bits_per_limb) { + context->create_new_range_constraint(slice_hi_idx, (1ULL << (bits_per_limb - (slice_bit_position + 1))) - 1); + } + bool_t result = witness_t(context, bit_value); + return result; +} + +INSTANTIATE_STDLIB_ULTRA_TYPE_VA(uint_plookup, uint8_t); +INSTANTIATE_STDLIB_ULTRA_TYPE_VA(uint_plookup, uint16_t); +INSTANTIATE_STDLIB_ULTRA_TYPE_VA(uint_plookup, uint32_t); +INSTANTIATE_STDLIB_ULTRA_TYPE_VA(uint_plookup, uint64_t); + +} // namespace stdlib +} // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/uint/plookup/uint.hpp b/cpp/src/aztec/stdlib/primitives/uint/plookup/uint.hpp new file mode 100644 index 0000000000..2ba2699140 --- /dev/null +++ b/cpp/src/aztec/stdlib/primitives/uint/plookup/uint.hpp @@ -0,0 +1,181 @@ +#pragma once +#include "../../bool/bool.hpp" +#include "../../byte_array/byte_array.hpp" +#include "../../composers/composers_fwd.hpp" +#include "../../field/field.hpp" +#include "../../plookup/plookup.hpp" + +namespace plonk { +namespace stdlib { + +template class uint_plookup { + public: + static constexpr size_t width = sizeof(Native) * 8; + + uint_plookup(const witness_t& other); + uint_plookup(const field_t& other); + uint_plookup(const uint256_t& value = 0); + uint_plookup(Composer* composer, const uint256_t& value = 0); + uint_plookup(const byte_array& other); + uint_plookup(Composer* parent_context, const std::vector>& wires); + uint_plookup(Composer* parent_context, const std::array, width>& wires); + + uint_plookup(const Native v) + : uint_plookup(static_cast(v)) + {} + + std::vector constrain_accumulators(Composer* ctx, const uint32_t witness_index) const; + + static constexpr size_t bits_per_limb = 12; + static constexpr size_t num_accumulators() { return (width + bits_per_limb - 1) / bits_per_limb; } + + uint_plookup(const uint_plookup& other); + uint_plookup(uint_plookup&& other); + + uint_plookup& operator=(const uint_plookup& other); + uint_plookup& operator=(uint_plookup&& other); + + explicit operator byte_array() const; + explicit operator field_t() const; + + uint_plookup operator+(const uint_plookup& other) const; + uint_plookup operator-(const uint_plookup& other) const; + uint_plookup operator*(const uint_plookup& other) const; + uint_plookup operator/(const uint_plookup& other) const; + uint_plookup operator%(const uint_plookup& other) const; + + uint_plookup operator&(const uint_plookup& other) const; + uint_plookup operator^(const uint_plookup& other) const; + uint_plookup operator|(const uint_plookup& other) const; + uint_plookup operator~() const; + + uint_plookup operator>>(const size_t shift) const; + uint_plookup operator<<(const size_t shift) const; + + uint_plookup ror(const size_t target_rotation) const; + uint_plookup rol(const size_t target_rotation) const; + uint_plookup ror(const uint256_t target_rotation) const + { + return ror(static_cast(target_rotation.data[0])); + } + uint_plookup rol(const uint256_t target_rotation) const + { + return rol(static_cast(target_rotation.data[0])); + } + + bool_t operator>(const uint_plookup& other) const; + bool_t operator<(const uint_plookup& other) const; + bool_t operator>=(const uint_plookup& other) const; + bool_t operator<=(const uint_plookup& other) const; + bool_t operator==(const uint_plookup& other) const; + bool_t operator!=(const uint_plookup& other) const; + bool_t operator!() const; + + uint_plookup operator+=(const uint_plookup& other) + { + *this = operator+(other); + return *this; + } + uint_plookup operator-=(const uint_plookup& other) + { + *this = operator-(other); + return *this; + } + uint_plookup operator*=(const uint_plookup& other) + { + *this = operator*(other); + return *this; + } + uint_plookup operator/=(const uint_plookup& other) + { + *this = operator/(other); + return *this; + } + uint_plookup operator%=(const uint_plookup& other) + { + *this = operator%(other); + return *this; + } + + uint_plookup operator&=(const uint_plookup& other) + { + *this = operator&(other); + return *this; + } + uint_plookup operator^=(const uint_plookup& other) + { + *this = operator^(other); + return *this; + } + uint_plookup operator|=(const uint_plookup& other) + { + *this = operator|(other); + return *this; + } + + uint_plookup operator>>=(const size_t shift) + { + *this = operator>>(shift); + return *this; + } + uint_plookup operator<<=(const size_t shift) + { + *this = operator<<(shift); + return *this; + } + + uint_plookup normalize() const; + + uint256_t get_value() const; + + bool is_constant() const { return witness_index == IS_CONSTANT; } + Composer* get_context() const { return context; } + + bool_t at(const size_t bit_index) const; + + size_t get_width() const { return width; } + + uint32_t get_witness_index() const { return witness_index; } + + uint256_t get_additive_constant() const { return additive_constant; } + + std::vector get_accumulators() const { return accumulators; } + uint256_t get_unbounded_value() const; + + protected: + Composer* context; + + enum WitnessStatus { OK, NOT_NORMALIZED, WEAK_NORMALIZED }; + + mutable uint256_t additive_constant; + mutable WitnessStatus witness_status; + + // N.B. Not an accumulator! Contains 6-bit slices of input + mutable std::vector accumulators; + mutable uint32_t witness_index; + + static constexpr uint256_t CIRCUIT_UINT_MAX_PLUS_ONE = (uint256_t(1) << width); + static constexpr uint256_t MASK = CIRCUIT_UINT_MAX_PLUS_ONE - 1; + + private: + enum LogicOp { + AND, + XOR, + }; + + std::pair divmod(const uint_plookup& other) const; + uint_plookup logic_operator(const uint_plookup& other, const LogicOp op_type) const; +}; + +template inline std::ostream& operator<<(std::ostream& os, uint_plookup const& v) +{ + return os << v.get_value(); +} + +EXTERN_STDLIB_ULTRA_TYPE_VA(uint_plookup, uint8_t); +EXTERN_STDLIB_ULTRA_TYPE_VA(uint_plookup, uint16_t); +EXTERN_STDLIB_ULTRA_TYPE_VA(uint_plookup, uint32_t); +EXTERN_STDLIB_ULTRA_TYPE_VA(uint_plookup, uint64_t); + +} // namespace stdlib +} // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/uint/uint.cpp b/cpp/src/aztec/stdlib/primitives/uint/uint.cpp index fa3b205349..f1ee0b4a6f 100644 --- a/cpp/src/aztec/stdlib/primitives/uint/uint.cpp +++ b/cpp/src/aztec/stdlib/primitives/uint/uint.cpp @@ -12,15 +12,16 @@ namespace stdlib { template std::vector uint::constrain_accumulators(Composer* context, const uint32_t witness_index, - const size_t num_bits) const + const size_t num_bits, + std::string const& msg) const { if constexpr (Composer::type == waffle::PLOOKUP) { // TODO: manage higher bit ranges - const auto sequence = plonk::stdlib::plookup::read_sequence_from_table( - waffle::PlookupMultiTableId::UINT32_XOR, - field_t::from_witness_index(context, witness_index), - field_t::from_witness_index(context, context->zero_idx), - true); + const auto sequence = + plookup_read::get_lookup_accumulators(plookup::MultiTableId::UINT32_XOR, + field_t::from_witness_index(context, witness_index), + field_t::from_witness_index(context, context->zero_idx), + true); std::vector out(num_accumulators()); for (size_t i = 0; i < num_accumulators(); ++i) { @@ -28,7 +29,7 @@ std::vector uint::constrain_accumulators(Composer* c } return out; } - return context->decompose_into_base4_accumulators(witness_index, num_bits); + return context->decompose_into_base4_accumulators(witness_index, num_bits, msg); } template @@ -36,7 +37,8 @@ uint::uint(const witness_t& witness) : context(witness.context) , additive_constant(0) , witness_status(WitnessStatus::OK) - , accumulators(constrain_accumulators(context, witness.witness_index)) + , accumulators(constrain_accumulators( + context, witness.witness_index, width, "uint: range constraint fails in constructor of uint from witness")) , witness_index(accumulators[num_accumulators() - 1]) {} @@ -52,7 +54,8 @@ uint::uint(const field_t& value) additive_constant = value.additive_constant; } else { field_t norm = value.normalize(); - accumulators = constrain_accumulators(context, norm.witness_index); + accumulators = constrain_accumulators( + context, norm.witness_index, width, "uint: range constraint fails in constructor of uint from field_t"); witness_index = accumulators[num_accumulators() - 1]; } } @@ -240,7 +243,8 @@ template uint uint uint uint bool_t uintget_variable(right_idx)) - uint256_t(context->get_variable(left_idx)) * uint256_t(4); + if constexpr (Composer::type == waffle::ComposerType::PLOOKUP) { + uint256_t lo_bit = quad & 1; + uint256_t hi_bit = (quad & 2) >> 1; + // difference in quads = 0, 1, 2, 3 = delta + // (delta - lo_bit) / 2 \in [0, 1] + // lo_bit \in [0, 1] + waffle::add_quad gate{ + context->add_variable(lo_bit), context->add_variable(hi_bit), right_idx, left_idx, 1, 2, -1, 4, 0, + }; + context->create_new_range_constraint(gate.a, 1); + context->create_new_range_constraint(gate.b, 1); + bool_t result; + + if ((bit_index & 1UL) == 0UL) { + result.witness_index = gate.a; + result.witness_bool = (lo_bit == 1) ? true : false; + } else { + result.witness_index = gate.b; + result.witness_bool = (hi_bit == 1) ? true : false; + } + return result; + } + // if 'index' is odd, we want a low bit /** * Write Δ = accumulators[pivot] - 4 . accumulators[pivot - 1]. We would like to construct * the bit representation of Δ by imposing the constraint that Δ = lo_bit + 2 . hi_bit @@ -370,10 +398,10 @@ template bool_t uint +#include +#include + namespace plonk { namespace stdlib { @@ -41,7 +47,8 @@ template class uint { std::vector constrain_accumulators(Composer* ctx, const uint32_t witness_index, - const size_t num_bits = width) const; + const size_t num_bits, + std::string const& msg) const; static constexpr size_t num_accumulators() { @@ -185,15 +192,27 @@ template inline std::ostream& operator<<(std::ostream& return os << v.get_value(); } -template using uint8 = uint; -template using uint16 = uint; -template using uint32 = uint; -template using uint64 = uint; - -EXTERN_STDLIB_TYPE_VA(uint, uint8_t); -EXTERN_STDLIB_TYPE_VA(uint, uint16_t); -EXTERN_STDLIB_TYPE_VA(uint, uint32_t); -EXTERN_STDLIB_TYPE_VA(uint, uint64_t); +template +using uint8 = typename std::conditional, + uint>::type; +template +using uint16 = typename std::conditional, + uint>::type; +template +using uint32 = typename std::conditional, + uint>::type; +template +using uint64 = typename std::conditional, + uint>::type; + +EXTERN_STDLIB_BASIC_TYPE_VA(uint, uint8_t); +EXTERN_STDLIB_BASIC_TYPE_VA(uint, uint16_t); +EXTERN_STDLIB_BASIC_TYPE_VA(uint, uint32_t); +EXTERN_STDLIB_BASIC_TYPE_VA(uint, uint64_t); } // namespace stdlib } // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/primitives/uint/uint.test.cpp b/cpp/src/aztec/stdlib/primitives/uint/uint.test.cpp index 47875d2f14..bfc5d562e7 100644 --- a/cpp/src/aztec/stdlib/primitives/uint/uint.test.cpp +++ b/cpp/src/aztec/stdlib/primitives/uint/uint.test.cpp @@ -2,9 +2,6 @@ #include #include #include -#include -#include -#include // #pragma GCC diagnostic ignored "-Wunused-variable" // #pragma GCC diagnostic ignored "-Wunused-parameter" @@ -16,35 +13,17 @@ namespace { auto& engine = numeric::random::get_debug_engine(); } +// NOTE: We only test width 32, but widths 8, 16, 32 and 64 can all be tested. +// In widths 8, 16, 32: all tests pass. +// In width 64, the following tests fail for UltraComposer. +// test_xor_special, test_xor_more_constants, test_and_constants, test_and_special, test_or_special, +// test_ror_special, test_hash_rounds, test_and, test_xor, test_or. +// They fail with 'C++ exception with description"Last key slice greater than 64" thrown in the test body."' namespace test_stdlib_uint { typedef uint32_t uint_native; size_t uint_native_width = 8 * sizeof(uint_native); uint_native uint_native_max = static_cast((static_cast(1) << uint_native_width) - 1); -typedef waffle::TurboComposer Composer; -typedef stdlib::uint uint_ct; -typedef stdlib::bool_t bool_ct; -typedef stdlib::witness_t witness_ct; -typedef stdlib::byte_array byte_array_ct; - -std::vector special_values({ 0U, - 1U, - 2U, - static_cast(1 << uint_native_width / 4), - static_cast(1 << uint_native_width / 2), - static_cast((1 << uint_native_width / 2) + 1), - uint_native_max }); - -size_t num_special_values = special_values.size(); -std::vector special_uints(num_special_values); - -auto set_up_special_cases(Composer* ctx) -{ - for (size_t i = 0; i != num_special_values; ++i) { - special_uints[i] = witness_ct(ctx, special_values[i]); - }; -}; - template Native get_random() { return static_cast(engine.get_random_uint64()); @@ -59,565 +38,530 @@ template std::vector get_several_random(size_t num) return result; } -TEST(stdlib_uint, test_weak_normalize) +/** + * @brief Utility function for testing the uint_ct comparison operators + * + * @details Given a uint_ct a and a constant const_b, this allows to create a + * uint_ct b having a desired relation to a (either >. = or <). + */ +uint_native impose_comparison(uint_native const_a, + uint_native const_b, + uint_native a_val, + bool force_equal = false, + bool force_gt = false, + bool force_lt = false) { - auto run_test = [](bool constant_only, bool add_constant) { - Composer composer = Composer(); - uint_ct a; - uint_native a_val = get_random(); - uint_native const_a = get_random(); - uint_native expected; - - if (constant_only) { - a = const_a; - expected = const_a; - } else { - a = witness_ct(&composer, a_val); - expected = a_val; - if (add_constant) { - a += const_a; - expected += const_a; - } - }; - - EXPECT_EQ(expected, a.get_value()); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); - bool verified = verifier.verify_proof(proof); - EXPECT_EQ(verified, true); - }; - - run_test(true, false); - run_test(false, false); - run_test(false, true); + uint_native b_val; + if (force_equal) { + b_val = a_val + const_a - const_b; + } else if (force_lt) { // forcing b < a + // if a_val + const_a != const_b, then we set up b_val + const_b = a_val + const_a - 1 + // elif a_val + const_a = const_b, then we set up b_val + const_b = a_val + const_a + // and we increment a by 1, leading to a_val + const_a = b_val + const_b + 1. + b_val = (a_val + const_a - const_b) ? a_val + const_a - const_b - 1 : const_a - const_b + (a_val++); + } else if (force_gt) { // forcing b > a + // set b_val + const_b = a_val + const_a + 1 unless that would wrap, in which case we instead + // set b_val + const_b = a then decrease a by 1. + b_val = (a_val + const_a - const_b) == uint_native_width ? const_a - const_b + (a_val--) + : a_val + const_a - const_b + 1; + } else { + b_val = get_random(); + } + return b_val; } -TEST(stdlib_uint, test_byte_array_conversion) +uint_native rotate(uint_native value, size_t rotation) { - Composer composer = Composer(); - uint_ct a = witness_ct(&composer, 0x7f6f5f6f10111213); - std::string longest_expected = { 0x7f, 0x6f, 0x5f, 0x4f, 0x10, 0x11, 0x12, 0x13 }; - // truncate, so we are running different tests for different choices of uint_native - std::string expected = longest_expected.substr(longest_expected.length() - sizeof(uint_native)); - byte_array_ct arr(&composer); - arr.write(static_cast(a)); - - EXPECT_EQ(arr.size(), sizeof(uint_native)); - EXPECT_EQ(arr.get_string(), expected); + return rotation ? static_cast(value >> rotation) + + static_cast(value << (uint_native_width - rotation)) + : value; } +template class stdlib_uint : public testing::Test { + typedef typename std::conditional, + stdlib::uint>::type uint_ct; + typedef stdlib::bool_t bool_ct; + typedef stdlib::witness_t witness_ct; + typedef stdlib::byte_array byte_array_ct; + + static inline std::vector special_values{ 0U, + 1U, + 2U, + static_cast(1 << uint_native_width / 4), + static_cast(1 << uint_native_width / 2), + static_cast((1 << uint_native_width / 2) + 1), + uint_native_max }; + + static std::vector get_special_uints(Composer* ctx) + { + std::vector special_uints; + for (size_t i = 0; i != special_values.size(); ++i) { + special_uints.emplace_back(witness_ct(ctx, special_values[i])); + }; + return special_uints; + }; -TEST(stdlib_uint, test_input_output_consistency) -{ - Composer composer = Composer(); - - for (size_t i = 1; i < 1024; i *= 2) { - uint_native a_expected = (uint_native)i; - uint_native b_expected = (uint_native)i; + public: + static void test_weak_normalize() + { + auto run_test = [](bool constant_only, bool add_constant) { + Composer composer = Composer(); + uint_ct a; + uint_native a_val = get_random(); + uint_native const_a = get_random(); + uint_native expected; + + if (constant_only) { + a = const_a; + expected = const_a; + } else { + a = witness_ct(&composer, a_val); + expected = a_val; + if (add_constant) { + a += const_a; + expected += const_a; + } + }; + + EXPECT_EQ(expected, a.get_value()); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); + bool verified = verifier.verify_proof(proof); + EXPECT_EQ(verified, true); + }; - uint_ct a = witness_ct(&composer, a_expected); - uint_ct b = witness_ct(&composer, b_expected); + run_test(true, false); + run_test(false, false); + run_test(false, true); + } + static void test_byte_array_conversion() + { + Composer composer = Composer(); + uint_ct a = witness_ct(&composer, 0x7f6f5f4f10111213); + std::string longest_expected = { 0x7f, 0x6f, 0x5f, 0x4f, 0x10, 0x11, 0x12, 0x13 }; + // truncate, so we are running different tests for different choices of uint_native + std::string expected = longest_expected.substr(longest_expected.length() - sizeof(uint_native)); byte_array_ct arr(&composer); - arr.write(static_cast(a)); - arr.write(static_cast(b)); - EXPECT_EQ(arr.size(), 2 * sizeof(uint_native)); - - uint_ct a_result(arr.slice(0, sizeof(uint_native))); - uint_ct b_result(arr.slice(sizeof(uint_native))); - - EXPECT_EQ(a_result.get_value(), a_expected); - EXPECT_EQ(b_result.get_value(), b_expected); + EXPECT_EQ(arr.size(), sizeof(uint_native)); + EXPECT_EQ(arr.get_string(), expected); } -} -TEST(stdlib_uint, test_create_from_wires) -{ - Composer composer = Composer(); - - uint_ct a = uint_ct(&composer, - std::vector{ - bool_ct(false), - bool_ct(false), - bool_ct(false), - bool_ct(false), - bool_ct(false), - bool_ct(false), - bool_ct(false), - witness_ct(&composer, true), - }); - - EXPECT_EQ(a.at(0).get_value(), false); - EXPECT_EQ(a.at(7).get_value(), true); - EXPECT_EQ(static_cast(a.get_value()), 128U); -} + static void test_input_output_consistency() + { + Composer composer = Composer(); -/** - * @brief Test addition of special values. - * */ -TEST(stdlib_uint, test_add_special) -{ - Composer composer = Composer(); + for (size_t i = 1; i < 1024; i *= 2) { + uint_native a_expected = (uint_native)i; + uint_native b_expected = (uint_native)i; - witness_ct first_input(&composer, 1U); - witness_ct second_input(&composer, 0U); + uint_ct a = witness_ct(&composer, a_expected); + uint_ct b = witness_ct(&composer, b_expected); - uint_ct a = first_input; - uint_ct b = second_input; - uint_ct c = a + b; - /** - * Fibbonacci sequence a(0) = 0, a(1), ..., a(2 + 32) = 5702887 - * a | 1 | 1 | 2 | 3 | 5 | ... - * b | 0 | 1 | 1 | 2 | 3 | ... - * c | 1 | 2 | 3 | 5 | 8 | ... - */ - for (size_t i = 0; i < uint_native_width; ++i) { - b = a; - a = c; - c = a + b; - } + byte_array_ct arr(&composer); - set_up_special_cases(&composer); - for (size_t i = 0; i != num_special_values; ++i) { - uint_native x = special_values[i]; - uint_ct x_ct = special_uints[i]; + arr.write(static_cast(a)); + arr.write(static_cast(b)); - for (size_t j = i; j != num_special_values; ++j) { - uint_native y = special_values[j]; - uint_ct y_ct = special_uints[j]; + EXPECT_EQ(arr.size(), 2 * sizeof(uint_native)); - uint_native expected_value = x + y; - uint_ct z_ct = x_ct + y_ct; - uint_native value = static_cast(z_ct.get_value()); + uint_ct a_result(arr.slice(0, sizeof(uint_native))); + uint_ct b_result(arr.slice(sizeof(uint_native))); - EXPECT_EQ(value, expected_value); + EXPECT_EQ(a_result.get_value(), a_expected); + EXPECT_EQ(b_result.get_value(), b_expected); } - }; - - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + } -TEST(stdlib_uint, test_sub_special) -{ - Composer composer = Composer(); - - witness_ct a_val(&composer, static_cast(4)); - // witness_ct b_val(&composer, static_cast(5)); - uint_native const_a = 1; - uint_native const_b = 2; - uint_ct a = uint_ct(a_val) + const_a; - // uint_ct b = uint_ct(b_val) + const_b; - uint_ct b = const_b; - uint_ct diff = a - b; - - set_up_special_cases(&composer); - for (size_t i = 0; i != num_special_values; ++i) { - uint_native x = special_values[i]; - uint_ct x_ct = special_uints[i]; - - for (size_t j = i; j != num_special_values; ++j) { - uint_native y = special_values[j]; - uint_ct y_ct = special_uints[j]; - - uint_native expected_value = x - y; - uint_ct z_ct = x_ct - y_ct; - uint_native value = static_cast(z_ct.get_value()); - - EXPECT_EQ(value, expected_value); - } - }; + static void test_create_from_wires() + { + Composer composer = Composer(); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); - bool verified = verifier.verify_proof(proof); + uint_ct a = uint_ct(&composer, + std::vector{ + bool_ct(false), + bool_ct(false), + bool_ct(false), + bool_ct(false), + bool_ct(false), + bool_ct(false), + bool_ct(false), + witness_ct(&composer, true), + }); + + EXPECT_EQ(a.at(0).get_value(), false); + EXPECT_EQ(a.at(7).get_value(), true); + EXPECT_EQ(static_cast(a.get_value()), 128U); + } - EXPECT_EQ(verified, true); -} + /** + * @brief Test addition of special values. + * */ + static void test_add_special() + { + Composer composer = Composer(); -TEST(stdlib_uint, test_add_with_constants) -{ - size_t n = 8; - std::vector witnesses = get_several_random(3 * n); - uint_native expected[8]; - for (size_t i = 2; i < n; ++i) { - expected[0] = witnesses[3 * i]; - expected[1] = witnesses[3 * i + 1]; - expected[2] = witnesses[3 * i + 2]; - expected[3] = expected[0] + expected[1]; - expected[4] = expected[1] + expected[0]; - expected[5] = expected[1] + expected[2]; - expected[6] = expected[3] + expected[4]; - expected[7] = expected[4] + expected[5]; - } - Composer composer = Composer(); - uint_ct result[8]; - for (size_t i = 2; i < n; ++i) { - result[0] = uint_ct(&composer, witnesses[3 * i]); - result[1] = (witness_ct(&composer, witnesses[3 * i + 1])); - result[2] = (witness_ct(&composer, witnesses[3 * i + 2])); - result[3] = result[0] + result[1]; - result[4] = result[1] + result[0]; - result[5] = result[1] + result[2]; - result[6] = result[3] + result[4]; - result[7] = result[4] + result[5]; - } + witness_ct first_input(&composer, 1U); + witness_ct second_input(&composer, 0U); - for (size_t i = 0; i < n; ++i) { - EXPECT_EQ(result[i].get_value(), expected[i]); - } + uint_ct a = first_input; + uint_ct b = second_input; + uint_ct c = a + b; + /** + * Fibbonacci sequence a(0) = 0, a(1), ..., a(2 + 32) = 5702887 + * a | 1 | 1 | 2 | 3 | 5 | ... + * b | 0 | 1 | 1 | 2 | 3 | ... + * c | 1 | 2 | 3 | 5 | 8 | ... + */ + for (size_t i = 0; i < uint_native_width; ++i) { + b = a; + a = c; + c = a + b; + } - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); - bool proof_valid = verifier.verify_proof(proof); - EXPECT_EQ(proof_valid, true); -} + auto special_uints = get_special_uints(&composer); + for (size_t i = 0; i != special_values.size(); ++i) { + uint_native x = special_values[i]; + uint_ct x_ct = special_uints[i]; -TEST(stdlib_uint, test_mul_special) -{ - uint_native a_expected = 1U; - uint_native b_expected = 2U; - uint_native c_expected = a_expected + b_expected; - for (size_t i = 0; i < 100; ++i) { - b_expected = a_expected; - a_expected = c_expected; - c_expected = a_expected * b_expected; - } + for (size_t j = i; j != special_values.size(); ++j) { + uint_native y = special_values[j]; + uint_ct y_ct = special_uints[j]; - Composer composer = Composer(); + uint_native expected_value = x + y; + uint_ct z_ct = x_ct + y_ct; + uint_native value = static_cast(z_ct.get_value()); - witness_ct first_input(&composer, 1U); - witness_ct second_input(&composer, 2U); + EXPECT_EQ(value, expected_value); + } + }; - uint_ct a = first_input; - uint_ct b = second_input; - uint_ct c = a + b; - for (size_t i = 0; i < 100; ++i) { - b = a; - a = c; - c = a * b; + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); } - uint_native c_result = - static_cast(composer.get_variable(c.get_witness_index()).from_montgomery_form().data[0]); - EXPECT_EQ(c_result, c_expected); - - set_up_special_cases(&composer); - for (size_t i = 0; i != num_special_values; ++i) { - uint_native x = special_values[i]; - uint_ct x_ct = special_uints[i]; - for (size_t j = i; j != num_special_values; ++j) { - uint_native y = special_values[j]; - uint_ct y_ct = special_uints[j]; + static void test_sub_special() + { + Composer composer = Composer(); - uint_native expected_value = x * y; - uint_ct z_ct = x_ct * y_ct; - uint_native value = static_cast(z_ct.get_value()); + witness_ct a_val(&composer, static_cast(4)); + // witness_ct b_val(&composer, static_cast(5)); + uint_native const_a = 1; + uint_native const_b = 2; + uint_ct a = uint_ct(a_val) + const_a; + // uint_ct b = uint_ct(b_val) + const_b; + uint_ct b = const_b; + uint_ct diff = a - b; - EXPECT_EQ(value, expected_value); - } - }; + auto special_uints = get_special_uints(&composer); + for (size_t i = 0; i != special_values.size(); ++i) { + uint_native x = special_values[i]; + uint_ct x_ct = special_uints[i]; - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + for (size_t j = i; j != special_values.size(); ++j) { + uint_native y = special_values[j]; + uint_ct y_ct = special_uints[j]; -TEST(stdlib_uint, test_mul_big) -{ - uint_native max = uint_native_max; + uint_native expected_value = x - y; + uint_ct z_ct = x_ct - y_ct; + uint_native value = static_cast(z_ct.get_value()); - Composer composer = Composer(); - uint_ct a = witness_ct(&composer, max); - a = a + max; - uint_ct b = a; - uint_ct c = a * b; + EXPECT_EQ(value, expected_value); + } + }; - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); + bool verified = verifier.verify_proof(proof); -TEST(stdlib_uint, test_xor_special) -{ - uint_native a_expected = static_cast(0x1312f0ffa3b10422); - uint_native b_expected = static_cast(0xfafab007eac21343); - uint_native c_expected = a_expected ^ b_expected; - for (size_t i = 0; i < uint_native_width; ++i) { - b_expected = a_expected; - a_expected = c_expected; - c_expected = a_expected + b_expected; - a_expected = c_expected ^ a_expected; + EXPECT_EQ(verified, true); } - Composer composer = Composer(); + static void test_add_with_constants() + { + size_t n = 8; + std::vector witnesses = get_several_random(3 * n); + uint_native expected[8]; + for (size_t i = 2; i < n; ++i) { + expected[0] = witnesses[3 * i]; + expected[1] = witnesses[3 * i + 1]; + expected[2] = witnesses[3 * i + 2]; + expected[3] = expected[0] + expected[1]; + expected[4] = expected[1] + expected[0]; + expected[5] = expected[1] + expected[2]; + expected[6] = expected[3] + expected[4]; + expected[7] = expected[4] + expected[5]; + } + Composer composer = Composer(); + uint_ct result[8]; + for (size_t i = 2; i < n; ++i) { + result[0] = uint_ct(&composer, witnesses[3 * i]); + result[1] = (witness_ct(&composer, witnesses[3 * i + 1])); + result[2] = (witness_ct(&composer, witnesses[3 * i + 2])); + result[3] = result[0] + result[1]; + result[4] = result[1] + result[0]; + result[5] = result[1] + result[2]; + result[6] = result[3] + result[4]; + result[7] = result[4] + result[5]; + } - witness_ct first_input(&composer, static_cast(0x1312f0ffa3b10422)); - witness_ct second_input(&composer, static_cast(0xfafab007eac21343)); + for (size_t i = 0; i < n; ++i) { + EXPECT_EQ(result[i].get_value(), expected[i]); + } - uint_ct a = first_input; - uint_ct b = second_input; - uint_ct c = a ^ b; - for (size_t i = 0; i < uint_native_width; ++i) { - b = a; - a = c; - c = a + b; - a = c ^ a; + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); + bool proof_valid = verifier.verify_proof(proof); + EXPECT_EQ(proof_valid, true); } - uint_native a_result = - static_cast(composer.get_variable(a.get_witness_index()).from_montgomery_form().data[0]); - EXPECT_EQ(a_result, a_expected); - - auto prover = composer.create_prover(); + static void test_mul_special() + { + uint_native a_expected = 1U; + uint_native b_expected = 2U; + uint_native c_expected = a_expected + b_expected; + for (size_t i = 0; i < 100; ++i) { + b_expected = a_expected; + a_expected = c_expected; + c_expected = a_expected * b_expected; + } - auto verifier = composer.create_verifier(); + Composer composer = Composer(); - waffle::plonk_proof proof = prover.construct_proof(); + witness_ct first_input(&composer, 1U); + witness_ct second_input(&composer, 2U); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + uint_ct a = first_input; + uint_ct b = second_input; + uint_ct c = a + b; + for (size_t i = 0; i < 100; ++i) { + b = a; + a = c; + c = a * b; + } + uint_native c_result = + static_cast(composer.get_variable(c.get_witness_index()).from_montgomery_form().data[0]); + EXPECT_EQ(c_result, c_expected); -TEST(stdlib_uint, test_xor_constants) -{ - Composer composer = Composer(); + auto special_uints = get_special_uints(&composer); + for (size_t i = 0; i != special_values.size(); ++i) { + uint_native x = special_values[i]; + uint_ct x_ct = special_uints[i]; - uint_native a_expected = static_cast(0x1312f0ffa3b10422); - uint_native b_expected = static_cast(0xfafab007eac21343); - uint_native c_expected = a_expected ^ b_expected; + for (size_t j = i; j != special_values.size(); ++j) { + uint_native y = special_values[j]; + uint_ct y_ct = special_uints[j]; - uint_ct const_a(&composer, static_cast(0x1312f0ffa3b10422)); - uint_ct const_b(&composer, static_cast(0xfafab007eac21343)); - uint_ct c = const_a ^ const_b; - c.get_witness_index(); + uint_native expected_value = x * y; + uint_ct z_ct = x_ct * y_ct; + uint_native value = static_cast(z_ct.get_value()); - EXPECT_EQ(c.get_additive_constant(), uint256_t(c_expected)); -} + EXPECT_EQ(value, expected_value); + } + }; -TEST(stdlib_uint, test_xor_more_constants) -{ - uint_native a_expected = static_cast(0x1312f0ffa3b10422); - uint_native b_expected = static_cast(0xfafab007eac21343); - uint_native c_expected = a_expected ^ b_expected; - for (size_t i = 0; i < 1; ++i) { - b_expected = a_expected; - a_expected = c_expected; - c_expected = (a_expected + b_expected) ^ - (static_cast(0x1312f0ffa3b10422) ^ static_cast(0xfafab007eac21343)); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); } - Composer composer = Composer(); + static void test_mul_big() + { + uint_native max = uint_native_max; - witness_ct first_input(&composer, static_cast(0x1312f0ffa3b10422)); - witness_ct second_input(&composer, static_cast(0xfafab007eac21343)); + Composer composer = Composer(); + uint_ct a = witness_ct(&composer, max); + a = a + max; + uint_ct b = a; + uint_ct c = a * b; - uint_ct a = first_input; - uint_ct b = second_input; - uint_ct c = a ^ b; - for (size_t i = 0; i < 1; ++i) { - uint_ct const_a = static_cast(0x1312f0ffa3b10422); - uint_ct const_b = static_cast(0xfafab007eac21343); - b = a; - a = c; - c = (a + b) ^ (const_a ^ const_b); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); } - uint32_t c_witness_index = c.get_witness_index(); - uint_native c_result = - static_cast(composer.get_variable(c_witness_index).from_montgomery_form().data[0]); - EXPECT_EQ(c_result, c_expected); - auto prover = composer.create_prover(); - - auto verifier = composer.create_verifier(); - - waffle::plonk_proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + static void test_xor_special() + { + uint_native a_expected = static_cast(0x10000000a3b10422); + uint_native b_expected = static_cast(0xfafab007eac21343); + uint_native c_expected = a_expected ^ b_expected; + for (size_t i = 0; i < uint_native_width; ++i) { + b_expected = a_expected; + a_expected = c_expected; + c_expected = a_expected + b_expected; + a_expected = c_expected ^ a_expected; + } -TEST(stdlib_uint, test_and_constants) -{ - uint_native a_expected = static_cast(0x1312f0ffa3b10422); - uint_native b_expected = static_cast(0xfafab007eac21343); - uint_native c_expected = a_expected & b_expected; - for (size_t i = 0; i < 1; ++i) { - b_expected = a_expected; - a_expected = c_expected; - c_expected = (~a_expected & static_cast(0x1312f0ffa3b10422)) + - (b_expected & static_cast(0xfafab007eac21343)); - // c_expected = (a_expected + b_expected) & (static_cast(0x1312f0ffa3b10422) & - // static_cast(0xfafab007eac21343)); - } + Composer composer = Composer(); - Composer composer = Composer(); + witness_ct first_input(&composer, static_cast(0x10000000a3b10422)); + witness_ct second_input(&composer, static_cast(0xfafab007eac21343)); - witness_ct first_input(&composer, static_cast(0x1312f0ffa3b10422)); - witness_ct second_input(&composer, static_cast(0xfafab007eac21343)); + uint_ct a = first_input; + uint_ct b = second_input; + uint_ct c = a ^ b; + for (size_t i = 0; i < uint_native_width; ++i) { + b = a; + a = c; + c = a + b; + a = c ^ a; + } + uint_native a_result = + static_cast(composer.get_variable(a.get_witness_index()).from_montgomery_form().data[0]); - uint_ct a = first_input; - uint_ct b = second_input; - uint_ct c = a & b; - for (size_t i = 0; i < 1; ++i) { - uint_ct const_a = static_cast(0x1312f0ffa3b10422); - uint_ct const_b = static_cast(0xfafab007eac21343); - b = a; - a = c; - c = (~a & const_a) + (b & const_b); - } - uint32_t c_witness_index = c.get_witness_index(); - uint_native c_result = - static_cast(composer.get_variable(c_witness_index).from_montgomery_form().data[0]); - EXPECT_EQ(c_result, c_expected); - auto prover = composer.create_prover(); + EXPECT_EQ(a_result, a_expected); - auto verifier = composer.create_verifier(); + auto prover = composer.create_prover(); - waffle::plonk_proof proof = prover.construct_proof(); + auto verifier = composer.create_verifier(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + waffle::plonk_proof proof = prover.construct_proof(); -TEST(stdlib_uint, test_and_special) -{ - uint_native a_expected = static_cast(0x1312f0ffa3b10422); - uint_native b_expected = static_cast(0xfafab007eac21343); - uint_native c_expected = a_expected + b_expected; - for (size_t i = 0; i < uint_native_width; ++i) { - b_expected = a_expected; - a_expected = c_expected; - c_expected = a_expected + b_expected; - a_expected = c_expected & a_expected; + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); } - Composer composer = Composer(); + static void test_xor_constants() + { + Composer composer = Composer(); + + uint_native a_expected = static_cast(0x10000000a3b10422); + uint_native b_expected = static_cast(0xfafab007eac21343); + uint_native c_expected = a_expected ^ b_expected; - witness_ct first_input(&composer, static_cast(0x1312f0ffa3b10422)); - witness_ct second_input(&composer, static_cast(0xfafab007eac21343)); + uint_ct const_a(&composer, static_cast(0x10000000a3b10422)); + uint_ct const_b(&composer, static_cast(0xfafab007eac21343)); + uint_ct c = const_a ^ const_b; + c.get_witness_index(); - uint_ct a = first_input; - uint_ct b = second_input; - uint_ct c = a + b; - for (size_t i = 0; i < uint_native_width; ++i) { - b = a; - a = c; - c = a + b; - a = c & a; + EXPECT_EQ(c.get_additive_constant(), uint256_t(c_expected)); } - uint_native a_result = - static_cast(composer.get_variable(a.get_witness_index()).from_montgomery_form().data[0]); - EXPECT_EQ(a_result, a_expected); - auto prover = composer.create_prover(); + static void test_xor_more_constants() + { + uint_native a_expected = static_cast(0x10000000a3b10422); + uint_native b_expected = static_cast(0xfafab007eac21343); + uint_native c_expected = a_expected ^ b_expected; + for (size_t i = 0; i < 1; ++i) { + b_expected = a_expected; + a_expected = c_expected; + c_expected = (a_expected + b_expected) ^ + (static_cast(0x10000000a3b10422) ^ static_cast(0xfafab007eac21343)); + } - auto verifier = composer.create_verifier(); + Composer composer = Composer(); - waffle::plonk_proof proof = prover.construct_proof(); + witness_ct first_input(&composer, static_cast(0x10000000a3b10422)); + witness_ct second_input(&composer, static_cast(0xfafab007eac21343)); + + uint_ct a = first_input; + uint_ct b = second_input; + uint_ct c = a ^ b; + for (size_t i = 0; i < 1; ++i) { + uint_ct const_a = static_cast(0x10000000a3b10422); + uint_ct const_b = static_cast(0xfafab007eac21343); + b = a; + a = c; + c = (a + b) ^ (const_a ^ const_b); + } + uint32_t c_witness_index = c.get_witness_index(); + uint_native c_result = + static_cast(composer.get_variable(c_witness_index).from_montgomery_form().data[0]); + EXPECT_EQ(c_result, c_expected); + auto prover = composer.create_prover(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + auto verifier = composer.create_verifier(); -TEST(stdlib_uint, test_or_special) -{ - uint_native a_expected = static_cast(0x1312f0ffa3b10422); - uint_native b_expected = static_cast(0xfafab007eac21343); - uint_native c_expected = a_expected ^ b_expected; - for (size_t i = 0; i < uint_native_width; ++i) { - b_expected = a_expected; - a_expected = c_expected; - c_expected = a_expected + b_expected; - a_expected = c_expected | a_expected; - } + waffle::plonk_proof proof = prover.construct_proof(); - Composer composer = Composer(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } - witness_ct first_input(&composer, static_cast(0x1312f0ffa3b10422)); - witness_ct second_input(&composer, static_cast(0xfafab007eac21343)); + static void test_and_constants() + { + uint_native a_expected = static_cast(0x10000000a3b10422); + uint_native b_expected = static_cast(0xfafab007eac21343); + uint_native c_expected = a_expected & b_expected; + for (size_t i = 0; i < 1; ++i) { + b_expected = a_expected; + a_expected = c_expected; + c_expected = (~a_expected & static_cast(0x10000000a3b10422)) + + (b_expected & static_cast(0xfafab007eac21343)); + // c_expected = (a_expected + b_expected) & (static_cast(0x10000000a3b10422) & + // static_cast(0xfafab007eac21343)); + } - uint_ct a = first_input; - uint_ct b = second_input; - uint_ct c = a ^ b; - for (size_t i = 0; i < uint_native_width; ++i) { - b = a; - a = c; - c = a + b; - a = c | a; - } - uint_native a_result = - static_cast(composer.get_variable(a.get_witness_index()).from_montgomery_form().data[0]); - EXPECT_EQ(a_result, a_expected); + Composer composer = Composer(); - auto prover = composer.create_prover(); + witness_ct first_input(&composer, static_cast(0x10000000a3b10422)); + witness_ct second_input(&composer, static_cast(0xfafab007eac21343)); + + uint_ct a = first_input; + uint_ct b = second_input; + uint_ct c = a & b; + for (size_t i = 0; i < 1; ++i) { + uint_ct const_a = static_cast(0x10000000a3b10422); + uint_ct const_b = static_cast(0xfafab007eac21343); + b = a; + a = c; + c = (~a & const_a) + (b & const_b); + } + uint32_t c_witness_index = c.get_witness_index(); + uint_native c_result = + static_cast(composer.get_variable(c_witness_index).from_montgomery_form().data[0]); + EXPECT_EQ(c_result, c_expected); + auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); + auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); + waffle::plonk_proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } -TEST(stdlib_uint, test_gt_special) -{ - const auto run_test = [](bool lhs_constant, bool rhs_constant, int type = 0) { - uint_native a_expected = static_cast(0x1312f0ffa3b10422); - uint_native b_expected; - switch (type) { - case 0: { - b_expected = static_cast(0xbac21343); // a < b - break; - } - case 1: { - b_expected = static_cast(0x000affe2); // a > b - break; - } - case 2: { - b_expected = static_cast(0x1312f0ffa3b10422); // a = b - break; - } - default: { - b_expected = static_cast(0xbac21343); // a < b - } + static void test_and_special() + { + uint_native a_expected = static_cast(0x10000000a3b10422); + uint_native b_expected = static_cast(0xfafab007eac21343); + uint_native c_expected = a_expected + b_expected; + for (size_t i = 0; i < uint_native_width; ++i) { + b_expected = a_expected; + a_expected = c_expected; + c_expected = a_expected + b_expected; + a_expected = c_expected & a_expected; } - bool c_expected = a_expected > b_expected; Composer composer = Composer(); - uint_ct a; - uint_ct b; - if (lhs_constant) { - a = uint_ct(nullptr, a_expected); - } else { - a = witness_ct(&composer, a_expected); - } - if (rhs_constant) { - b = uint_ct(nullptr, b_expected); - } else { - b = witness_ct(&composer, b_expected); - } - // mix in some constant terms for good measure - a *= uint_ct(&composer, 2); - a += uint_ct(&composer, 0x00112233); - b *= uint_ct(&composer, 2); - b += uint_ct(&composer, 0x00112233); - - bool_ct c = a > b; + witness_ct first_input(&composer, static_cast(0x10000000a3b10422)); + witness_ct second_input(&composer, static_cast(0xfafab007eac21343)); - bool c_result = static_cast(c.get_value()); - EXPECT_EQ(c_result, c_expected); + uint_ct a = first_input; + uint_ct b = second_input; + uint_ct c = a + b; + for (size_t i = 0; i < uint_native_width; ++i) { + b = a; + a = c; + c = a + b; + a = c & a; + } + uint_native a_result = + static_cast(composer.get_variable(a.get_witness_index()).from_montgomery_form().data[0]); + EXPECT_EQ(a_result, a_expected); auto prover = composer.create_prover(); @@ -627,1278 +571,1571 @@ TEST(stdlib_uint, test_gt_special) bool result = verifier.verify_proof(proof); EXPECT_EQ(result, true); - }; - - run_test(false, false, 0); - run_test(false, true, 0); - run_test(true, false, 0); - run_test(true, true, 0); - run_test(false, false, 1); - run_test(false, true, 1); - run_test(true, false, 1); - run_test(true, true, 1); - run_test(false, false, 2); - run_test(false, true, 2); - run_test(true, false, 2); - run_test(true, true, 2); -} - -uint_native rotate(uint_native value, size_t rotation) -{ - return rotation ? static_cast(value >> rotation) + - static_cast(value << (uint_native_width - rotation)) - : value; -} - -TEST(stdlib_uint, test_ror_special) -{ - uint_native a_expected = static_cast(0x1312f0ffa3b10422); - uint_native b_expected = static_cast(0xfafab007eac21343); - uint_native c_expected = a_expected ^ b_expected; - for (size_t i = 0; i < uint_native_width; ++i) { - b_expected = a_expected; - a_expected = c_expected; - c_expected = a_expected + b_expected; - a_expected = rotate(c_expected, i % 31) + rotate(a_expected, (i + 1) % 31); } - Composer composer = Composer(); + static void test_or_special() + { + uint_native a_expected = static_cast(0x10000000a3b10422); + uint_native b_expected = static_cast(0xfafab007eac21343); + uint_native c_expected = a_expected ^ b_expected; + for (size_t i = 0; i < uint_native_width; ++i) { + b_expected = a_expected; + a_expected = c_expected; + c_expected = a_expected + b_expected; + a_expected = c_expected | a_expected; + } - witness_ct first_input(&composer, static_cast(0x1312f0ffa3b10422)); - witness_ct second_input(&composer, static_cast(0xfafab007eac21343)); + Composer composer = Composer(); - uint_ct a = first_input; - uint_ct b = second_input; - uint_ct c = a ^ b; - for (size_t i = 0; i < uint_native_width; ++i) { - b = a; - a = c; - c = a + b; - a = c.ror(static_cast(i % 31)) + a.ror(static_cast((i + 1) % 31)); - } - uint_native a_result = - static_cast(composer.get_variable(a.get_witness_index()).from_montgomery_form().data[0]); - EXPECT_EQ(a_result, a_expected); + witness_ct first_input(&composer, static_cast(0x10000000a3b10422)); + witness_ct second_input(&composer, static_cast(0xfafab007eac21343)); - auto prover = composer.create_prover(); + uint_ct a = first_input; + uint_ct b = second_input; + uint_ct c = a ^ b; + for (size_t i = 0; i < uint_native_width; ++i) { + b = a; + a = c; + c = a + b; + a = c | a; + } + uint_native a_result = + static_cast(composer.get_variable(a.get_witness_index()).from_montgomery_form().data[0]); + EXPECT_EQ(a_result, a_expected); - auto verifier = composer.create_verifier(); + auto prover = composer.create_prover(); - waffle::plonk_proof proof = prover.construct_proof(); + auto verifier = composer.create_verifier(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + waffle::plonk_proof proof = prover.construct_proof(); -/** - * @brief If uint_native_width == 32, test part of SHA256. Otherwise, do something similar. - * - * @details Notes that the static casts have to be there becuase of -Wc++11-narrowing flag. - * - * TurboPLONK: 19896 gates - * StandardPLONK: 210363 gates - */ -TEST(stdlib_uint, test_hash_rounds) -{ - std::vector k_constants(64); - std::vector round_values(8); - if (uint_native_width == 32) { - k_constants = { static_cast(0x428a2f98), static_cast(0x71374491), - static_cast(0xb5c0fbcf), static_cast(0xe9b5dba5), - static_cast(0x3956c25b), static_cast(0x59f111f1), - static_cast(0x923f82a4), static_cast(0xab1c5ed5), - static_cast(0xd807aa98), static_cast(0x12835b01), - static_cast(0x243185be), static_cast(0x550c7dc3), - static_cast(0x72be5d74), static_cast(0x80deb1fe), - static_cast(0x9bdc06a7), static_cast(0xc19bf174), - static_cast(0xe49b69c1), static_cast(0xefbe4786), - static_cast(0x0fc19dc6), static_cast(0x240ca1cc), - static_cast(0x2de92c6f), static_cast(0x4a7484aa), - static_cast(0x5cb0a9dc), static_cast(0x76f988da), - static_cast(0x983e5152), static_cast(0xa831c66d), - static_cast(0xb00327c8), static_cast(0xbf597fc7), - static_cast(0xc6e00bf3), static_cast(0xd5a79147), - static_cast(0x06ca6351), static_cast(0x14292967), - static_cast(0x27b70a85), static_cast(0x2e1b2138), - static_cast(0x4d2c6dfc), static_cast(0x53380d13), - static_cast(0x650a7354), static_cast(0x766a0abb), - static_cast(0x81c2c92e), static_cast(0x92722c85), - static_cast(0xa2bfe8a1), static_cast(0xa81a664b), - static_cast(0xc24b8b70), static_cast(0xc76c51a3), - static_cast(0xd192e819), static_cast(0xd6990624), - static_cast(0xf40e3585), static_cast(0x106aa070), - static_cast(0x19a4c116), static_cast(0x1e376c08), - static_cast(0x2748774c), static_cast(0x34b0bcb5), - static_cast(0x391c0cb3), static_cast(0x4ed8aa4a), - static_cast(0x5b9cca4f), static_cast(0x682e6ff3), - static_cast(0x748f82ee), static_cast(0x78a5636f), - static_cast(0x84c87814), static_cast(0x8cc70208), - static_cast(0x90befffa), static_cast(0xa4506ceb), - static_cast(0xbef9a3f7), static_cast(0xc67178f2) }; - - round_values = { static_cast(0x01020304), static_cast(0x0a0b0c0d), - static_cast(0x1a2b3e4d), static_cast(0x03951bd3), - static_cast(0x0e0fa3fe), static_cast(0x01000000), - static_cast(0x0f0eeea1), static_cast(0x12345678) }; - } else { - k_constants = get_several_random(64); - round_values = get_several_random(8); - }; - - std::vector w_alt = get_several_random(64); - - uint_native a_alt = round_values[0]; - uint_native b_alt = round_values[1]; - uint_native c_alt = round_values[2]; - uint_native d_alt = round_values[3]; - uint_native e_alt = round_values[4]; - uint_native f_alt = round_values[5]; - uint_native g_alt = round_values[6]; - uint_native h_alt = round_values[7]; - for (size_t i = 0; i < 64; ++i) { - uint_native S1_alt = rotate(e_alt, 7 % uint_native_width) ^ rotate(e_alt, 11 % uint_native_width) ^ - rotate(e_alt, 25 % uint_native_width); - uint_native ch_alt = (e_alt & f_alt) ^ ((~e_alt) & g_alt); - uint_native temp1_alt = h_alt + S1_alt + ch_alt + k_constants[i % 64] + w_alt[i]; - - uint_native S0_alt = rotate(a_alt, 2 % uint_native_width) ^ rotate(a_alt, 13 % uint_native_width) ^ - rotate(a_alt, 22 % uint_native_width); - uint_native maj_alt = (a_alt & b_alt) ^ (a_alt & c_alt) ^ (b_alt & c_alt); - uint_native temp2_alt = S0_alt + maj_alt; - - h_alt = g_alt; - g_alt = f_alt; - f_alt = e_alt; - e_alt = d_alt + temp1_alt; - d_alt = c_alt; - c_alt = b_alt; - b_alt = a_alt; - a_alt = temp1_alt + temp2_alt; + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); } - Composer composer = Composer(); - std::vector w; - std::vector k; - for (size_t i = 0; i < 64; ++i) { - w.emplace_back(uint_ct(witness_ct(&composer, w_alt[i]))); - k.emplace_back(uint_ct(&composer, k_constants[i % 64])); - } - uint_ct a = witness_ct(&composer, round_values[0]); - uint_ct b = witness_ct(&composer, round_values[1]); - uint_ct c = witness_ct(&composer, round_values[2]); - uint_ct d = witness_ct(&composer, round_values[3]); - uint_ct e = witness_ct(&composer, round_values[4]); - uint_ct f = witness_ct(&composer, round_values[5]); - uint_ct g = witness_ct(&composer, round_values[6]); - uint_ct h = witness_ct(&composer, round_values[7]); - for (size_t i = 0; i < 64; ++i) { - uint_ct S1 = e.ror(7U % uint_native_width) ^ e.ror(11U % uint_native_width) ^ e.ror(25U % uint_native_width); - uint_ct ch = (e & f) + ((~e) & g); - uint_ct temp1 = h + S1 + ch + k[i] + w[i]; - - uint_ct S0 = a.ror(2U % uint_native_width) ^ a.ror(13U % uint_native_width) ^ a.ror(22U % uint_native_width); - uint_ct T0 = (b & c); - uint_ct T1 = (b - T0) + (c - T0); - uint_ct T2 = a & T1; - uint_ct maj = T2 + T0; - uint_ct temp2 = S0 + maj; - - h = g; - g = f; - f = e; - e = d + temp1; - d = c; - c = b; - b = a; - a = temp1 + temp2; - } + static void test_gt_special() + { + const auto run_test = [](bool lhs_constant, bool rhs_constant, int type = 0) { + uint_native a_expected = static_cast(0x10000000a3b10422); + uint_native b_expected; + switch (type) { + case 0: { + b_expected = static_cast(0x20000000bac21343); // a < b + break; + } + case 1: { + b_expected = static_cast(0x0000000000002f12); // a > b + break; + } + case 2: { + b_expected = static_cast(0x10000000a3b10422); // a = b + break; + } + default: { + b_expected = static_cast(0x20000000bac21343); // a < b + } + } + bool c_expected = a_expected > b_expected; - uint_native a_result = - static_cast(composer.get_variable(a.get_witness_index()).from_montgomery_form().data[0]); - uint_native b_result = - static_cast(composer.get_variable(b.get_witness_index()).from_montgomery_form().data[0]); - uint_native c_result = - static_cast(composer.get_variable(c.get_witness_index()).from_montgomery_form().data[0]); - uint_native d_result = - static_cast(composer.get_variable(d.get_witness_index()).from_montgomery_form().data[0]); - uint_native e_result = - static_cast(composer.get_variable(e.get_witness_index()).from_montgomery_form().data[0]); - uint_native f_result = - static_cast(composer.get_variable(f.get_witness_index()).from_montgomery_form().data[0]); - uint_native g_result = - static_cast(composer.get_variable(g.get_witness_index()).from_montgomery_form().data[0]); - uint_native h_result = - static_cast(composer.get_variable(h.get_witness_index()).from_montgomery_form().data[0]); - - EXPECT_EQ(a_result, a_alt); - EXPECT_EQ(b_result, b_alt); - EXPECT_EQ(c_result, c_alt); - EXPECT_EQ(d_result, d_alt); - EXPECT_EQ(e_result, e_alt); - EXPECT_EQ(f_result, f_alt); - EXPECT_EQ(g_result, g_alt); - EXPECT_EQ(h_result, h_alt); + Composer composer = Composer(); - auto prover = composer.create_prover(); + uint_ct a; + uint_ct b; + if (lhs_constant) { + a = uint_ct(nullptr, a_expected); + } else { + a = witness_ct(&composer, a_expected); + } + if (rhs_constant) { + b = uint_ct(nullptr, b_expected); + } else { + b = witness_ct(&composer, b_expected); + } + // mix in some constant terms for good measure + a *= uint_ct(&composer, 2); + a += uint_ct(&composer, 1); + b *= uint_ct(&composer, 2); + b += uint_ct(&composer, 1); - auto verifier = composer.create_verifier(); + bool_ct c = a > b; - waffle::plonk_proof proof = prover.construct_proof(); + bool c_result = static_cast(c.get_value()); + EXPECT_EQ(c_result, c_expected); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + auto prover = composer.create_prover(); -// BELOW HERE ARE TESTS FORMERLY MARKED AS TURBO + auto verifier = composer.create_verifier(); -/** - * @brief Utility function for testing the uint_ct comparison operators - * - * @details Given a uint_ct a and a constant const_b, this allows to create a - * uint_ct b having a desired relation to a (either >. = or <). - */ -uint_native impose_comparison(uint_native const_a, - uint_native const_b, - uint_native a_val, - bool force_equal = false, - bool force_gt = false, - bool force_lt = false) -{ - uint_native b_val; - if (force_equal) { - b_val = a_val + const_a - const_b; - } else if (force_lt) { // forcing b < a - // if a_val + const_a != const_b, then we set up b_val + const_b = a_val + const_a - 1 - // elif a_val + const_a = const_b, then we set up b_val + const_b = a_val + const_a - // and we increment a by 1, leading to a_val + const_a = b_val + const_b + 1. - b_val = (a_val + const_a - const_b) ? a_val + const_a - const_b - 1 : const_a - const_b + (a_val++); - } else if (force_gt) { // forcing b > a - // set b_val + const_b = a_val + const_a + 1 unless that would wrap, in which case we instead - // set b_val + const_b = a then decrease a by 1. - b_val = (a_val + const_a - const_b) == uint_native_width ? const_a - const_b + (a_val--) - : a_val + const_a - const_b + 1; - } else { - b_val = get_random(); - } - return b_val; -} + waffle::plonk_proof proof = prover.construct_proof(); -/** - * @brief Test addition of random uint's, trying all combinations of (constant, witness). - */ -TEST(stdlib_uint, test_add) -{ - Composer composer = Composer(); - - const auto add_integers = [&composer](bool lhs_constant = false, bool rhs_constant = false) { - uint_native a_val = get_random(); - uint_native b_val = get_random(); - uint_native expected = a_val + b_val; - uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); - uint_ct b = rhs_constant ? uint_ct(&composer, b_val) : witness_ct(&composer, b_val); - uint_ct c = a + b; - c = c.normalize(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + }; - uint_native result = uint_native(c.get_value()); + run_test(false, false, 0); + run_test(false, true, 0); + run_test(true, false, 0); + run_test(true, true, 0); + run_test(false, false, 1); + run_test(false, true, 1); + run_test(true, false, 1); + run_test(true, true, 1); + run_test(false, false, 2); + run_test(false, true, 2); + run_test(true, false, 2); + run_test(true, true, 2); + } - EXPECT_EQ(result, expected); - }; + static uint_native rotate(uint_native value, size_t rotation) + { + return rotation ? static_cast(value >> rotation) + + static_cast(value << (uint_native_width - rotation)) + : value; + } - add_integers(false, false); - add_integers(false, true); - add_integers(true, false); - add_integers(true, true); + static void test_ror_special() + { + uint_native a_expected = static_cast(0x10000000a3b10422); + uint_native b_expected = static_cast(0xfafab007eac21343); + uint_native c_expected = a_expected ^ b_expected; + for (size_t i = 0; i < uint_native_width; ++i) { + b_expected = a_expected; + a_expected = c_expected; + c_expected = a_expected + b_expected; + a_expected = rotate(c_expected, i % 31) + rotate(a_expected, (i + 1) % 31); + } - auto prover = composer.create_prover(); + Composer composer = Composer(); - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + witness_ct first_input(&composer, static_cast(0x10000000a3b10422)); + witness_ct second_input(&composer, static_cast(0xfafab007eac21343)); - waffle::plonk_proof proof = prover.construct_proof(); + uint_ct a = first_input; + uint_ct b = second_input; + uint_ct c = a ^ b; + for (size_t i = 0; i < uint_native_width; ++i) { + b = a; + a = c; + c = a + b; + a = c.ror(static_cast(i % 31)) + a.ror(static_cast((i + 1) % 31)); + } + uint_native a_result = + static_cast(composer.get_variable(a.get_witness_index()).from_montgomery_form().data[0]); + EXPECT_EQ(a_result, a_expected); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + auto prover = composer.create_prover(); -TEST(stdlib_uint, test_sub) -{ - Composer composer = Composer(); - - const auto sub_integers = [&composer](bool lhs_constant = false, bool rhs_constant = false) { - uint_native a_val = get_random(); - uint_native b_val = get_random(); - uint_native const_shift_val = get_random(); - uint_native expected = a_val - (b_val + const_shift_val); - uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); - uint_ct b = rhs_constant ? uint_ct(&composer, b_val) : witness_ct(&composer, b_val); - uint_ct b_shift = uint_ct(&composer, const_shift_val); - uint_ct c = b + b_shift; - uint_ct d = a - c; - d = d.normalize(); - - uint_native result = uint_native(d.get_value()); + auto verifier = composer.create_verifier(); - EXPECT_EQ(result, expected); - }; + waffle::plonk_proof proof = prover.construct_proof(); - sub_integers(false, false); - sub_integers(false, true); - sub_integers(true, false); - sub_integers(true, true); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } - auto prover = composer.create_prover(); + /** + * @brief If uint_native_width == 32, test part of SHA256. Otherwise, do something similar. + * + * @details Notes that the static casts have to be there becuase of -Wc++11-narrowing flag. + * + * TurboPLONK: 19896 gates + * StandardPLONK: 210363 gates + */ + static void test_hash_rounds() + { + std::vector k_constants(64); + std::vector round_values(8); + if (uint_native_width == 32) { + k_constants = { static_cast(0x428a2f98), static_cast(0x71374491), + static_cast(0xb5c0fbcf), static_cast(0xe9b5dba5), + static_cast(0x3956c25b), static_cast(0x59f111f1), + static_cast(0x923f82a4), static_cast(0xab1c5ed5), + static_cast(0xd807aa98), static_cast(0x12835b01), + static_cast(0x243185be), static_cast(0x550c7dc3), + static_cast(0x72be5d74), static_cast(0x80deb1fe), + static_cast(0x9bdc06a7), static_cast(0xc19bf174), + static_cast(0xe49b69c1), static_cast(0xefbe4786), + static_cast(0x0fc19dc6), static_cast(0x240ca1cc), + static_cast(0x2de92c6f), static_cast(0x4a7484aa), + static_cast(0x5cb0a9dc), static_cast(0x76f988da), + static_cast(0x983e5152), static_cast(0xa831c66d), + static_cast(0xb00327c8), static_cast(0xbf597fc7), + static_cast(0xc6e00bf3), static_cast(0xd5a79147), + static_cast(0x06ca6351), static_cast(0x14292967), + static_cast(0x27b70a85), static_cast(0x2e1b2138), + static_cast(0x4d2c6dfc), static_cast(0x53380d13), + static_cast(0x650a7354), static_cast(0x766a0abb), + static_cast(0x81c2c92e), static_cast(0x92722c85), + static_cast(0xa2bfe8a1), static_cast(0xa81a664b), + static_cast(0xc24b8b70), static_cast(0xc76c51a3), + static_cast(0xd192e819), static_cast(0xd6990624), + static_cast(0xf40e3585), static_cast(0x106aa070), + static_cast(0x19a4c116), static_cast(0x1e376c08), + static_cast(0x2748774c), static_cast(0x34b0bcb5), + static_cast(0x391c0cb3), static_cast(0x4ed8aa4a), + static_cast(0x5b9cca4f), static_cast(0x682e6ff3), + static_cast(0x748f82ee), static_cast(0x78a5636f), + static_cast(0x84c87814), static_cast(0x8cc70208), + static_cast(0x90befffa), static_cast(0xa4506ceb), + static_cast(0xbef9a3f7), static_cast(0xc67178f2) }; + + round_values = { static_cast(0x01020304), static_cast(0x0a0b0c0d), + static_cast(0x1a2b3e4d), static_cast(0x03951bd3), + static_cast(0x0e0fa3fe), static_cast(0x01000000), + static_cast(0x0f0eeea1), static_cast(0x12345678) }; + } else { + k_constants = get_several_random(64); + round_values = get_several_random(8); + }; - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + std::vector w_alt = get_several_random(64); + + uint_native a_alt = round_values[0]; + uint_native b_alt = round_values[1]; + uint_native c_alt = round_values[2]; + uint_native d_alt = round_values[3]; + uint_native e_alt = round_values[4]; + uint_native f_alt = round_values[5]; + uint_native g_alt = round_values[6]; + uint_native h_alt = round_values[7]; + for (size_t i = 0; i < 64; ++i) { + uint_native S1_alt = rotate(e_alt, 7 % uint_native_width) ^ rotate(e_alt, 11 % uint_native_width) ^ + rotate(e_alt, 25 % uint_native_width); + uint_native ch_alt = (e_alt & f_alt) ^ ((~e_alt) & g_alt); + uint_native temp1_alt = h_alt + S1_alt + ch_alt + k_constants[i % 64] + w_alt[i]; + + uint_native S0_alt = rotate(a_alt, 2 % uint_native_width) ^ rotate(a_alt, 13 % uint_native_width) ^ + rotate(a_alt, 22 % uint_native_width); + uint_native maj_alt = (a_alt & b_alt) ^ (a_alt & c_alt) ^ (b_alt & c_alt); + uint_native temp2_alt = S0_alt + maj_alt; + + h_alt = g_alt; + g_alt = f_alt; + f_alt = e_alt; + e_alt = d_alt + temp1_alt; + d_alt = c_alt; + c_alt = b_alt; + b_alt = a_alt; + a_alt = temp1_alt + temp2_alt; + } + Composer composer = Composer(); - waffle::plonk_proof proof = prover.construct_proof(); + std::vector w; + std::vector k; + for (size_t i = 0; i < 64; ++i) { + w.emplace_back(uint_ct(witness_ct(&composer, w_alt[i]))); + k.emplace_back(uint_ct(&composer, k_constants[i % 64])); + } + uint_ct a = witness_ct(&composer, round_values[0]); + uint_ct b = witness_ct(&composer, round_values[1]); + uint_ct c = witness_ct(&composer, round_values[2]); + uint_ct d = witness_ct(&composer, round_values[3]); + uint_ct e = witness_ct(&composer, round_values[4]); + uint_ct f = witness_ct(&composer, round_values[5]); + uint_ct g = witness_ct(&composer, round_values[6]); + uint_ct h = witness_ct(&composer, round_values[7]); + for (size_t i = 0; i < 64; ++i) { + uint_ct S1 = + e.ror(7U % uint_native_width) ^ e.ror(11U % uint_native_width) ^ e.ror(25U % uint_native_width); + uint_ct ch = (e & f) + ((~e) & g); + uint_ct temp1 = h + S1 + ch + k[i] + w[i]; + + uint_ct S0 = + a.ror(2U % uint_native_width) ^ a.ror(13U % uint_native_width) ^ a.ror(22U % uint_native_width); + uint_ct T0 = (b & c); + uint_ct T1 = (b - T0) + (c - T0); + uint_ct T2 = a & T1; + uint_ct maj = T2 + T0; + uint_ct temp2 = S0 + maj; + + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + uint_native a_result = + static_cast(composer.get_variable(a.get_witness_index()).from_montgomery_form().data[0]); + uint_native b_result = + static_cast(composer.get_variable(b.get_witness_index()).from_montgomery_form().data[0]); + uint_native c_result = + static_cast(composer.get_variable(c.get_witness_index()).from_montgomery_form().data[0]); + uint_native d_result = + static_cast(composer.get_variable(d.get_witness_index()).from_montgomery_form().data[0]); + uint_native e_result = + static_cast(composer.get_variable(e.get_witness_index()).from_montgomery_form().data[0]); + uint_native f_result = + static_cast(composer.get_variable(f.get_witness_index()).from_montgomery_form().data[0]); + uint_native g_result = + static_cast(composer.get_variable(g.get_witness_index()).from_montgomery_form().data[0]); + uint_native h_result = + static_cast(composer.get_variable(h.get_witness_index()).from_montgomery_form().data[0]); + + EXPECT_EQ(a_result, a_alt); + EXPECT_EQ(b_result, b_alt); + EXPECT_EQ(c_result, c_alt); + EXPECT_EQ(d_result, d_alt); + EXPECT_EQ(e_result, e_alt); + EXPECT_EQ(f_result, f_alt); + EXPECT_EQ(g_result, g_alt); + EXPECT_EQ(h_result, h_alt); -TEST(stdlib_uint, test_mul) -{ - Composer composer = Composer(); - - const auto mul_integers = [&composer](bool lhs_constant = false, bool rhs_constant = false) { - uint_native a_val = get_random(); - uint_native b_val = get_random(); - uint_native const_a = get_random(); - uint_native const_b = get_random(); - uint_native expected = static_cast(a_val + const_a) * static_cast(b_val + const_b); - uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); - uint_ct b = rhs_constant ? uint_ct(&composer, b_val) : witness_ct(&composer, b_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct b_shift = uint_ct(&composer, const_b); - uint_ct c = a + a_shift; - uint_ct d = b + b_shift; - uint_ct e = c * d; - e = e.normalize(); - - uint_native result = uint_native(e.get_value()); + auto prover = composer.create_prover(); - EXPECT_EQ(result, expected); - }; + auto verifier = composer.create_verifier(); - mul_integers(false, false); - mul_integers(false, true); - mul_integers(true, false); - mul_integers(true, true); + waffle::plonk_proof proof = prover.construct_proof(); - auto prover = composer.create_prover(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + // BELOW HERE ARE TESTS FORMERLY MARKED AS TURBO - waffle::plonk_proof proof = prover.construct_proof(); + /** + * @brief Test addition of random uint's, trying all combinations of (constant, witness). + */ + static void test_add() + { + Composer composer = Composer(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + const auto add_integers = [&composer](bool lhs_constant = false, bool rhs_constant = false) { + uint_native a_val = get_random(); + uint_native b_val = get_random(); + uint_native expected = a_val + b_val; + uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); + uint_ct b = rhs_constant ? uint_ct(&composer, b_val) : witness_ct(&composer, b_val); + uint_ct c = a + b; + c = c.normalize(); -TEST(stdlib_uint, test_divide) -{ - Composer composer = Composer(); - - const auto divide_integers = [&composer](bool lhs_constant = false, - bool rhs_constant = false, - bool dividend_is_divisor = false, - bool dividend_zero = false, - bool divisor_zero = false) { - uint_native a_val = get_random(); - uint_native b_val = dividend_is_divisor ? a_val : get_random(); - uint_native const_a = dividend_zero ? 0 - a_val : get_random(); - uint_native const_b = divisor_zero ? 0 - b_val : (dividend_is_divisor ? const_a : get_random()); - uint_native expected = static_cast(a_val + const_a) / static_cast(b_val + const_b); - uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); - uint_ct b = rhs_constant ? uint_ct(&composer, b_val) : witness_ct(&composer, b_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct b_shift = uint_ct(&composer, const_b); - uint_ct c = a + a_shift; - uint_ct d = b + b_shift; - uint_ct e = c / d; - e = e.normalize(); - - uint_native result = static_cast(e.get_value()); + uint_native result = uint_native(c.get_value()); - EXPECT_EQ(result, expected); - }; + EXPECT_EQ(result, expected); + }; - divide_integers(false, false, false, false, false); - divide_integers(false, false, false, false, false); - divide_integers(false, false, false, false, false); - divide_integers(false, false, false, false, false); - divide_integers(false, false, false, false, false); + add_integers(false, false); + add_integers(false, true); + add_integers(true, false); + add_integers(true, true); - divide_integers(false, true, false, false, false); - divide_integers(true, false, false, false, false); - divide_integers(true, true, false, false, false); // fails; 0 != 1 + auto prover = composer.create_prover(); - divide_integers(false, false, true, false, false); - divide_integers(false, true, true, false, false); - divide_integers(true, false, true, false, false); - divide_integers(true, true, true, false, false); + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); - divide_integers(false, false, false, true, false); - divide_integers(false, true, false, true, false); // fails; 0 != 1 - divide_integers(true, false, false, true, false); - divide_integers(true, true, false, true, false); + waffle::plonk_proof proof = prover.construct_proof(); - auto prover = composer.create_prover(); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + static void test_sub() + { + Composer composer = Composer(); - waffle::plonk_proof proof = prover.construct_proof(); + const auto sub_integers = [&composer](bool lhs_constant = false, bool rhs_constant = false) { + uint_native a_val = get_random(); + uint_native b_val = get_random(); + uint_native const_shift_val = get_random(); + uint_native expected = a_val - (b_val + const_shift_val); + uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); + uint_ct b = rhs_constant ? uint_ct(&composer, b_val) : witness_ct(&composer, b_val); + uint_ct b_shift = uint_ct(&composer, const_shift_val); + uint_ct c = b + b_shift; + uint_ct d = a - c; + d = d.normalize(); + + uint_native result = uint_native(d.get_value()); + + EXPECT_EQ(result, expected); + }; - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + sub_integers(false, false); + sub_integers(false, true); + sub_integers(true, false); + sub_integers(true, true); -TEST(stdlib_uint, test_modulo) -{ - Composer composer = Composer(); - - const auto mod_integers = [&composer](bool lhs_constant = false, - bool rhs_constant = false, - bool dividend_is_divisor = false, - bool dividend_zero = false, - bool divisor_zero = false) { - uint_native a_val = get_random(); - uint_native b_val = dividend_is_divisor ? a_val : get_random(); - uint_native const_a = dividend_zero ? 0 - a_val : get_random(); - uint_native const_b = divisor_zero ? 0 - b_val : (dividend_is_divisor ? const_a : get_random()); - uint_native expected = static_cast(a_val + const_a) % static_cast(b_val + const_b); - uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); - uint_ct b = rhs_constant ? uint_ct(&composer, b_val) : witness_ct(&composer, b_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct b_shift = uint_ct(&composer, const_b); - uint_ct c = a + a_shift; - uint_ct d = b + b_shift; - uint_ct e = c % d; - e = e.normalize(); - - uint_native result = uint_native(e.get_value()); + auto prover = composer.create_prover(); - EXPECT_EQ(result, expected); - }; + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); - mod_integers(false, false, false, false, false); - mod_integers(false, true, false, false, false); - mod_integers(true, false, false, false, false); - mod_integers(true, true, false, false, false); + waffle::plonk_proof proof = prover.construct_proof(); - mod_integers(false, false, true, false, false); - mod_integers(false, true, true, false, false); - mod_integers(true, false, true, false, false); - mod_integers(true, true, true, false, false); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } - mod_integers(false, false, false, true, false); - mod_integers(false, true, false, true, false); - mod_integers(true, false, false, true, false); - mod_integers(true, true, false, true, false); + static void test_mul() + { + Composer composer = Composer(); - auto prover = composer.create_prover(); + const auto mul_integers = [&composer](bool lhs_constant = false, bool rhs_constant = false) { + uint_native a_val = get_random(); + uint_native b_val = get_random(); + uint_native const_a = get_random(); + uint_native const_b = get_random(); + uint_native expected = + static_cast(a_val + const_a) * static_cast(b_val + const_b); + uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); + uint_ct b = rhs_constant ? uint_ct(&composer, b_val) : witness_ct(&composer, b_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct b_shift = uint_ct(&composer, const_b); + uint_ct c = a + a_shift; + uint_ct d = b + b_shift; + uint_ct e = c * d; + e = e.normalize(); + + uint_native result = uint_native(e.get_value()); + + EXPECT_EQ(result, expected); + }; - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + mul_integers(false, false); + mul_integers(false, true); + mul_integers(true, false); + mul_integers(true, true); - waffle::plonk_proof proof = prover.construct_proof(); + auto prover = composer.create_prover(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); -TEST(stdlib_uint, test_divide_by_zero_fails) -{ + waffle::plonk_proof proof = prover.construct_proof(); - const auto divide_integers = [](bool lhs_constant = false, - bool rhs_constant = false, - bool dividend_is_divisor = false, - bool dividend_zero = false, - bool divisor_zero = false) { + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } + + static void test_divide() + { Composer composer = Composer(); - uint_native a_val = get_random(); - uint_native b_val = dividend_is_divisor ? a_val : get_random(); - uint_native const_a = dividend_zero ? 0 - a_val : get_random(); - uint_native const_b = divisor_zero ? 0 - b_val : (dividend_is_divisor ? const_a : get_random()); - uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); - uint_ct b = rhs_constant ? uint_ct(&composer, b_val) : witness_ct(&composer, b_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct b_shift = uint_ct(&composer, const_b); - uint_ct c = a + a_shift; - uint_ct d = b + b_shift; - uint_ct e = c / d; - e = e.normalize(); + const auto divide_integers = [&composer](bool lhs_constant = false, + bool rhs_constant = false, + bool dividend_is_divisor = false, + bool dividend_zero = false, + bool divisor_zero = false) { + uint_native a_val = get_random(); + uint_native b_val = dividend_is_divisor ? a_val : get_random(); + uint_native const_a = dividend_zero ? 0 - a_val : get_random(); + uint_native const_b = + divisor_zero ? 0 - b_val : (dividend_is_divisor ? const_a : get_random()); + uint_native expected = + static_cast(a_val + const_a) / static_cast(b_val + const_b); + uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); + uint_ct b = rhs_constant ? uint_ct(&composer, b_val) : witness_ct(&composer, b_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct b_shift = uint_ct(&composer, const_b); + uint_ct c = a + a_shift; + uint_ct d = b + b_shift; + uint_ct e = c / d; + e = e.normalize(); + + uint_native result = static_cast(e.get_value()); + + EXPECT_EQ(result, expected); + }; + + divide_integers(false, false, false, false, false); + divide_integers(false, false, false, false, false); + divide_integers(false, false, false, false, false); + divide_integers(false, false, false, false, false); + divide_integers(false, false, false, false, false); + + divide_integers(false, true, false, false, false); + divide_integers(true, false, false, false, false); + divide_integers(true, true, false, false, false); // fails; 0 != 1 + + divide_integers(false, false, true, false, false); + divide_integers(false, true, true, false, false); + divide_integers(true, false, true, false, false); + divide_integers(true, true, true, false, false); + + divide_integers(false, false, false, true, false); + divide_integers(false, true, false, true, false); // fails; 0 != 1 + divide_integers(true, false, false, true, false); + divide_integers(true, true, false, true, false); auto prover = composer.create_prover(); + printf("composer gates = %zu\n", composer.get_num_gates()); auto verifier = composer.create_verifier(); waffle::plonk_proof proof = prover.construct_proof(); bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, false); - }; - - divide_integers(false, false, false, false, true); - divide_integers(false, false, false, true, true); - divide_integers(true, true, false, false, true); - divide_integers(true, true, false, true, true); -} + EXPECT_EQ(proof_result, true); + } -TEST(stdlib_uint, test_divide_special) -{ - Composer composer = Composer(); - - set_up_special_cases(&composer); - - for (size_t i = 0; i != num_special_values; ++i) { - uint_native x = special_values[i]; - uint_ct x_ct = special_uints[i]; - - for (size_t j = i; j != num_special_values; ++j) { - uint_native y = special_values[j]; - uint_ct y_ct = special_uints[j]; - - // uint_native hits this error when trying to divide by zero: - // Stop reason: signal SIGFPE: integer divide by zero - uint_native expected_value; - uint_ct z_ct; - uint_native value; - if (y != 0) { - expected_value = x / y; - z_ct = x_ct / y_ct; - value = static_cast(z_ct.get_value()); - EXPECT_EQ(value, expected_value); - } - } - }; + static void test_modulo() + { + Composer composer = Composer(); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); -} + const auto mod_integers = [&composer](bool lhs_constant = false, + bool rhs_constant = false, + bool dividend_is_divisor = false, + bool dividend_zero = false, + bool divisor_zero = false) { + uint_native a_val = get_random(); + uint_native b_val = dividend_is_divisor ? a_val : get_random(); + uint_native const_a = dividend_zero ? 0 - a_val : get_random(); + uint_native const_b = + divisor_zero ? 0 - b_val : (dividend_is_divisor ? const_a : get_random()); + uint_native expected = + static_cast(a_val + const_a) % static_cast(b_val + const_b); + uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); + uint_ct b = rhs_constant ? uint_ct(&composer, b_val) : witness_ct(&composer, b_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct b_shift = uint_ct(&composer, const_b); + uint_ct c = a + a_shift; + uint_ct d = b + b_shift; + uint_ct e = c % d; + e = e.normalize(); + + uint_native result = uint_native(e.get_value()); + + EXPECT_EQ(result, expected); + }; -/** - * @brief Make sure we prevent proving v / v = 0 by setting the divison remainder to be v. - * TODO: This is lifted from the implementation. Should rewrite this test after introducing framework that separates - * circuit construction from witness generation. + mod_integers(false, false, false, false, false); + mod_integers(false, true, false, false, false); + mod_integers(true, false, false, false, false); + mod_integers(true, true, false, false, false); - */ -TEST(stdlib_uint, div_remainder_constraint) -{ - waffle::TurboComposer composer = waffle::TurboComposer(); - - uint_native val = get_random(); - - uint_ct a = witness_ct(&composer, val); - uint_ct b = witness_ct(&composer, val); - - const uint32_t dividend_idx = a.get_witness_index(); - const uint32_t divisor_idx = b.get_witness_index(); - - const uint256_t divisor = b.get_value(); - - const uint256_t q = 0; - const uint256_t r = val; - - const uint32_t quotient_idx = composer.add_variable(q); - const uint32_t remainder_idx = composer.add_variable(r); - - // In this example there are no additive constaints, so we just replace them by zero below. - - // constraint: qb + const_b q + 0 b - a + r - const_a == 0 - // i.e., a + const_a = q(b + const_b) + r - const waffle::mul_quad division_gate{ .a = quotient_idx, // q - .b = divisor_idx, // b - .c = dividend_idx, // a - .d = remainder_idx, // r - .mul_scaling = fr::one(), - .a_scaling = b.get_additive_constant(), - .b_scaling = fr::zero(), - .c_scaling = fr::neg_one(), - .d_scaling = fr::one(), - .const_scaling = -a.get_additive_constant() }; - composer.create_big_mul_gate(division_gate); - - // set delta = (b + const_b - r) - - // constraint: b - r - delta + const_b == 0 - const uint256_t delta = divisor - r - 1; - const uint32_t delta_idx = composer.add_variable(delta); - - const waffle::add_triple delta_gate{ - .a = divisor_idx, // b - .b = remainder_idx, // r - .c = delta_idx, // d - .a_scaling = fr::one(), - .b_scaling = fr::neg_one(), - .c_scaling = fr::neg_one(), - .const_scaling = b.get_additive_constant(), - }; + mod_integers(false, false, true, false, false); + mod_integers(false, true, true, false, false); + mod_integers(true, false, true, false, false); + mod_integers(true, true, true, false, false); - composer.create_add_gate(delta_gate); + mod_integers(false, false, false, true, false); + mod_integers(false, true, false, true, false); + mod_integers(true, false, false, true, false); + mod_integers(true, true, false, true, false); - // validate delta is in the correct range - stdlib::field_t::from_witness_index(&composer, delta_idx) - .create_range_constraint(uint_native_width); + auto prover = composer.create_prover(); - // normalize witness quotient and remainder - // minimal bit range for quotient: from 0 (in case a = b-1) to width (when b = 1). - uint_ct quotient(&composer); - composer.decompose_into_base4_accumulators(quotient_idx, uint_native_width); + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); - // constrain remainder to lie in [0, 2^width-1] - uint_ct remainder(&composer); - composer.decompose_into_base4_accumulators(remainder_idx, uint_native_width); + waffle::plonk_proof proof = prover.construct_proof(); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, false); -} + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } -TEST(stdlib_uint, test_and) -{ - Composer composer = Composer(); - - const auto and_integers = [&composer](bool lhs_constant = false, bool rhs_constant = false) { - uint_native a_val = get_random(); - uint_native b_val = get_random(); - uint_native const_a = get_random(); - uint_native const_b = get_random(); - uint_native expected = (a_val + const_a) & (b_val + const_b); - uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); - uint_ct b = rhs_constant ? uint_ct(&composer, b_val) : witness_ct(&composer, b_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct b_shift = uint_ct(&composer, const_b); - uint_ct c = a + a_shift; - uint_ct d = b + b_shift; - uint_ct e = c & d; - e = e.normalize(); - - uint_native result = uint_native(e.get_value()); + static void test_divide_by_zero_fails() + { + + const auto divide_integers = [](bool lhs_constant = false, + bool rhs_constant = false, + bool dividend_is_divisor = false, + bool dividend_zero = false, + bool divisor_zero = false) { + Composer composer = Composer(); + + uint_native a_val = get_random(); + uint_native b_val = dividend_is_divisor ? a_val : get_random(); + uint_native const_a = dividend_zero ? 0 - a_val : get_random(); + uint_native const_b = + divisor_zero ? 0 - b_val : (dividend_is_divisor ? const_a : get_random()); + uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); + uint_ct b = rhs_constant ? uint_ct(&composer, b_val) : witness_ct(&composer, b_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct b_shift = uint_ct(&composer, const_b); + uint_ct c = a + a_shift; + uint_ct d = b + b_shift; + uint_ct e = c / d; + e = e.normalize(); + + auto prover = composer.create_prover(); + + auto verifier = composer.create_verifier(); + + waffle::plonk_proof proof = prover.construct_proof(); + + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, false); + }; - EXPECT_EQ(result, expected); - }; + divide_integers(false, false, false, false, true); + divide_integers(false, false, false, true, true); + divide_integers(true, true, false, false, true); + divide_integers(true, true, false, true, true); + } - and_integers(false, false); - and_integers(false, true); - and_integers(true, false); - and_integers(true, true); + static void test_divide_special() + { + Composer composer = Composer(); - auto prover = composer.create_prover(); + auto special_uints = get_special_uints(&composer); + + for (size_t i = 0; i != special_values.size(); ++i) { + uint_native x = special_values[i]; + uint_ct x_ct = special_uints[i]; + + for (size_t j = i; j != special_values.size(); ++j) { + uint_native y = special_values[j]; + uint_ct y_ct = special_uints[j]; + + // uint_native hits this error when trying to divide by zero: + // Stop reason: signal SIGFPE: integer divide by zero + uint_native expected_value; + uint_ct z_ct; + uint_native value; + if (y != 0) { + expected_value = x / y; + z_ct = x_ct / y_ct; + value = static_cast(z_ct.get_value()); + EXPECT_EQ(value, expected_value); + } + } + }; - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } - waffle::plonk_proof proof = prover.construct_proof(); + /** + * @brief Make sure we prevent proving v / v = 0 by setting the divison remainder to be v. + * TODO: This is lifted from the implementation. Should rewrite this test after introducing framework that separates + * circuit construction from witness generation. - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + */ + static void div_remainder_constraint() + { + Composer composer = Composer(); -TEST(stdlib_uint, test_xor) -{ - Composer composer = Composer(); - - const auto xor_integers = [&composer](bool lhs_constant = false, bool rhs_constant = false) { - uint_native a_val = get_random(); - uint_native b_val = get_random(); - uint_native const_a = get_random(); - uint_native const_b = get_random(); - uint_native expected = (a_val + const_a) ^ (b_val + const_b); - uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); - uint_ct b = rhs_constant ? uint_ct(&composer, b_val) : witness_ct(&composer, b_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct b_shift = uint_ct(&composer, const_b); - uint_ct c = a + a_shift; - uint_ct d = b + b_shift; - uint_ct e = c ^ d; - e = e.normalize(); - - uint_native result = uint_native(e.get_value()); + uint_native val = get_random(); - EXPECT_EQ(result, expected); - }; + uint_ct a = witness_ct(&composer, val); + uint_ct b = witness_ct(&composer, val); - xor_integers(false, false); - xor_integers(false, true); - xor_integers(true, false); - xor_integers(true, true); + const uint32_t dividend_idx = a.get_witness_index(); + const uint32_t divisor_idx = b.get_witness_index(); - auto prover = composer.create_prover(); + const uint256_t divisor = b.get_value(); - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + const uint256_t q = 0; + const uint256_t r = val; - waffle::plonk_proof proof = prover.construct_proof(); + const uint32_t quotient_idx = composer.add_variable(q); + const uint32_t remainder_idx = composer.add_variable(r); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + // In this example there are no additive constaints, so we just replace them by zero below. -TEST(stdlib_uint, test_or) -{ - Composer composer = Composer(); - - const auto or_integers = [&composer](bool lhs_constant = false, bool rhs_constant = false) { - uint_native a_val = get_random(); - uint_native b_val = get_random(); - uint_native const_a = get_random(); - uint_native const_b = get_random(); - uint_native expected = (a_val + const_a) | (b_val + const_b); - uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); - uint_ct b = rhs_constant ? uint_ct(&composer, b_val) : witness_ct(&composer, b_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct b_shift = uint_ct(&composer, const_b); - uint_ct c = a + a_shift; - uint_ct d = b + b_shift; - uint_ct e = c | d; - e = e.normalize(); - - uint_native result = uint_native(e.get_value()); + // constraint: qb + const_b q + 0 b - a + r - const_a == 0 + // i.e., a + const_a = q(b + const_b) + r + const waffle::mul_quad division_gate{ .a = quotient_idx, // q + .b = divisor_idx, // b + .c = dividend_idx, // a + .d = remainder_idx, // r + .mul_scaling = fr::one(), + .a_scaling = b.get_additive_constant(), + .b_scaling = fr::zero(), + .c_scaling = fr::neg_one(), + .d_scaling = fr::one(), + .const_scaling = -a.get_additive_constant() }; + composer.create_big_mul_gate(division_gate); - EXPECT_EQ(result, expected); - }; + // set delta = (b + const_b - r) - or_integers(false, false); - or_integers(false, false); - or_integers(false, false); - or_integers(false, false); - or_integers(false, false); - or_integers(false, true); - or_integers(true, false); - or_integers(true, true); + // constraint: b - r - delta + const_b == 0 + const uint256_t delta = divisor - r - 1; + const uint32_t delta_idx = composer.add_variable(delta); - auto prover = composer.create_prover(); + const waffle::add_triple delta_gate{ + .a = divisor_idx, // b + .b = remainder_idx, // r + .c = delta_idx, // d + .a_scaling = fr::one(), + .b_scaling = fr::neg_one(), + .c_scaling = fr::neg_one(), + .const_scaling = b.get_additive_constant(), + }; - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + composer.create_add_gate(delta_gate); - waffle::plonk_proof proof = prover.construct_proof(); + // validate delta is in the correct range + stdlib::field_t::from_witness_index(&composer, delta_idx) + .create_range_constraint(uint_native_width, + "delta range constraint fails in div_remainder_constraint test"); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + // normalize witness quotient and remainder + // minimal bit range for quotient: from 0 (in case a = b-1) to width (when b = 1). + uint_ct quotient(&composer); + composer.create_range_constraint( + quotient_idx, uint_native_width, "quotient range constraint fails in div_remainder_constraint test"); -TEST(stdlib_uint, test_not) -{ - Composer composer = Composer(); + // constrain remainder to lie in [0, 2^width-1] + uint_ct remainder(&composer); + composer.create_range_constraint( + remainder_idx, uint_native_width, "remainder range constraint fails in div_remainder_constraint test"); - const auto not_integers = [&composer](bool lhs_constant = false, bool = false) { - uint_native a_val = get_random(); - uint_native const_a = get_random(); - uint_native expected = ~(a_val + const_a); - uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct c = a + a_shift; - uint_ct e = ~c; - e = e.normalize(); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, false); + } - uint_native result = uint_native(e.get_value()); + static void test_and() + { + Composer composer = Composer(); - EXPECT_EQ(result, expected); - }; + const auto and_integers = [&composer](bool lhs_constant = false, bool rhs_constant = false) { + uint_native a_val = get_random(); + uint_native b_val = get_random(); + uint_native const_a = get_random(); + uint_native const_b = get_random(); + uint_native expected = (a_val + const_a) & (b_val + const_b); + uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); + uint_ct b = rhs_constant ? uint_ct(&composer, b_val) : witness_ct(&composer, b_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct b_shift = uint_ct(&composer, const_b); + uint_ct c = a + a_shift; + uint_ct d = b + b_shift; + uint_ct e = c & d; + e = e.normalize(); + + uint_native result = uint_native(e.get_value()); + + EXPECT_EQ(result, expected); + }; - not_integers(false, false); - not_integers(false, true); - not_integers(true, false); - not_integers(true, true); + and_integers(false, false); + and_integers(false, true); + and_integers(true, false); + and_integers(true, true); - auto prover = composer.create_prover(); + auto prover = composer.create_prover(); - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); + waffle::plonk_proof proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } -TEST(stdlib_uint, test_gt) -{ - Composer composer = Composer(); - const auto compare_integers = [&composer](bool force_equal = false, bool force_gt = false, bool force_lt = false) { - uint_native const_a = get_random(); - uint_native const_b = get_random(); - uint_native a_val = get_random(); - uint_native b_val = impose_comparison(const_a, const_b, a_val, force_equal, force_gt, force_lt); - - bool expected = static_cast(b_val + const_b) > static_cast(a_val + const_a); - uint_ct a = witness_ct(&composer, a_val); - uint_ct b = witness_ct(&composer, b_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct b_shift = uint_ct(&composer, const_b); - uint_ct c = a + a_shift; - uint_ct d = b + b_shift; - bool_ct e = d > c; - bool result = bool(e.get_value()); + static void test_xor() + { + Composer composer = Composer(); - EXPECT_EQ(result, expected); - }; + const auto xor_integers = [&composer](bool lhs_constant = false, bool rhs_constant = false) { + uint_native a_val = get_random(); + uint_native b_val = get_random(); + uint_native const_a = get_random(); + uint_native const_b = get_random(); + uint_native expected = (a_val + const_a) ^ (b_val + const_b); + uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); + uint_ct b = rhs_constant ? uint_ct(&composer, b_val) : witness_ct(&composer, b_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct b_shift = uint_ct(&composer, const_b); + uint_ct c = a + a_shift; + uint_ct d = b + b_shift; + uint_ct e = c ^ d; + e = e.normalize(); + + uint_native result = uint_native(e.get_value()); + + EXPECT_EQ(result, expected); + }; - compare_integers(false, false, false); // both are random - compare_integers(false, false, false); // '' - compare_integers(false, false, false); // '' - compare_integers(false, false, false); // '' - compare_integers(false, false, true); // b < a - compare_integers(false, true, false); // b > a - compare_integers(true, false, false); // b = a - compare_integers(false, true, false); // b > a - compare_integers(true, false, false); // b = a + xor_integers(false, false); + xor_integers(false, true); + xor_integers(true, false); + xor_integers(true, true); - auto prover = composer.create_prover(); + auto prover = composer.create_prover(); - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); + waffle::plonk_proof proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } -TEST(stdlib_uint, test_lt) -{ - Composer composer = Composer(); - - const auto compare_integers = [&composer](bool force_equal = false, bool force_gt = false, bool force_lt = false) { - uint_native const_a = get_random(); - uint_native const_b = get_random(); - uint_native a_val = get_random(); - uint_native b_val = impose_comparison(const_a, const_b, a_val, force_equal, force_gt, force_lt); - - bool expected = static_cast(b_val + const_b) < static_cast(a_val + const_a); - uint_ct a = witness_ct(&composer, a_val); - uint_ct b = witness_ct(&composer, b_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct b_shift = uint_ct(&composer, const_b); - uint_ct c = a + a_shift; - uint_ct d = b + b_shift; - bool_ct e = d < c; - bool result = bool(e.get_value()); + static void test_or() + { + Composer composer = Composer(); - EXPECT_EQ(result, expected); - }; + const auto or_integers = [&composer](bool lhs_constant = false, bool rhs_constant = false) { + uint_native a_val = get_random(); + uint_native b_val = get_random(); + uint_native const_a = get_random(); + uint_native const_b = get_random(); + uint_native expected = (a_val + const_a) | (b_val + const_b); + uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); + uint_ct b = rhs_constant ? uint_ct(&composer, b_val) : witness_ct(&composer, b_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct b_shift = uint_ct(&composer, const_b); + uint_ct c = a + a_shift; + uint_ct d = b + b_shift; + uint_ct e = c | d; + e = e.normalize(); + + uint_native result = uint_native(e.get_value()); + + EXPECT_EQ(result, expected); + }; - compare_integers(false, false, false); - compare_integers(false, false, false); - compare_integers(false, false, false); - compare_integers(false, false, false); - compare_integers(false, false, true); - compare_integers(false, true, false); - compare_integers(true, false, false); - compare_integers(false, false, true); - compare_integers(false, true, false); - compare_integers(true, false, false); + or_integers(false, false); + or_integers(false, false); + or_integers(false, false); + or_integers(false, false); + or_integers(false, false); + or_integers(false, true); + or_integers(true, false); + or_integers(true, true); - auto prover = composer.create_prover(); + auto prover = composer.create_prover(); - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); + waffle::plonk_proof proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } -TEST(stdlib_uint, test_gte) -{ - Composer composer = Composer(); - - const auto compare_integers = [&composer](bool force_equal = false, bool force_gt = false, bool force_lt = false) { - uint_native const_a = get_random(); - uint_native const_b = get_random(); - uint_native a_val = get_random(); - uint_native b_val = impose_comparison(const_a, const_b, a_val, force_equal, force_gt, force_lt); - - bool expected = static_cast(b_val + const_b) >= static_cast(a_val + const_a); - uint_ct a = witness_ct(&composer, a_val); - uint_ct b = witness_ct(&composer, b_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct b_shift = uint_ct(&composer, const_b); - uint_ct c = a + a_shift; - uint_ct d = b + b_shift; - bool_ct e = d >= c; - bool result = bool(e.get_value()); - EXPECT_EQ(result, expected); - }; + static void test_not() + { + Composer composer = Composer(); - compare_integers(false, false, false); - compare_integers(false, false, false); - compare_integers(false, false, false); - compare_integers(false, false, false); - compare_integers(false, false, true); - compare_integers(false, true, false); - compare_integers(true, false, false); - compare_integers(false, false, true); - compare_integers(false, true, false); - compare_integers(true, false, false); + const auto not_integers = [&composer](bool lhs_constant = false, bool = false) { + uint_native a_val = get_random(); + uint_native const_a = get_random(); + uint_native expected = ~(a_val + const_a); + uint_ct a = lhs_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct c = a + a_shift; + uint_ct e = ~c; + e = e.normalize(); - auto prover = composer.create_prover(); + uint_native result = uint_native(e.get_value()); - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + EXPECT_EQ(result, expected); + }; - waffle::plonk_proof proof = prover.construct_proof(); + not_integers(false, false); + not_integers(false, true); + not_integers(true, false); + not_integers(true, true); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + auto prover = composer.create_prover(); -TEST(stdlib_uint, test_lte) -{ - Composer composer = Composer(); - - const auto compare_integers = [&composer](bool force_equal = false, bool force_gt = false, bool force_lt = false) { - uint_native const_a = get_random(); - uint_native const_b = get_random(); - uint_native a_val = get_random(); - uint_native b_val = impose_comparison(const_a, const_b, a_val, force_equal, force_gt, force_lt); - - bool expected = static_cast(b_val + const_b) <= static_cast(a_val + const_a); - uint_ct a = witness_ct(&composer, a_val); - uint_ct b = witness_ct(&composer, b_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct b_shift = uint_ct(&composer, const_b); - uint_ct c = a + a_shift; - uint_ct d = b + b_shift; - bool_ct e = d <= c; - bool result = bool(e.get_value()); + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); - EXPECT_EQ(result, expected); - }; + waffle::plonk_proof proof = prover.construct_proof(); - compare_integers(false, false, false); - compare_integers(false, false, false); - compare_integers(false, false, false); - compare_integers(false, false, false); - compare_integers(false, false, true); - compare_integers(false, true, false); - compare_integers(true, false, false); - compare_integers(false, false, true); - compare_integers(false, true, false); - compare_integers(true, false, false); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } - auto prover = composer.create_prover(); + static void test_gt() + { + Composer composer = Composer(); + const auto compare_integers = + [&composer](bool force_equal = false, bool force_gt = false, bool force_lt = false) { + uint_native const_a = get_random(); + uint_native const_b = get_random(); + uint_native a_val = get_random(); + uint_native b_val = impose_comparison(const_a, const_b, a_val, force_equal, force_gt, force_lt); + + bool expected = static_cast(b_val + const_b) > static_cast(a_val + const_a); + uint_ct a = witness_ct(&composer, a_val); + uint_ct b = witness_ct(&composer, b_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct b_shift = uint_ct(&composer, const_b); + uint_ct c = a + a_shift; + uint_ct d = b + b_shift; + bool_ct e = d > c; + bool result = bool(e.get_value()); + + EXPECT_EQ(result, expected); + }; + + compare_integers(false, false, false); // both are random + compare_integers(false, false, false); // '' + compare_integers(false, false, false); // '' + compare_integers(false, false, false); // '' + compare_integers(false, false, true); // b < a + compare_integers(false, true, false); // b > a + compare_integers(true, false, false); // b = a + compare_integers(false, true, false); // b > a + compare_integers(true, false, false); // b = a - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + auto prover = composer.create_prover(); - waffle::plonk_proof proof = prover.construct_proof(); + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + waffle::plonk_proof proof = prover.construct_proof(); -TEST(stdlib_uint, test_equality_operator) -{ - Composer composer = Composer(); - - const auto compare_integers = [&composer](bool force_equal = false, bool force_gt = false, bool force_lt = false) { - uint_native const_a = get_random(); - uint_native const_b = get_random(); - uint_native a_val = get_random(); - uint_native b_val = impose_comparison(const_a, const_b, a_val, force_equal, force_gt, force_lt); - - bool expected = static_cast(b_val + const_b) == static_cast(a_val + const_a); - uint_ct a = witness_ct(&composer, a_val); - uint_ct b = witness_ct(&composer, b_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct b_shift = uint_ct(&composer, const_b); - uint_ct c = a + a_shift; - uint_ct d = b + b_shift; - bool_ct e = d == c; - bool result = bool(e.get_value()); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } - EXPECT_EQ(result, expected); - }; + static void test_lt() + { + Composer composer = Composer(); - compare_integers(false, false, false); - compare_integers(false, false, false); - compare_integers(false, false, false); - compare_integers(false, false, false); - compare_integers(false, false, true); - compare_integers(false, true, false); - compare_integers(true, false, false); - compare_integers(false, false, true); - compare_integers(false, true, false); - compare_integers(true, false, false); + const auto compare_integers = + [&composer](bool force_equal = false, bool force_gt = false, bool force_lt = false) { + uint_native const_a = get_random(); + uint_native const_b = get_random(); + uint_native a_val = get_random(); + uint_native b_val = impose_comparison(const_a, const_b, a_val, force_equal, force_gt, force_lt); + + bool expected = static_cast(b_val + const_b) < static_cast(a_val + const_a); + uint_ct a = witness_ct(&composer, a_val); + uint_ct b = witness_ct(&composer, b_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct b_shift = uint_ct(&composer, const_b); + uint_ct c = a + a_shift; + uint_ct d = b + b_shift; + bool_ct e = d < c; + bool result = bool(e.get_value()); + + EXPECT_EQ(result, expected); + }; + + compare_integers(false, false, false); + compare_integers(false, false, false); + compare_integers(false, false, false); + compare_integers(false, false, false); + compare_integers(false, false, true); + compare_integers(false, true, false); + compare_integers(true, false, false); + compare_integers(false, false, true); + compare_integers(false, true, false); + compare_integers(true, false, false); - auto prover = composer.create_prover(); + auto prover = composer.create_prover(); - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); + waffle::plonk_proof proof = prover.construct_proof(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } -TEST(stdlib_uint, test_not_equality_operator) -{ - Composer composer = Composer(); - - const auto compare_integers = [&composer](bool force_equal = false, bool force_gt = false, bool force_lt = false) { - uint_native const_a = get_random(); - uint_native const_b = get_random(); - uint_native a_val = get_random(); - uint_native b_val = impose_comparison(const_a, const_b, a_val, force_equal, force_gt, force_lt); - - bool expected = static_cast(b_val + const_b) != static_cast(a_val + const_a); - uint_ct a = witness_ct(&composer, a_val); - uint_ct b = witness_ct(&composer, b_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct b_shift = uint_ct(&composer, const_b); - uint_ct c = a + a_shift; - uint_ct d = b + b_shift; - bool_ct e = d != c; - bool result = bool(e.get_value()); + static void test_gte() + { + Composer composer = Composer(); - EXPECT_EQ(result, expected); - }; + const auto compare_integers = + [&composer](bool force_equal = false, bool force_gt = false, bool force_lt = false) { + uint_native const_a = get_random(); + uint_native const_b = get_random(); + uint_native a_val = get_random(); + uint_native b_val = impose_comparison(const_a, const_b, a_val, force_equal, force_gt, force_lt); + + bool expected = static_cast(b_val + const_b) >= static_cast(a_val + const_a); + uint_ct a = witness_ct(&composer, a_val); + uint_ct b = witness_ct(&composer, b_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct b_shift = uint_ct(&composer, const_b); + uint_ct c = a + a_shift; + uint_ct d = b + b_shift; + bool_ct e = d >= c; + bool result = bool(e.get_value()); + EXPECT_EQ(result, expected); + }; + + compare_integers(false, false, false); + compare_integers(false, false, false); + compare_integers(false, false, false); + compare_integers(false, false, false); + compare_integers(false, false, true); + compare_integers(false, true, false); + compare_integers(true, false, false); + compare_integers(false, false, true); + compare_integers(false, true, false); + compare_integers(true, false, false); - compare_integers(false, false, false); - compare_integers(false, false, false); - compare_integers(false, false, false); - compare_integers(false, false, false); - compare_integers(false, false, true); - compare_integers(false, true, false); - compare_integers(true, false, false); - compare_integers(false, false, true); - compare_integers(false, true, false); - compare_integers(true, false, false); + auto prover = composer.create_prover(); - auto prover = composer.create_prover(); + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + waffle::plonk_proof proof = prover.construct_proof(); - waffle::plonk_proof proof = prover.construct_proof(); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + static void test_lte() + { + Composer composer = Composer(); -TEST(stdlib_uint, test_logical_not) -{ - Composer composer = Composer(); - - const auto not_integer = [&composer](bool force_zero) { - uint_native const_a = get_random(); - uint_native a_val = force_zero ? 0 - const_a : get_random(); - bool expected = !static_cast(const_a + a_val); - uint_ct a = witness_ct(&composer, a_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct c = a + a_shift; - bool_ct e = !c; - bool result = bool(e.get_value()); + const auto compare_integers = + [&composer](bool force_equal = false, bool force_gt = false, bool force_lt = false) { + uint_native const_a = get_random(); + uint_native const_b = get_random(); + uint_native a_val = get_random(); + uint_native b_val = impose_comparison(const_a, const_b, a_val, force_equal, force_gt, force_lt); + + bool expected = static_cast(b_val + const_b) <= static_cast(a_val + const_a); + uint_ct a = witness_ct(&composer, a_val); + uint_ct b = witness_ct(&composer, b_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct b_shift = uint_ct(&composer, const_b); + uint_ct c = a + a_shift; + uint_ct d = b + b_shift; + bool_ct e = d <= c; + bool result = bool(e.get_value()); + + EXPECT_EQ(result, expected); + }; + + compare_integers(false, false, false); + compare_integers(false, false, false); + compare_integers(false, false, false); + compare_integers(false, false, false); + compare_integers(false, false, true); + compare_integers(false, true, false); + compare_integers(true, false, false); + compare_integers(false, false, true); + compare_integers(false, true, false); + compare_integers(true, false, false); - EXPECT_EQ(result, expected); - }; + auto prover = composer.create_prover(); - not_integer(true); - not_integer(true); - not_integer(false); - not_integer(false); + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); - auto prover = composer.create_prover(); + waffle::plonk_proof proof = prover.construct_proof(); - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } + + static void test_equality_operator() + { + Composer composer = Composer(); - waffle::plonk_proof proof = prover.construct_proof(); + const auto compare_integers = + [&composer](bool force_equal = false, bool force_gt = false, bool force_lt = false) { + uint_native const_a = get_random(); + uint_native const_b = get_random(); + uint_native a_val = get_random(); + uint_native b_val = impose_comparison(const_a, const_b, a_val, force_equal, force_gt, force_lt); + + bool expected = static_cast(b_val + const_b) == static_cast(a_val + const_a); + uint_ct a = witness_ct(&composer, a_val); + uint_ct b = witness_ct(&composer, b_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct b_shift = uint_ct(&composer, const_b); + uint_ct c = a + a_shift; + uint_ct d = b + b_shift; + bool_ct e = d == c; + bool result = bool(e.get_value()); + + EXPECT_EQ(result, expected); + }; + + compare_integers(false, false, false); + compare_integers(false, false, false); + compare_integers(false, false, false); + compare_integers(false, false, false); + compare_integers(false, false, true); + compare_integers(false, true, false); + compare_integers(true, false, false); + compare_integers(false, false, true); + compare_integers(false, true, false); + compare_integers(true, false, false); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + auto prover = composer.create_prover(); -TEST(stdlib_uint, test_right_shift) -{ - Composer composer = Composer(); - - const auto shift_integer = [&composer](const bool is_constant, const uint_native shift) { - uint_native const_a = get_random(); - uint_native a_val = get_random(); - uint_native expected = static_cast(a_val + const_a) >> shift; - uint_ct a = is_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct c = a + a_shift; - uint_ct d = c >> shift; - uint_native result = uint_native(d.get_value()); + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); - EXPECT_EQ(result, expected); - }; + waffle::plonk_proof proof = prover.construct_proof(); - for (uint_native i = 0; i < uint_native_width; ++i) { - shift_integer(false, i); - shift_integer(true, i); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); } - auto prover = composer.create_prover(); + static void test_not_equality_operator() + { + Composer composer = Composer(); - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + const auto compare_integers = + [&composer](bool force_equal = false, bool force_gt = false, bool force_lt = false) { + uint_native const_a = get_random(); + uint_native const_b = get_random(); + uint_native a_val = get_random(); + uint_native b_val = impose_comparison(const_a, const_b, a_val, force_equal, force_gt, force_lt); + + bool expected = static_cast(b_val + const_b) != static_cast(a_val + const_a); + uint_ct a = witness_ct(&composer, a_val); + uint_ct b = witness_ct(&composer, b_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct b_shift = uint_ct(&composer, const_b); + uint_ct c = a + a_shift; + uint_ct d = b + b_shift; + bool_ct e = d != c; + bool result = bool(e.get_value()); + + EXPECT_EQ(result, expected); + }; + + compare_integers(false, false, false); + compare_integers(false, false, false); + compare_integers(false, false, false); + compare_integers(false, false, false); + compare_integers(false, false, true); + compare_integers(false, true, false); + compare_integers(true, false, false); + compare_integers(false, false, true); + compare_integers(false, true, false); + compare_integers(true, false, false); - waffle::plonk_proof proof = prover.construct_proof(); + auto prover = composer.create_prover(); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); -TEST(stdlib_uint, test_left_shift) -{ - Composer composer = Composer(); - - const auto shift_integer = [&composer](const bool is_constant, const uint_native shift) { - uint_native const_a = get_random(); - uint_native a_val = get_random(); - uint_native expected = static_cast((a_val + const_a) << shift); - uint_ct a = is_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct c = a + a_shift; - uint_ct d = c << shift; - uint_native result = uint_native(d.get_value()); + waffle::plonk_proof proof = prover.construct_proof(); - EXPECT_EQ(result, expected); - }; + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } - for (uint_native i = 0; i < uint_native_width; ++i) { - shift_integer(true, i); - shift_integer(false, i); + static void test_logical_not() + { + Composer composer = Composer(); + + const auto not_integer = [&composer](bool force_zero) { + uint_native const_a = get_random(); + uint_native a_val = force_zero ? 0 - const_a : get_random(); + bool expected = !static_cast(const_a + a_val); + uint_ct a = witness_ct(&composer, a_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct c = a + a_shift; + bool_ct e = !c; + bool result = bool(e.get_value()); + + EXPECT_EQ(result, expected); + }; + + not_integer(true); + not_integer(true); + not_integer(false); + not_integer(false); + + auto prover = composer.create_prover(); + + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); + + waffle::plonk_proof proof = prover.construct_proof(); + + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); } - auto prover = composer.create_prover(); + static void test_right_shift() + { + Composer composer = Composer(); - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + const auto shift_integer = [&composer](const bool is_constant, const uint_native shift) { + uint_native const_a = get_random(); + uint_native a_val = get_random(); + uint_native expected = static_cast(a_val + const_a) >> shift; + uint_ct a = is_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct c = a + a_shift; + uint_ct d = c >> shift; + uint_native result = uint_native(d.get_value()); + + EXPECT_EQ(result, expected); + }; - waffle::plonk_proof proof = prover.construct_proof(); + for (uint_native i = 0; i < uint_native_width; ++i) { + shift_integer(false, i); + shift_integer(true, i); + } - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + auto prover = composer.create_prover(); -TEST(stdlib_uint, test_ror) -{ - Composer composer = Composer(); + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); + + waffle::plonk_proof proof = prover.construct_proof(); + + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } + + static void test_left_shift() + { + Composer composer = Composer(); - const auto ror_integer = [&composer](const bool is_constant, const uint_native rotation) { - const auto ror = [](const uint_native in, const uint_native rval) { - return rval ? (in >> rval) | (in << (uint_native_width - rval)) : in; + const auto shift_integer = [&composer](const bool is_constant, const uint_native shift) { + uint_native const_a = get_random(); + uint_native a_val = get_random(); + uint_native expected = static_cast((a_val + const_a) << shift); + uint_ct a = is_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct c = a + a_shift; + uint_ct d = c << shift; + uint_native result = uint_native(d.get_value()); + + EXPECT_EQ(result, expected); }; - uint_native const_a = get_random(); - uint_native a_val = get_random(); - uint_native expected = static_cast(ror(static_cast(const_a + a_val), rotation)); - uint_ct a = is_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct c = a + a_shift; - uint_ct d = c.ror(rotation); - uint_native result = uint_native(d.get_value()); + for (uint_native i = 0; i < uint_native_width; ++i) { + shift_integer(true, i); + shift_integer(false, i); + } - EXPECT_EQ(result, expected); - }; + auto prover = composer.create_prover(); + + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); + + waffle::plonk_proof proof = prover.construct_proof(); - for (uint_native i = 0; i < uint_native_width; ++i) { - ror_integer(true, i); - ror_integer(false, i); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); } - auto prover = composer.create_prover(); + static void test_ror() + { + Composer composer = Composer(); - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + const auto ror_integer = [&composer](const bool is_constant, const uint_native rotation) { + const auto ror = [](const uint_native in, const uint_native rval) { + return rval ? (in >> rval) | (in << (uint_native_width - rval)) : in; + }; + + uint_native const_a = get_random(); + uint_native a_val = get_random(); + uint_native expected = static_cast(ror(static_cast(const_a + a_val), rotation)); + uint_ct a = is_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct c = a + a_shift; + uint_ct d = c.ror(rotation); + uint_native result = uint_native(d.get_value()); + + EXPECT_EQ(result, expected); + }; - waffle::plonk_proof proof = prover.construct_proof(); + for (uint_native i = 0; i < uint_native_width; ++i) { + ror_integer(true, i); + ror_integer(false, i); + } - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + auto prover = composer.create_prover(); -TEST(stdlib_uint, test_rol) -{ - Composer composer = Composer(); + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); + + waffle::plonk_proof proof = prover.construct_proof(); + + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } - const auto rol_integer = [&composer](const bool is_constant, const uint_native rotation) { - const auto rol = [](const uint_native in, const uint_native rval) { - return rval ? (in << rval) | (in >> (uint_native_width - rval)) : in; + static void test_rol() + { + Composer composer = Composer(); + + const auto rol_integer = [&composer](const bool is_constant, const uint_native rotation) { + const auto rol = [](const uint_native in, const uint_native rval) { + return rval ? (in << rval) | (in >> (uint_native_width - rval)) : in; + }; + + uint_native const_a = get_random(); + uint_native a_val = get_random(); + uint_native expected = static_cast(rol(static_cast(const_a + a_val), rotation)); + uint_ct a = is_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct c = a + a_shift; + uint_ct d = c.rol(rotation); + uint_native result = uint_native(d.get_value()); + + EXPECT_EQ(result, expected); }; - uint_native const_a = get_random(); - uint_native a_val = get_random(); - uint_native expected = static_cast(rol(static_cast(const_a + a_val), rotation)); - uint_ct a = is_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct c = a + a_shift; - uint_ct d = c.rol(rotation); - uint_native result = uint_native(d.get_value()); + for (uint_native i = 0; i < uint_native_width; ++i) { + rol_integer(true, i); + rol_integer(false, i); + } - EXPECT_EQ(result, expected); - }; + auto prover = composer.create_prover(); + + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); - for (uint_native i = 0; i < uint_native_width; ++i) { - rol_integer(true, i); - rol_integer(false, i); + waffle::plonk_proof proof = prover.construct_proof(); + + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); } - auto prover = composer.create_prover(); + /** + * @brief Test the the function uint_ct::at used to extract bits. + */ + static void test_at() + { + Composer composer = Composer(); - printf("composer gates = %zu\n", composer.get_num_gates()); - auto verifier = composer.create_verifier(); + const auto bit_test = [&composer](const bool is_constant) { + // construct a sum of uint_ct's, where at least one is a constant, + // and validate its correctness bitwise + uint_native const_a = get_random(); + uint_native a_val = get_random(); + uint_native c_val = const_a + a_val; + uint_ct a = is_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); + uint_ct a_shift = uint_ct(&composer, const_a); + uint_ct c = a + a_shift; + for (size_t i = 0; i < uint_native_width; ++i) { + bool_ct result = c.at(i); + bool expected = (((c_val >> i) & 1UL) == 1UL) ? true : false; + EXPECT_EQ(result.get_value(), expected); + EXPECT_EQ(result.get_context(), c.get_context()); + } + }; - waffle::plonk_proof proof = prover.construct_proof(); + bit_test(false); + bit_test(true); - bool proof_result = verifier.verify_proof(proof); - EXPECT_EQ(proof_result, true); -} + auto prover = composer.create_prover(); -/** - * @brief Test the the function uint_ct::at used to extract bits. - */ -TEST(stdlib_uint, test_at) + printf("composer gates = %zu\n", composer.get_num_gates()); + auto verifier = composer.create_verifier(); + + waffle::plonk_proof proof = prover.construct_proof(); + + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); + } +}; + +typedef testing::Types ComposerTypes; + +TYPED_TEST_SUITE(stdlib_uint, ComposerTypes); + +TYPED_TEST(stdlib_uint, test_weak_normalize) { - Composer composer = Composer(); - - const auto bit_test = [&composer](const bool is_constant) { - // construct a sum of uint_ct's, where at least one is a constant, - // and validate its correctness bitwise - uint_native const_a = get_random(); - uint_native a_val = get_random(); - uint_native c_val = const_a + a_val; - uint_ct a = is_constant ? uint_ct(&composer, a_val) : witness_ct(&composer, a_val); - uint_ct a_shift = uint_ct(&composer, const_a); - uint_ct c = a + a_shift; - for (size_t i = 0; i < uint_native_width; ++i) { - bool_ct result = c.at(i); - bool expected = (((c_val >> i) & 1UL) == 1UL) ? true : false; - EXPECT_EQ(result.get_value(), expected); - EXPECT_EQ(result.get_context(), c.get_context()); - } - }; + TestFixture::test_weak_normalize(); +} +TYPED_TEST(stdlib_uint, test_byte_array_conversion) +{ + TestFixture::test_byte_array_conversion(); +} +TYPED_TEST(stdlib_uint, test_input_output_consistency) +{ + TestFixture::test_input_output_consistency(); +} +TYPED_TEST(stdlib_uint, test_create_from_wires) +{ + TestFixture::test_create_from_wires(); +} +TYPED_TEST(stdlib_uint, test_add_special) +{ + TestFixture::test_add_special(); +} +TYPED_TEST(stdlib_uint, test_sub_special) +{ + TestFixture::test_sub_special(); +} +TYPED_TEST(stdlib_uint, test_add_with_constants) +{ + TestFixture::test_add_with_constants(); +} +TYPED_TEST(stdlib_uint, test_mul_special) +{ + TestFixture::test_mul_special(); +} +TYPED_TEST(stdlib_uint, test_mul_big) +{ + TestFixture::test_mul_big(); +} +TYPED_TEST(stdlib_uint, test_xor_special) +{ + TestFixture::test_xor_special(); +} +TYPED_TEST(stdlib_uint, test_xor_constants) +{ + TestFixture::test_xor_constants(); +} +TYPED_TEST(stdlib_uint, test_xor_more_constants) +{ + TestFixture::test_xor_more_constants(); +} +TYPED_TEST(stdlib_uint, test_and_constants) +{ + TestFixture::test_and_constants(); +} +TYPED_TEST(stdlib_uint, test_and_special) +{ + TestFixture::test_and_special(); +} +TYPED_TEST(stdlib_uint, test_or_special) +{ + TestFixture::test_or_special(); +} +TYPED_TEST(stdlib_uint, test_gt_special) +{ + TestFixture::test_gt_special(); +} +TYPED_TEST(stdlib_uint, test_ror_special) +{ + TestFixture::test_ror_special(); +} +TYPED_TEST(stdlib_uint, test_hash_rounds) +{ + TestFixture::test_hash_rounds(); +} +// BELOW HERE ARE TESTS FORMERLY MARKED AS TURBO +TYPED_TEST(stdlib_uint, test_add) +{ + TestFixture::test_add(); +} +TYPED_TEST(stdlib_uint, test_sub) +{ + TestFixture::test_sub(); +} +TYPED_TEST(stdlib_uint, test_mul) +{ + TestFixture::test_mul(); +} +TYPED_TEST(stdlib_uint, test_divide) +{ + TestFixture::test_divide(); +} +TYPED_TEST(stdlib_uint, test_modulo) +{ + TestFixture::test_modulo(); +} +TYPED_TEST(stdlib_uint, test_divide_by_zero_fails) +{ + TestFixture::test_divide_by_zero_fails(); +} +TYPED_TEST(stdlib_uint, test_divide_special) +{ + TestFixture::test_divide_special(); +} +TYPED_TEST(stdlib_uint, div_remainder_constraint) +{ + TestFixture::div_remainder_constraint(); +} +TYPED_TEST(stdlib_uint, test_and) +{ + TestFixture::test_and(); +} +TYPED_TEST(stdlib_uint, test_xor) +{ + TestFixture::test_xor(); +} +TYPED_TEST(stdlib_uint, test_or) +{ + TestFixture::test_or(); +} +TYPED_TEST(stdlib_uint, test_not) +{ + TestFixture::test_not(); +} +TYPED_TEST(stdlib_uint, test_gt) +{ + TestFixture::test_gt(); +} +TYPED_TEST(stdlib_uint, test_lt) +{ + TestFixture::test_lt(); +} +TYPED_TEST(stdlib_uint, test_gte) +{ + TestFixture::test_gte(); +} +TYPED_TEST(stdlib_uint, test_lte) +{ + TestFixture::test_lte(); +} +TYPED_TEST(stdlib_uint, test_equality_operator) +{ + TestFixture::test_equality_operator(); +} +TYPED_TEST(stdlib_uint, test_not_equality_operator) +{ + TestFixture::test_not_equality_operator(); +} +TYPED_TEST(stdlib_uint, test_logical_not) +{ + TestFixture::test_logical_not(); +} +TYPED_TEST(stdlib_uint, test_right_shift) +{ + TestFixture::test_right_shift(); +} +TYPED_TEST(stdlib_uint, test_left_shift) +{ + TestFixture::test_left_shift(); +} +TYPED_TEST(stdlib_uint, test_ror) +{ + TestFixture::test_ror(); +} +TYPED_TEST(stdlib_uint, test_rol) +{ + TestFixture::test_rol(); +} +TYPED_TEST(stdlib_uint, test_at) +{ + TestFixture::test_at(); +} - bit_test(false); - bit_test(true); +// There was one plookup-specific test in the ./plookup/uint_plookup.test.cpp +TEST(stdlib_uint32, test_accumulators_plookup_uint32) +{ + using uint32_ct = plonk::stdlib::uint32; + using witness_ct = plonk::stdlib::witness_t; + + waffle::UltraComposer composer = waffle::UltraComposer(); + + uint32_t a_val = engine.get_random_uint32(); + uint32_t b_val = engine.get_random_uint32(); + uint32_ct a = witness_ct(&composer, a_val); + uint32_ct b = witness_ct(&composer, b_val); + a = a ^ b; + uint32_t val = a_val ^ b_val; + uint32_t MASK = (1U << uint32_ct::bits_per_limb) - 1; + const auto accumulators = a.get_accumulators(); + for (size_t i = 0; i < uint32_ct::num_accumulators(); ++i) { + const uint64_t result = uint256_t(composer.get_variable(accumulators[i])).data[0]; + const uint64_t expected = val & MASK; + val = val >> uint32_ct::bits_per_limb; + EXPECT_EQ(result, expected); + } + printf("calling preprocess\n"); auto prover = composer.create_prover(); printf("composer gates = %zu\n", composer.get_num_gates()); auto verifier = composer.create_verifier(); - waffle::plonk_proof proof = prover.construct_proof(); + auto proof = prover.construct_proof(); bool proof_result = verifier.verify_proof(proof); EXPECT_EQ(proof_result, true); } - } // namespace test_stdlib_uint diff --git a/cpp/src/aztec/stdlib/primitives/uint/uint_plookups.test.cpp b/cpp/src/aztec/stdlib/primitives/uint/uint_plookups.test.cpp deleted file mode 100644 index db6ede722d..0000000000 --- a/cpp/src/aztec/stdlib/primitives/uint/uint_plookups.test.cpp +++ /dev/null @@ -1,540 +0,0 @@ -// TODO NOT WORKING YET. NEEDS TO BE REFACTORED -// #include "../bool/bool.hpp" -// #include "uint.hpp" -// #include -// #include -// #include - -// using namespace barretenberg; -// using namespace plonk; - -// namespace { -// auto& engine = numeric::random::get_debug_engine(); -// } - -// namespace test_stdlib_uint32_plookups { -// typedef stdlib::bool_t bool_t; -// typedef stdlib::uint8 uint8; -// typedef stdlib::uint32 uint32; -// typedef stdlib::witness_t witness_t; - -// std::vector get_random_ints(size_t num) -// { -// std::vector result; -// for (size_t i = 0; i < num; ++i) { -// result.emplace_back(engine.get_random_uint32()); -// } -// return result; -// } - -// TEST(stdlib_uint32_plookups, test_create_from_wires) -// { -// waffle::PlookupComposer composer = waffle::PlookupComposer(); - -// uint8 a = uint8(&composer, -// std::vector{ -// bool_t(false), -// bool_t(false), -// bool_t(false), -// bool_t(false), -// bool_t(false), -// bool_t(false), -// bool_t(false), -// witness_t(&composer, true), -// }); - -// EXPECT_EQ(a.at(0).get_value(), false); -// EXPECT_EQ(a.at(7).get_value(), true); -// EXPECT_EQ(static_cast(a.get_value()), 128U); -// } - -// TEST(stdlib_uint32_plookups, test_add) -// { -// waffle::PlookupComposer composer = waffle::PlookupComposer(); - -// witness_t first_input(&composer, 1U); -// witness_t second_input(&composer, 0U); - -// uint32 a = first_input; -// uint32 b = second_input; -// uint32 c = a + b; -// for (size_t i = 0; i < 32; ++i) { -// b = a; -// a = c; -// c = a + b; -// } -// waffle::PlookupProver prover = composer.create_prover(); - -// waffle::PlookupVerifier verifier = composer.create_verifier(); - -// waffle::plonk_proof proof = prover.construct_proof(); - -// bool result = verifier.verify_proof(proof); -// EXPECT_EQ(result, true); -// } - -// TEST(stdlib_uint32_plookups, test_add_with_constants) -// { -// size_t n = 3; -// std::vector witnesses = get_random_ints(3 * n); -// uint32_t expected[8]; -// for (size_t i = 2; i < n; ++i) { -// expected[0] = witnesses[3 * i]; -// expected[1] = witnesses[3 * i + 1]; -// expected[2] = witnesses[3 * i + 2]; -// expected[3] = expected[0] + expected[1]; -// expected[4] = expected[1] + expected[0]; -// // expected[5] = expected[1] + expected[2]; -// // expected[6] = expected[3] + expected[4]; -// // expected[7] = expected[4] + expected[5]; -// } -// waffle::PlookupComposer composer = waffle::PlookupComposer(); -// uint32 result[8]; -// for (size_t i = 2; i < n; ++i) { -// result[0] = uint32(&composer, witnesses[3 * i]); -// result[1] = (witness_t(&composer, witnesses[3 * i + 1])); -// // result[2] = (witness_t(&composer, witnesses[3 * i + 2])); -// result[3] = result[0] + result[1]; -// result[4] = result[1] + result[0]; -// // result[5] = result[1] + result[2]; -// result[6] = result[3] + result[4]; -// // result[7] = result[4] + result[5]; -// } - -// // for (size_t i = 0; i < 8; ++i) { -// // EXPECT_EQ(get_value(result[i]), expected[i]); -// // } -// waffle::PlookupProver prover = composer.create_prover(); - -// waffle::PlookupVerifier verifier = composer.create_verifier(); - -// waffle::plonk_proof proof = prover.construct_proof(); - -// bool proof_valid = verifier.verify_proof(proof); -// EXPECT_EQ(proof_valid, true); -// } - -// TEST(stdlib_uint32_plookups, test_mul) -// { -// uint32_t a_expected = 1U; -// uint32_t b_expected = 2U; -// uint32_t c_expected = a_expected + b_expected; -// for (size_t i = 0; i < 100; ++i) { -// b_expected = a_expected; -// a_expected = c_expected; -// c_expected = a_expected * b_expected; -// } - -// waffle::PlookupComposer composer = waffle::PlookupComposer(); - -// witness_t first_input(&composer, 1U); -// witness_t second_input(&composer, 2U); - -// uint32 a = first_input; -// uint32 b = second_input; -// uint32 c = a + b; -// for (size_t i = 0; i < 100; ++i) { -// b = a; -// a = c; -// c = a * b; -// } -// uint32_t c_result = -// static_cast(composer.get_variable(c.get_witness_index()).from_montgomery_form().data[0]); -// EXPECT_EQ(c_result, c_expected); -// waffle::PlookupProver prover = composer.create_prover(); - -// waffle::PlookupVerifier verifier = composer.create_verifier(); - -// waffle::plonk_proof proof = prover.construct_proof(); - -// bool result = verifier.verify_proof(proof); -// EXPECT_EQ(result, true); -// } - -// TEST(stdlib_uint32_plookups, test_xor) -// { -// uint32_t a_expected = 0xa3b10422; -// uint32_t b_expected = 0xeac21343; -// uint32_t c_expected = a_expected ^ b_expected; -// for (size_t i = 0; i < 32; ++i) { -// b_expected = a_expected; -// a_expected = c_expected; -// c_expected = a_expected + b_expected; -// a_expected = c_expected ^ a_expected; -// } - -// waffle::PlookupComposer composer = waffle::PlookupComposer(); - -// witness_t first_input(&composer, 0xa3b10422); -// witness_t second_input(&composer, 0xeac21343); - -// uint32 a = first_input; -// uint32 b = second_input; -// uint32 c = a ^ b; -// for (size_t i = 0; i < 32; ++i) { -// b = a; -// a = c; -// c = a + b; -// a = c ^ a; -// } -// uint32_t a_result = -// static_cast(composer.get_variable(a.get_witness_index()).from_montgomery_form().data[0]); -// EXPECT_EQ(a_result, a_expected); -// waffle::PlookupProver prover = composer.create_prover(); - -// waffle::PlookupVerifier verifier = composer.create_verifier(); - -// waffle::plonk_proof proof = prover.construct_proof(); - -// bool result = verifier.verify_proof(proof); -// EXPECT_EQ(result, true); -// } - -// TEST(stdlib_uint32_plookups, test_xor_constants) -// { -// waffle::PlookupComposer composer = waffle::PlookupComposer(); - -// uint32_t a_expected = 0xa3b10422; -// uint32_t b_expected = 0xeac21343; -// uint32_t c_expected = a_expected ^ b_expected; - -// uint32 const_a(&composer, 0xa3b10422); -// uint32 const_b(&composer, 0xeac21343); -// uint32 c = const_a ^ const_b; -// c.get_witness_index(); - -// EXPECT_EQ(c.get_additive_constant(), uint256_t(c_expected)); -// } - -// TEST(stdlib_uint32_plookups, test_xor_more_constants) -// { -// uint32_t a_expected = 0xa3b10422; -// uint32_t b_expected = 0xeac21343; -// uint32_t c_expected = a_expected ^ b_expected; -// for (size_t i = 0; i < 1; ++i) { -// b_expected = a_expected; -// a_expected = c_expected; -// c_expected = (a_expected + b_expected) ^ (0xa3b10422 ^ 0xeac21343); -// } - -// waffle::PlookupComposer composer = waffle::PlookupComposer(); - -// witness_t first_input(&composer, 0xa3b10422); -// witness_t second_input(&composer, 0xeac21343); - -// uint32 a = first_input; -// uint32 b = second_input; -// uint32 c = a ^ b; -// for (size_t i = 0; i < 1; ++i) { -// uint32 const_a = 0xa3b10422; -// uint32 const_b = 0xeac21343; -// b = a; -// a = c; -// c = (a + b) ^ (const_a ^ const_b); -// } -// uint32_t c_witness_index = c.get_witness_index(); -// uint32_t c_result = static_cast(composer.get_variable(c_witness_index).from_montgomery_form().data[0]); -// EXPECT_EQ(c_result, c_expected); -// waffle::PlookupProver prover = composer.create_prover(); - -// waffle::PlookupVerifier verifier = composer.create_verifier(); - -// waffle::plonk_proof proof = prover.construct_proof(); - -// bool result = verifier.verify_proof(proof); -// EXPECT_EQ(result, true); -// } - -// TEST(stdlib_uint32_plookups, test_and_constants) -// { -// uint32_t a_expected = 0xa3b10422; -// uint32_t b_expected = 0xeac21343; -// uint32_t c_expected = a_expected & b_expected; -// for (size_t i = 0; i < 1; ++i) { -// b_expected = a_expected; -// a_expected = c_expected; -// c_expected = (~a_expected & 0xa3b10422) + (b_expected & 0xeac21343); -// // c_expected = (a_expected + b_expected) & (0xa3b10422 & 0xeac21343); -// } - -// waffle::PlookupComposer composer = waffle::PlookupComposer(); - -// witness_t first_input(&composer, 0xa3b10422); -// witness_t second_input(&composer, 0xeac21343); - -// uint32 a = first_input; -// uint32 b = second_input; -// uint32 c = a & b; -// for (size_t i = 0; i < 1; ++i) { -// uint32 const_a = 0xa3b10422; -// uint32 const_b = 0xeac21343; -// b = a; -// a = c; -// c = (~a & const_a) + (b & const_b); -// } -// uint32_t c_witness_index = c.get_witness_index(); -// uint32_t c_result = static_cast(composer.get_variable(c_witness_index).from_montgomery_form().data[0]); -// EXPECT_EQ(c_result, c_expected); -// waffle::PlookupProver prover = composer.create_prover(); - -// waffle::PlookupVerifier verifier = composer.create_verifier(); - -// waffle::plonk_proof proof = prover.construct_proof(); - -// bool result = verifier.verify_proof(proof); -// EXPECT_EQ(result, true); -// } - -// TEST(stdlib_uint32s, test_and) -// { -// uint32_t a_expected = 0xa3b10422; -// uint32_t b_expected = 0xeac21343; -// uint32_t c_expected = a_expected + b_expected; -// for (size_t i = 0; i < 32; ++i) { -// b_expected = a_expected; -// a_expected = c_expected; -// c_expected = a_expected + b_expected; -// a_expected = c_expected & a_expected; -// } - -// waffle::PlookupComposer composer = waffle::PlookupComposer(); - -// witness_t first_input(&composer, 0xa3b10422); -// witness_t second_input(&composer, 0xeac21343); - -// uint32 a = first_input; -// uint32 b = second_input; -// uint32 c = a + b; -// for (size_t i = 0; i < 32; ++i) { -// b = a; -// a = c; -// c = a + b; -// a = c & a; -// } -// uint32_t a_result = -// static_cast(composer.get_variable(a.get_witness_index()).from_montgomery_form().data[0]); -// EXPECT_EQ(a_result, a_expected); - -// waffle::PlookupProver prover = composer.create_prover(); - -// waffle::PlookupVerifier verifier = composer.create_verifier(); - -// waffle::plonk_proof proof = prover.construct_proof(); - -// bool result = verifier.verify_proof(proof); -// EXPECT_EQ(result, true); -// } - -// TEST(stdlib_uint32_plookups, test_or) -// { -// uint32_t a_expected = 0xa3b10422; -// uint32_t b_expected = 0xeac21343; -// uint32_t c_expected = a_expected ^ b_expected; -// for (size_t i = 0; i < 32; ++i) { -// b_expected = a_expected; -// a_expected = c_expected; -// c_expected = a_expected + b_expected; -// a_expected = c_expected | a_expected; -// } - -// waffle::PlookupComposer composer = waffle::PlookupComposer(); - -// witness_t first_input(&composer, 0xa3b10422); -// witness_t second_input(&composer, 0xeac21343); - -// uint32 a = first_input; -// uint32 b = second_input; -// uint32 c = a ^ b; -// for (size_t i = 0; i < 32; ++i) { -// b = a; -// a = c; -// c = a + b; -// a = c | a; -// } -// uint32_t a_result = -// static_cast(composer.get_variable(a.get_witness_index()).from_montgomery_form().data[0]); -// EXPECT_EQ(a_result, a_expected); - -// waffle::PlookupProver prover = composer.create_prover(); - -// waffle::PlookupVerifier verifier = composer.create_verifier(); - -// waffle::plonk_proof proof = prover.construct_proof(); - -// bool result = verifier.verify_proof(proof); -// EXPECT_EQ(result, true); -// } - -// uint32_t rotate(uint32_t value, size_t rotation) -// { -// return rotation ? (value >> rotation) + (value << (32 - rotation)) : value; -// } - -// TEST(stdlib_uint32_plookups, test_ror) -// { -// uint32_t a_expected = 0xa3b10422; -// uint32_t b_expected = 0xeac21343; -// uint32_t c_expected = a_expected ^ b_expected; -// for (size_t i = 0; i < 32; ++i) { -// b_expected = a_expected; -// a_expected = c_expected; -// c_expected = a_expected + b_expected; -// a_expected = rotate(c_expected, i % 31) + rotate(a_expected, (i + 1) % 31); -// } - -// waffle::PlookupComposer composer = waffle::PlookupComposer(); - -// witness_t first_input(&composer, 0xa3b10422); -// witness_t second_input(&composer, 0xeac21343); - -// uint32 a = first_input; -// uint32 b = second_input; -// uint32 c = a ^ b; -// for (size_t i = 0; i < 32; ++i) { -// b = a; -// a = c; -// c = a + b; -// a = c.ror(static_cast(i % 31)) + a.ror(static_cast((i + 1) % 31)); -// } -// uint32_t a_result = -// static_cast(composer.get_variable(a.get_witness_index()).from_montgomery_form().data[0]); -// EXPECT_EQ(a_result, a_expected); - -// waffle::PlookupProver prover = composer.create_prover(); - -// waffle::PlookupVerifier verifier = composer.create_verifier(); - -// waffle::plonk_proof proof = prover.construct_proof(); - -// bool result = verifier.verify_proof(proof); -// EXPECT_EQ(result, true); -// } - -// uint32_t k_constants[64]{ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, -// 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, -// 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, -// 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, -// 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, -// 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, -// 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, -// 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, -// 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, -// 0xc67178f2 }; -// uint32_t round_values[8]{ -// 0x01020304, 0x0a0b0c0d, 0x1a2b3e4d, 0x03951bd3, 0x0e0fa3fe, 0x01000000, 0x0f0eeea1, 0x12345678 -// }; - -// // subtraction -// // A - B -// // = 2^{32} - B + A -// // ...but only if constants are zero -// // can also do (2^{32} - B + A) + (2^{32} - B.const) -// // ...but what about multiplicative value? Um...erm... -// TEST(stdlib_uint32_plookups, test_hash_rounds) -// { -// uint32_t w_alt[64]; - -// for (size_t i = 0; i < 64; ++i) { -// w_alt[i] = static_cast(barretenberg::fr::random_element().data[0]); -// } -// uint32_t a_alt = round_values[0]; -// uint32_t b_alt = round_values[1]; -// uint32_t c_alt = round_values[2]; -// uint32_t d_alt = round_values[3]; -// uint32_t e_alt = round_values[4]; -// uint32_t f_alt = round_values[5]; -// uint32_t g_alt = round_values[6]; -// uint32_t h_alt = round_values[7]; -// for (size_t i = 0; i < 64; ++i) { -// uint32_t S1_alt = rotate(e_alt, 7) ^ rotate(e_alt, 11) ^ rotate(e_alt, 25); -// uint32_t ch_alt = (e_alt & f_alt) ^ ((~e_alt) & g_alt); -// uint32_t temp1_alt = h_alt + S1_alt + ch_alt + k_constants[i % 64] + w_alt[i]; - -// uint32_t S0_alt = rotate(a_alt, 2) ^ rotate(a_alt, 13) ^ rotate(a_alt, 22); -// uint32_t maj_alt = (a_alt & b_alt) ^ (a_alt & c_alt) ^ (b_alt & c_alt); -// uint32_t temp2_alt = S0_alt + maj_alt; - -// h_alt = g_alt; -// g_alt = f_alt; -// f_alt = e_alt; -// e_alt = d_alt + temp1_alt; -// d_alt = c_alt; -// c_alt = b_alt; -// b_alt = a_alt; -// a_alt = temp1_alt + temp2_alt; -// } -// waffle::PlookupComposer composer = waffle::PlookupComposer(); - -// std::vector w; -// std::vector k; -// for (size_t i = 0; i < 64; ++i) { -// w.emplace_back(uint32(witness_t(&composer, w_alt[i]))); -// k.emplace_back(uint32(&composer, k_constants[i % 64])); -// } -// uint32 a = witness_t(&composer, round_values[0]); -// uint32 b = witness_t(&composer, round_values[1]); -// uint32 c = witness_t(&composer, round_values[2]); -// uint32 d = witness_t(&composer, round_values[3]); -// uint32 e = witness_t(&composer, round_values[4]); -// uint32 f = witness_t(&composer, round_values[5]); -// uint32 g = witness_t(&composer, round_values[6]); -// uint32 h = witness_t(&composer, round_values[7]); -// for (size_t i = 0; i < 64; ++i) { -// uint32 S1 = e.ror(7U) ^ e.ror(11U) ^ e.ror(25U); -// uint32 ch = (e & f) + ((~e) & g); -// uint32 temp1 = h + S1 + ch + k[i] + w[i]; - -// uint32 S0 = a.ror(2U) ^ a.ror(13U) ^ a.ror(22U); -// uint32 T0 = (b & c); -// uint32 T1 = (b - T0) + (c - T0); -// uint32 T2 = a & T1; -// uint32 maj = T2 + T0; -// uint32 temp2 = S0 + maj; - -// h = g; -// g = f; -// f = e; -// e = d + temp1; -// d = c; -// c = b; -// b = a; -// a = temp1 + temp2; -// } - -// uint32_t a_result = -// static_cast(composer.get_variable(a.get_witness_index()).from_montgomery_form().data[0]); -// uint32_t b_result = -// static_cast(composer.get_variable(b.get_witness_index()).from_montgomery_form().data[0]); -// uint32_t c_result = -// static_cast(composer.get_variable(c.get_witness_index()).from_montgomery_form().data[0]); -// uint32_t d_result = -// static_cast(composer.get_variable(d.get_witness_index()).from_montgomery_form().data[0]); -// uint32_t e_result = -// static_cast(composer.get_variable(e.get_witness_index()).from_montgomery_form().data[0]); -// uint32_t f_result = -// static_cast(composer.get_variable(f.get_witness_index()).from_montgomery_form().data[0]); -// uint32_t g_result = -// static_cast(composer.get_variable(g.get_witness_index()).from_montgomery_form().data[0]); -// uint32_t h_result = -// static_cast(composer.get_variable(h.get_witness_index()).from_montgomery_form().data[0]); - -// EXPECT_EQ(a_result, a_alt); -// EXPECT_EQ(b_result, b_alt); -// EXPECT_EQ(c_result, c_alt); -// EXPECT_EQ(d_result, d_alt); -// EXPECT_EQ(e_result, e_alt); -// EXPECT_EQ(f_result, f_alt); -// EXPECT_EQ(g_result, g_alt); -// EXPECT_EQ(h_result, h_alt); - -// waffle::PlookupProver prover = composer.create_prover(); - -// waffle::PlookupVerifier verifier = composer.create_verifier(); - -// waffle::plonk_proof proof = prover.construct_proof(); - -// bool result = verifier.verify_proof(proof); -// EXPECT_EQ(result, true); -// } - -// } // namespace test_stdlib_uint32_plookups \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/recursion/CMakeLists.txt b/cpp/src/aztec/stdlib/recursion/CMakeLists.txt index f34825d429..0790762551 100644 --- a/cpp/src/aztec/stdlib/recursion/CMakeLists.txt +++ b/cpp/src/aztec/stdlib/recursion/CMakeLists.txt @@ -1 +1 @@ -barretenberg_module(stdlib_recursion ecc plonk stdlib_primitives stdlib_pedersen stdlib_blake2s) \ No newline at end of file +barretenberg_module(stdlib_recursion ecc plonk stdlib_primitives stdlib_pedersen stdlib_blake3s) \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/recursion/transcript/transcript.hpp b/cpp/src/aztec/stdlib/recursion/transcript/transcript.hpp index f86f28b1e5..938cb8ecd8 100644 --- a/cpp/src/aztec/stdlib/recursion/transcript/transcript.hpp +++ b/cpp/src/aztec/stdlib/recursion/transcript/transcript.hpp @@ -7,8 +7,9 @@ #include "../../primitives/curves/bn254.hpp" #include "../verification_key/verification_key.hpp" -#include "../../hash/blake2s/blake2s.hpp" +#include "../../hash/blake3s/blake3s.hpp" #include "../../hash/pedersen/pedersen.hpp" +#include "../../hash/pedersen/pedersen_plookup.hpp" #include "../../primitives/bigfield/bigfield.hpp" #include "../../primitives/biggroup/biggroup.hpp" #include "../../primitives/bool/bool.hpp" @@ -24,12 +25,11 @@ template class Transcript { using witness_pt = witness_t; using fq_pt = bigfield; using group_pt = element; - using pedersen = plonk::stdlib::pedersen; using Key = plonk::stdlib::recursion::verification_key>; Transcript(Composer* in_context, const transcript::Manifest input_manifest) : context(in_context) - , transcript_base(input_manifest, transcript::HashType::PedersenBlake2s, 16) + , transcript_base(input_manifest, transcript::HashType::PedersenBlake3s, 16) , current_challenge(in_context) {} @@ -37,7 +37,7 @@ template class Transcript { const std::vector& input_transcript, const transcript::Manifest input_manifest) : context(in_context) - , transcript_base(input_transcript, input_manifest, transcript::HashType::PedersenBlake2s, 16) + , transcript_base(input_transcript, input_manifest, transcript::HashType::PedersenBlake3s, 16) , current_challenge(in_context) /*, transcript_bytes(in_context) */ { @@ -102,8 +102,7 @@ template class Transcript { field_keys.push_back(element_name); field_values.push_back(element); } - // 0x28b96cad7dce47b8e727159ce2adbdd119a4435d6edcba6361209301bd53bd0f - // 0xb96cad7dce47b8e727159ce2adbdd10019a4435d6edcba6361209301bd53bd0f + void add_group_element(const std::string& element_name, const group_pt& element) { uint256_t x = element.x.get_value().lo; @@ -199,13 +198,13 @@ template class Transcript { split(working_element, compression_buffer, field_pt(current_challenge), byte_counter, 32); } for (auto manifest_element : get_manifest().get_round_manifest(current_round).elements) { - if (manifest_element.num_bytes == 32) { + if (manifest_element.num_bytes == 32 && manifest_element.name != "public_inputs") { split(working_element, compression_buffer, get_field_element(manifest_element.name), byte_counter, manifest_element.num_bytes); - } else if (manifest_element.num_bytes == 64) { + } else if (manifest_element.num_bytes == 64 && manifest_element.name != "public_inputs") { group_pt point = get_circuit_group_element(manifest_element.name); field_pt y_hi = @@ -228,7 +227,7 @@ template class Transcript { for (size_t i = 0; i < field_array.size(); ++i) { split(working_element, compression_buffer, field_array[i], byte_counter, 32); } - } else if (manifest_element.num_bytes < 32) { + } else if (manifest_element.num_bytes < 32 && manifest_element.name != "public_inputs") { split(working_element, compression_buffer, get_field_element(manifest_element.name), @@ -236,7 +235,6 @@ template class Transcript { manifest_element.num_bytes); } } - std::vector> round_challenges; if (byte_counter != 0) { @@ -247,10 +245,15 @@ template class Transcript { compression_buffer.push_back(working_element); } - field_pt T0 = pedersen::compress(compression_buffer); + field_pt T0; + if constexpr (Composer::type == waffle::ComposerType::PLOOKUP) { + T0 = stdlib::pedersen_plookup::compress(compression_buffer); + } else { + T0 = stdlib::pedersen::compress(compression_buffer); + } byte_array compressed_buffer(T0); - byte_array base_hash = blake2s(compressed_buffer); + byte_array base_hash = stdlib::blake3s(compressed_buffer); byte_array first(field_pt(0), 16); first.write(base_hash.slice(0, 16)); @@ -265,7 +268,7 @@ template class Transcript { for (size_t i = 2; i < num_challenges; i += 2) { byte_array rolling_buffer = base_hash; rolling_buffer.write(byte_array(field_pt(i / 2), 1)); - byte_array hash_output = blake2s(rolling_buffer); + byte_array hash_output = stdlib::blake3s(rolling_buffer); byte_array hi(field_pt(0), 16); hi.write(hash_output.slice(0, 16)); diff --git a/cpp/src/aztec/stdlib/recursion/transcript/transcript.test.cpp b/cpp/src/aztec/stdlib/recursion/transcript/transcript.test.cpp index 1d2d466395..249194d90f 100644 --- a/cpp/src/aztec/stdlib/recursion/transcript/transcript.test.cpp +++ b/cpp/src/aztec/stdlib/recursion/transcript/transcript.test.cpp @@ -5,10 +5,11 @@ #include #include -#include using namespace plonk; +// ULTRATODO: Add tests for other composers too (make tests modular?) + typedef stdlib::field_t field_t; typedef stdlib::bool_t bool_t; typedef stdlib::uint uint32; @@ -79,7 +80,7 @@ TestData get_test_data() transcript::Transcript get_test_base_transcript(const TestData& data) { transcript::Transcript transcript = - transcript::Transcript(create_manifest(data.num_public_inputs), transcript::HashType::PedersenBlake2s, 16); + transcript::Transcript(create_manifest(data.num_public_inputs), transcript::HashType::PedersenBlake3s, 16); transcript.add_element("circuit_size", { 1, 2, 3, 4 }); transcript.add_element("public_input_size", { static_cast(data.num_public_inputs >> 24), diff --git a/cpp/src/aztec/stdlib/recursion/verification_key/verification_key.hpp b/cpp/src/aztec/stdlib/recursion/verification_key/verification_key.hpp index 491c83837e..6cc17b8f80 100644 --- a/cpp/src/aztec/stdlib/recursion/verification_key/verification_key.hpp +++ b/cpp/src/aztec/stdlib/recursion/verification_key/verification_key.hpp @@ -1,6 +1,6 @@ #pragma once #include -#include +#include #include #include @@ -15,9 +15,13 @@ #include #include #include +#include #include "../../primitives/uint/uint.hpp" +#include "../../primitives/memory/rom_table.hpp" #include "../../hash/pedersen/pedersen.hpp" +#include "../../hash/pedersen/pedersen_plookup.hpp" +#include "../../primitives/curves/bn254.hpp" namespace plonk { namespace stdlib { @@ -52,21 +56,39 @@ template struct evaluation_domain { field_t compress() const { - field_t out = pedersen::compress({ - root, - domain, - generator, - }); - return out; + if constexpr (Composer::type == waffle::ComposerType::PLOOKUP) { + field_t out = pedersen_plookup::compress({ + root, + domain, + generator, + }); + return out; + } else { + field_t out = pedersen::compress({ + root, + domain, + generator, + }); + return out; + } } static barretenberg::fr compress_native(const barretenberg::evaluation_domain& input) { - barretenberg::fr out = crypto::pedersen::compress_native({ - input.root, - input.domain, - input.generator, - }); + barretenberg::fr out; + if constexpr (Composer::type == waffle::ComposerType::PLOOKUP) { + out = crypto::pedersen::lookup::compress_native({ + input.root, + input.domain, + input.generator, + }); + } else { + out = crypto::pedersen::compress_native({ + input.root, + input.domain, + input.generator, + }); + } return out; } @@ -79,28 +101,35 @@ template struct evaluation_domain { uint32 size; }; -// stdlib verification key -// converts a verification key into a standard library type, instantiating the key's parameters -// as circuit variables. This allows the recursive verifier to accept arbitrary verification keys, -// where the circuit being verified is not fixed as part of the recursive circuit +/** + * @brief Converts a 'native' verification key into a standard library type, instantiating the `input_key` parameter as + * circuit variables. This allows the recursive verifier to accept arbitrary verification keys, where the circuit being + * verified is not fixed as part of the recursive circuit. + */ template struct verification_key { using Composer = typename Curve::Composer; static std::shared_ptr from_witness(Composer* ctx, const std::shared_ptr& input_key) { std::shared_ptr key = std::make_shared(); + // Native data: + key->context = ctx; key->base_key = input_key; + key->reference_string = input_key->reference_string; + key->polynomial_manifest = input_key->polynomial_manifest; + + // Circuit types: key->n = witness_t(ctx, barretenberg::fr(input_key->n)); key->num_public_inputs = witness_t(ctx, input_key->num_public_inputs); key->domain = evaluation_domain::from_witness(ctx, input_key->domain); - key->reference_string = input_key->reference_string; + for (const auto& [tag, value] : input_key->constraint_selectors) { key->constraint_selectors.insert({ tag, Curve::g1_ct::from_witness(ctx, value) }); } + for (const auto& [tag, value] : input_key->permutation_selectors) { key->permutation_selectors.insert({ tag, Curve::g1_ct::from_witness(ctx, value) }); } - key->polynomial_manifest = input_key->polynomial_manifest; return key; } @@ -109,6 +138,7 @@ template struct verification_key { const std::shared_ptr& input_key) { std::shared_ptr key = std::make_shared(); + key->context = ctx; key->base_key = input_key; key->n = field_t(ctx, input_key->n); key->num_public_inputs = field_t(ctx, input_key->num_public_inputs); @@ -131,13 +161,36 @@ template struct verification_key { void validate_key_is_in_set(const std::vector>& keys_in_set) { const auto circuit_key_compressed = compress(); - bool_t is_valid(false); - for (const auto& key : keys_in_set) { - barretenberg::fr compressed = compress_native(key); - is_valid = is_valid || (circuit_key_compressed == compressed); + bool found = false; + // if we're using Plookup, use a ROM table to index the keys + if constexpr (Composer::type == waffle::ComposerType::PLOOKUP) { + field_t key_index(witness_t(context, 0)); + std::vector> compressed_keys; + for (size_t i = 0; i < keys_in_set.size(); ++i) { + barretenberg::fr compressed = compress_native(keys_in_set[i]); + compressed_keys.emplace_back(compressed); + if (compressed == circuit_key_compressed.get_value()) { + key_index = witness_t(context, i); + found = true; + } + } + if (!found) { + context->failure( + "verification_key::validate_key_is_in_set failed - input key is not in the provided set!"); + } + rom_table key_table(compressed_keys); + + const auto output_key = key_table[key_index]; + output_key.assert_equal(circuit_key_compressed); + } else { + bool_t is_valid(false); + for (const auto& key : keys_in_set) { + barretenberg::fr compressed = compress_native(key); + is_valid = is_valid || (circuit_key_compressed == compressed); + } + + is_valid.assert_equal(true); } - - is_valid.assert_equal(true); } private: @@ -169,8 +222,12 @@ template struct verification_key { key_witnesses.push_back(selector.y.binary_basis_limbs[3].element); } - field_t compressed_key = pedersen::compress(key_witnesses); - + field_t compressed_key; + if constexpr (Composer::type == waffle::ComposerType::PLOOKUP) { + compressed_key = pedersen_plookup::compress(key_witnesses); + } else { + compressed_key = pedersen::compress(key_witnesses); + } return compressed_key; } @@ -178,7 +235,7 @@ template struct verification_key { { barretenberg::fr compressed_domain = evaluation_domain::compress_native(key->domain); - constexpr size_t num_limb_bits = 68; // TODO GET FROM BIGFIELD + constexpr size_t num_limb_bits = bn254::fq_ct::NUM_LIMB_BITS; const auto split_bigfield_limbs = [](const uint256_t& element) { std::vector limbs; limbs.push_back(element.slice(0, num_limb_bits)); @@ -219,27 +276,36 @@ template struct verification_key { key_witnesses.push_back(y_limbs[2]); key_witnesses.push_back(y_limbs[3]); } - barretenberg::fr compressed_key = crypto::pedersen::compress_native(key_witnesses); + barretenberg::fr compressed_key; + if constexpr (Composer::type == waffle::ComposerType::PLOOKUP) { + compressed_key = crypto::pedersen::lookup::compress_native(key_witnesses); + } else { + compressed_key = crypto::pedersen::compress_native(key_witnesses); + } return compressed_key; } public: + // Circuit Types: field_t n; field_t num_public_inputs; field_t z_pow_n; evaluation_domain domain; - std::shared_ptr reference_string; - std::map constraint_selectors; std::map permutation_selectors; + // Native data: + + std::shared_ptr reference_string; + waffle::PolynomialManifest polynomial_manifest; size_t program_width = 4; std::shared_ptr base_key; + Composer* context; }; } // namespace recursion diff --git a/cpp/src/aztec/stdlib/recursion/verifier/program_settings.hpp b/cpp/src/aztec/stdlib/recursion/verifier/program_settings.hpp index b2d7aef433..c547f81d06 100644 --- a/cpp/src/aztec/stdlib/recursion/verifier/program_settings.hpp +++ b/cpp/src/aztec/stdlib/recursion/verifier/program_settings.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include "../transcript/transcript.hpp" @@ -9,22 +8,131 @@ namespace plonk { namespace stdlib { namespace recursion { +template class recursive_ultra_verifier_settings : public waffle::unrolled_ultra_verifier_settings { + public: + typedef typename Curve::fr_ct fr_ct; + typedef typename Curve::g1::affine_element g1; + typedef typename Curve::Composer Composer; + typedef plonk::stdlib::recursion::Transcript Transcript_pt; + typedef waffle::VerifierPermutationWidget PermutationWidget; + typedef waffle::VerifierPlookupWidget PlookupWidget; + + typedef waffle::unrolled_ultra_settings base_settings; + + typedef waffle::VerifierUltraFixedBaseWidget UltraFixedBaseWidget; + typedef waffle::VerifierPlookupArithmeticWidget PlookupArithmeticWidget; + typedef waffle::VerifierTurboLogicWidget TurboLogicWidget; + typedef waffle::VerifierGenPermSortWidget GenPermSortWidget; + typedef waffle::VerifierEllipticWidget EllipticWidget; + typedef waffle::VerifierPlookupAuxiliaryWidget PlookupAuxiliaryWidget; + + static constexpr size_t num_challenge_bytes = 16; + static constexpr transcript::HashType hash_type = transcript::HashType::PlookupPedersenBlake3s; + static constexpr bool use_linearisation = + false; // We don't compute a linearisation polynomial when verifying within a circuit. + // idpolys is a flag that describes whether we're using Vitalik's trick of using trivial identity permutation + // polynomials (id_poly = false); OR whether the identity permutation polynomials are circuit-specific and stored in + // the proving/verification key (id_poly = true). + static constexpr bool idpolys = true; + + static fr_ct append_scalar_multiplication_inputs(typename Transcript_pt::Key* key, + const fr_ct& alpha_base, + const Transcript_pt& transcript, + std::map& scalars) + { + auto updated_alpha = PermutationWidget::append_scalar_multiplication_inputs( + key, alpha_base, transcript, scalars, use_linearisation, idpolys); + + updated_alpha = PlookupWidget::append_scalar_multiplication_inputs( + key, updated_alpha, transcript, scalars, use_linearisation); + + updated_alpha = + PlookupArithmeticWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); + + updated_alpha = + UltraFixedBaseWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); + + updated_alpha = GenPermSortWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); + + updated_alpha = EllipticWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); + + updated_alpha = + PlookupAuxiliaryWidget::append_scalar_multiplication_inputs(key, updated_alpha, transcript, scalars); + + return updated_alpha; + } + + static fr_ct compute_quotient_evaluation_contribution(typename Transcript_pt::Key* key, + const fr_ct& alpha_base, + const Transcript_pt& transcript, + fr_ct& r_0) + { + auto updated_alpha_base = PermutationWidget::compute_quotient_evaluation_contribution( + key, alpha_base, transcript, r_0, use_linearisation, idpolys); + + updated_alpha_base = PlookupWidget::compute_quotient_evaluation_contribution( + key, updated_alpha_base, transcript, r_0, use_linearisation); + + updated_alpha_base = + PlookupArithmeticWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); + + updated_alpha_base = + UltraFixedBaseWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); + + updated_alpha_base = + GenPermSortWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); + + updated_alpha_base = + EllipticWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); + + updated_alpha_base = + PlookupAuxiliaryWidget::compute_quotient_evaluation_contribution(key, updated_alpha_base, transcript, r_0); + + return updated_alpha_base; + } +}; + +// Only needed because ultra-to-standard recursion requires us to use a Pedersen hash which is common to both Ultra & +// Standard plonk i.e. the non-ultra version. +template +class recursive_ultra_to_standard_verifier_settings : public recursive_ultra_verifier_settings { + public: + typedef typename Curve::fr_ct fr_ct; + typedef typename Curve::g1::affine_element g1; + typedef typename Curve::Composer Composer; + typedef plonk::stdlib::recursion::Transcript Transcript_pt; + typedef waffle::VerifierPermutationWidget PermutationWidget; + typedef waffle::VerifierPlookupWidget PlookupWidget; + + typedef waffle::unrolled_ultra_to_standard_settings base_settings; + + typedef waffle::VerifierUltraFixedBaseWidget UltraFixedBaseWidget; + typedef waffle::VerifierPlookupArithmeticWidget PlookupArithmeticWidget; + typedef waffle::VerifierTurboLogicWidget TurboLogicWidget; + typedef waffle::VerifierGenPermSortWidget GenPermSortWidget; + typedef waffle::VerifierEllipticWidget EllipticWidget; + typedef waffle::VerifierPlookupAuxiliaryWidget PlookupAuxiliaryWidget; + + static constexpr transcript::HashType hash_type = transcript::HashType::PedersenBlake3s; +}; + template class recursive_turbo_verifier_settings : public waffle::unrolled_turbo_settings { public: typedef typename Curve::fr_ct fr_ct; - typedef typename Curve::g1_base_t::affine_element g1_base_t; + typedef typename Curve::g1::affine_element g1; typedef typename Curve::Composer Composer; typedef Transcript Transcript_pt; - typedef waffle::VerifierPermutationWidget PermutationWidget; + typedef waffle::VerifierPermutationWidget PermutationWidget; + typedef waffle::unrolled_turbo_settings base_settings; - typedef waffle::VerifierTurboFixedBaseWidget TurboFixedBaseWidget; - typedef waffle::VerifierTurboArithmeticWidget TurboArithmeticWidget; - typedef waffle::VerifierTurboRangeWidget TurboRangeWidget; - typedef waffle::VerifierTurboLogicWidget TurboLogicWidget; + typedef waffle::VerifierTurboFixedBaseWidget TurboFixedBaseWidget; + typedef waffle::VerifierTurboArithmeticWidget TurboArithmeticWidget; + typedef waffle::VerifierTurboRangeWidget TurboRangeWidget; + typedef waffle::VerifierTurboLogicWidget TurboLogicWidget; static constexpr size_t num_challenge_bytes = 16; - static constexpr transcript::HashType hash_type = transcript::HashType::PedersenBlake2s; + static constexpr transcript::HashType hash_type = transcript::HashType::PedersenBlake3s; static constexpr bool use_linearisation = false; static fr_ct append_scalar_multiplication_inputs(typename Transcript_pt::Key* key, @@ -67,6 +175,7 @@ template class recursive_turbo_verifier_settings : public waffl return updated_alpha_base; } }; + } // namespace recursion } // namespace stdlib } // namespace plonk diff --git a/cpp/src/aztec/stdlib/recursion/verifier/verifier.hpp b/cpp/src/aztec/stdlib/recursion/verifier/verifier.hpp index d01f6b6b29..f3ebb12e8d 100644 --- a/cpp/src/aztec/stdlib/recursion/verifier/verifier.hpp +++ b/cpp/src/aztec/stdlib/recursion/verifier/verifier.hpp @@ -4,7 +4,6 @@ #include "../../primitives/biggroup/biggroup.hpp" #include "../../primitives/bool/bool.hpp" #include "../../primitives/field/field.hpp" -#include "../../primitives/field/pow.hpp" #include "../verification_key/verification_key.hpp" #include "../transcript/transcript.hpp" @@ -54,7 +53,8 @@ void populate_kate_element_map(typename Curve::Composer* ctx, std::map& kate_g1_elements, std::map& kate_fr_elements_at_zeta, std::map& kate_fr_elements_at_zeta_large, - std::map& kate_fr_elements_at_zeta_omega) + std::map& kate_fr_elements_at_zeta_omega, + typename Curve::fr_ct& batch_opening_scalar) { using fr_ct = typename Curve::fr_ct; using g1_ct = typename Curve::g1_ct; @@ -67,6 +67,10 @@ void populate_kate_element_map(typename Curve::Composer* ctx, case waffle::PolynomialSource::WITNESS: { const auto element = transcript.get_group_element(label); ASSERT(element.on_curve()); + if (element.is_point_at_infinity()) { + std::cerr << label << " witness is point at infinity! Error!" << std::endl; + ctx->failure("witness " + label + " is point at infinity"); + } // g1_ct::from_witness validates that the point produced lies on the curve kate_g1_elements.insert({ label, g1_ct::from_witness(ctx, element) }); break; @@ -76,7 +80,11 @@ void populate_kate_element_map(typename Curve::Composer* ctx, // TODO: with user-defined circuits, we will need verify that the point // lies on the curve with constraints if (!element.get_value().on_curve()) { - std::cerr << "c selector not on curve!" << std::endl; + std::cerr << label << " constraint selector not on curve!" << std::endl; + } + if (element.get_value().is_point_at_infinity()) { + std::cerr << label << " constraint selector is point at infinity! Error!" << std::endl; + ctx->failure("constraint selector " + label + " is point at infinity"); } kate_g1_elements.insert({ label, element }); break; @@ -86,7 +94,11 @@ void populate_kate_element_map(typename Curve::Composer* ctx, // TODO: with user-defined circuits, we will need verify that the point // lies on the curve with constraints if (!element.get_value().on_curve()) { - std::cerr << "p selector not on curve!" << std::endl; + std::cerr << label << " permutation selector not on curve!" << std::endl; + } + if (element.get_value().is_point_at_infinity()) { + std::cerr << label << " permutation selector is point at infinity! Error!" << std::endl; + ctx->failure("permutation selector " + label + " is point at infinity"); } kate_g1_elements.insert({ label, element }); break; @@ -121,8 +133,7 @@ void populate_kate_element_map(typename Curve::Composer* ctx, fr_ct batch_evaluation = waffle::compute_kate_batch_evaluation(key, transcript); - kate_g1_elements.insert({ "BATCH_EVALUATION", g1::affine_one }); - kate_fr_elements_at_zeta_large.insert({ "BATCH_EVALUATION", -batch_evaluation }); + batch_opening_scalar = -batch_evaluation; kate_g1_elements.insert({ "PI_Z_OMEGA", g1_ct::from_witness(ctx, PI_Z_OMEGA) }); kate_fr_elements_at_zeta_large.insert({ "PI_Z_OMEGA", zeta * key->domain.root * u }); @@ -149,7 +160,7 @@ lagrange_evaluations get_lagrange_evaluations( typedef typename Curve::fr_ct fr_ct; typedef typename Curve::Composer Composer; - fr_ct z_pow = pow(z, domain.size); + fr_ct z_pow = z.pow(field_t(domain.size)); fr_ct numerator = z_pow - fr_ct(1); // compute modified vanishing polynomial Z_H*(z) @@ -191,6 +202,10 @@ lagrange_evaluations get_lagrange_evaluations( return result; } +/** + * Refer to src/aztec/plonk/proof_system/verifier/verifier.cpp verify_proof() for the native implementation, which + * includes detailed comments. + */ template recursion_output verify_proof(typename Curve::Composer* context, std::shared_ptr> key, @@ -229,16 +244,17 @@ recursion_output verify_proof(typename Curve::Composer* context, fr_ct alpha = transcript.get_challenge_field_element("alpha"); fr_ct zeta = transcript.get_challenge_field_element("z"); - key->z_pow_n = pow(zeta, key->n); + key->z_pow_n = zeta.pow(key->domain.domain); + lagrange_evaluations lagrange_evals = get_lagrange_evaluations(zeta, key->domain); // reconstruct evaluation of quotient polynomial from prover messages - fr_ct T0; fr_ct r_0 = fr_ct(0); - fr_ct alpha_base = alpha; + program_settings::compute_quotient_evaluation_contribution(key.get(), alpha, transcript, r_0); - alpha_base = program_settings::compute_quotient_evaluation_contribution(key.get(), alpha_base, transcript, r_0); + // We want to include t_eval in the transcript only when use_linearisation = false. This is always the case when + // verifying within a circuit. fr_ct t_eval = r_0 / lagrange_evals.vanishing_poly; transcript.add_field_element("t", t_eval); @@ -247,13 +263,16 @@ recursion_output verify_proof(typename Curve::Composer* context, fr_ct u = transcript.get_challenge_field_element("separator", 0); + fr_ct batch_opening_scalar; populate_kate_element_map, program_settings>(context, key.get(), transcript, kate_g1_elements, kate_fr_elements_at_zeta, kate_fr_elements_at_zeta_large, - kate_fr_elements_at_zeta_omega); + kate_fr_elements_at_zeta_omega, + batch_opening_scalar); + std::vector double_opening_scalars; std::vector double_opening_elements; std::vector opening_scalars; @@ -261,6 +280,7 @@ recursion_output verify_proof(typename Curve::Composer* context, std::vector big_opening_scalars; std::vector big_opening_elements; std::vector elements_to_add; + for (const auto& [label, fr_value] : kate_fr_elements_at_zeta) { const auto& g1_value = kate_g1_elements[label]; if (fr_value.get_value() == 0 && fr_value.witness_index != IS_CONSTANT) { @@ -314,6 +334,7 @@ recursion_output verify_proof(typename Curve::Composer* context, double_opening_scalars.emplace_back(fr_value); double_opening_elements.emplace_back(g1_value); } + const auto double_opening_result = g1_ct::batch_mul(double_opening_elements, double_opening_scalars, 128); opening_elements.emplace_back(double_opening_result); @@ -331,7 +352,8 @@ recursion_output verify_proof(typename Curve::Composer* context, opening_elements.push_back(previous_output.P0); opening_scalars.push_back(random_separator); - rhs_elements.push_back((-(previous_output.P1)).normalize()); + rhs_elements.push_back( + (-(previous_output.P1)).reduce()); // TODO: use .normalize() instead? (As per defi bridge project) rhs_scalars.push_back(random_separator); } @@ -378,15 +400,17 @@ recursion_output verify_proof(typename Curve::Composer* context, rhs_elements.push_back((-g1_ct(x1, y1)).normalize()); rhs_scalars.push_back(recursion_separator_challenge); } - auto opening_result = - g1_ct::bn254_endo_batch_mul(big_opening_elements, big_opening_scalars, opening_elements, opening_scalars, 128); + + auto opening_result = g1_ct::template bn254_endo_batch_mul_with_generator( + big_opening_elements, big_opening_scalars, opening_elements, opening_scalars, batch_opening_scalar, 128); + opening_result = opening_result + double_opening_result; for (const auto& to_add : elements_to_add) { opening_result = opening_result + to_add; } opening_result = opening_result.normalize(); - g1_ct rhs = g1_ct::batch_mul(rhs_elements, rhs_scalars, 128); + g1_ct rhs = g1_ct::template wnaf_batch_mul<128>(rhs_elements, rhs_scalars); rhs = rhs + PI_Z; rhs = (-rhs).normalize(); @@ -408,6 +432,7 @@ recursion_output verify_proof(typename Curve::Composer* context, rhs.y.binary_basis_limbs[2].element.normalize().witness_index, rhs.y.binary_basis_limbs[3].element.normalize().witness_index, }; + return recursion_output{ opening_result, rhs, transcript.get_field_element_vector("public_inputs"), proof_witness_indices, true, }; diff --git a/cpp/src/aztec/stdlib/recursion/verifier/verifier.test.cpp b/cpp/src/aztec/stdlib/recursion/verifier/verifier.test.cpp index cf0172b8be..9a84cf727f 100644 --- a/cpp/src/aztec/stdlib/recursion/verifier/verifier.test.cpp +++ b/cpp/src/aztec/stdlib/recursion/verifier/verifier.test.cpp @@ -5,19 +5,21 @@ #include #include #include -#include "../../hash/blake2s/blake2s.hpp" +#include "../../hash/blake3s/blake3s.hpp" #include "../../hash/pedersen/pedersen.hpp" #include "program_settings.hpp" using namespace plonk; template class stdlib_verifier : public testing::Test { - using InnerComposer = waffle::TurboComposer; + using InnerComposer = waffle::UltraComposer; typedef stdlib::bn254 inner_curve; typedef stdlib::bn254 outer_curve; typedef plonk::stdlib::recursion::verification_key verification_key_pt; - typedef plonk::stdlib::recursion::recursive_turbo_verifier_settings recursive_settings; + typedef plonk::stdlib::recursion::recursive_ultra_verifier_settings recursive_settings; + typedef plonk::stdlib::recursion::recursive_ultra_to_standard_verifier_settings + ultra_to_standard_recursive_settings; typedef inner_curve::fr_ct fr_ct; typedef inner_curve::public_witness_ct public_witness_ct; typedef inner_curve::witness_ct witness_ct; @@ -37,9 +39,9 @@ template class stdlib_verifier : public testing::Test { a = (a * b) + b + a; a = a.madd(b, c); } - plonk::stdlib::pedersen::compress(a, b); + plonk::stdlib::pedersen::compress(a, b); typename inner_curve::byte_array_ct to_hash(&composer, "nonsense test data"); - stdlib::blake2s(to_hash); + stdlib::blake3s(to_hash); barretenberg::fr bigfield_data = fr::random_element(); barretenberg::fr bigfield_data_a{ bigfield_data.data[0], bigfield_data.data[1], 0, 0 }; @@ -49,21 +51,120 @@ template class stdlib_verifier : public testing::Test { fr_ct(witness_ct(&composer, 0))); typename inner_curve::fq_ct big_b(fr_ct(witness_ct(&composer, bigfield_data_b.to_montgomery_form())), fr_ct(witness_ct(&composer, 0))); + big_a* big_b; }; + /** + * Test is included because UltraComposer used to fail for circuits which didn't lookup any tables. + */ + static void create_inner_circuit_no_tables(InnerComposer& composer, + const std::vector& public_inputs) + { + // A nice Pythagorean triples circuit example: "I know a & b s.t. a^2 + b^2 = c^2". + fr_ct a(witness_ct(&composer, public_inputs[0])); + fr_ct b(witness_ct(&composer, public_inputs[1])); + fr_ct c(witness_ct(&composer, public_inputs[2])); + + auto a_sq = a * a; + auto b_sq = b * b; + auto c_sq = c * c; + + (c_sq).assert_equal(a_sq + b_sq); + + c_sq.set_public(); + }; + + static void create_alternate_inner_circuit(InnerComposer& composer, + const std::vector& public_inputs) + { + fr_ct a(public_witness_ct(&composer, public_inputs[0])); + fr_ct b(public_witness_ct(&composer, public_inputs[1])); + fr_ct c(public_witness_ct(&composer, public_inputs[2])); + + for (size_t i = 0; i < 32; ++i) { + a = (a * b) + b + a; + a = c.madd(b, a); + } + plonk::stdlib::pedersen::compress(a, a); + inner_curve::byte_array_ct to_hash(&composer, "different nonsense test data"); + stdlib::blake3s(to_hash); + + barretenberg::fr bigfield_data = fr::random_element(); + barretenberg::fr bigfield_data_a{ bigfield_data.data[0], bigfield_data.data[1], 0, 0 }; + barretenberg::fr bigfield_data_b{ bigfield_data.data[2], bigfield_data.data[3], 0, 0 }; + + inner_curve::bn254::fq_ct big_a(fr_ct(witness_ct(&composer, bigfield_data_a.to_montgomery_form())), + fr_ct(witness_ct(&composer, 0))); + inner_curve::bn254::fq_ct big_b(fr_ct(witness_ct(&composer, bigfield_data_b.to_montgomery_form())), + fr_ct(witness_ct(&composer, 0))); + ((big_a * big_b) + big_a) * big_b; + } + static circuit_outputs create_outer_circuit(InnerComposer& inner_composer, OuterComposer& outer_composer) { - auto prover = inner_composer.create_unrolled_prover(); - const auto verification_key_raw = inner_composer.compute_verification_key(); + // These constexpr definitions are to allow for the following: + // An Ultra Pedersen hash evaluates to a different value from the Turbo/Standard versions of the Pedersen hash. + // Therefore, the fiat-shamir challenges generated by the prover and verifier _could_ accidentally be different + // if an ultra proof is generated using ultra-pedersen challenges, but is being verified within a non-ultra + // circuit which uses non-ultra-pedersen challenges. We need the prover and verifier hashes to be the same. The + // solution is to select the relevant prover and verifier types (whose settings use the same hash for + // fiat-shamir), depending on the Inner-Outer combo. It's a bit clunky, but the alternative is to have a + // template argument for the hashtype, and that would pervade the entire UltraComposer, which would be + // horrendous. + constexpr bool is_ultra_to_ultra = std::is_same::value; + typedef typename std::conditional::type ProverOfInnerCircuit; + typedef typename std::conditional::type VerifierOfInnerProof; + typedef + typename std::conditional::type + RecursiveSettings; + + info("Creating ultra (inner) unrolled prover..."); + ProverOfInnerCircuit prover; + if constexpr (is_ultra_to_ultra) { + prover = inner_composer.create_unrolled_prover(); + } else { + prover = inner_composer.create_unrolled_ultra_to_standard_prover(); + } + + info("Computing verification key..."); + const auto verification_key_native = inner_composer.compute_verification_key(); + // Convert the verification key's elements into _circuit_ types, using the OUTER composer. std::shared_ptr verification_key = - verification_key_pt::from_witness(&outer_composer, verification_key_raw); + verification_key_pt::from_witness(&outer_composer, verification_key_native); + + info("Constructing the ultra (inner) proof ..."); waffle::plonk_proof recursive_proof = prover.construct_proof(); + + { + // Native check is mainly for comparison vs circuit version of the verifier. + info("Creating a native ultra (inner) verifier..."); + VerifierOfInnerProof native_verifier; + + if constexpr (is_ultra_to_ultra) { + native_verifier = inner_composer.create_unrolled_verifier(); + } else { + native_verifier = inner_composer.create_unrolled_ultra_to_standard_verifier(); + } + + info("Verifying the ultra (inner) proof natively..."); + auto native_result = native_verifier.verify_proof(recursive_proof); + + info("Native result: ", native_result); + } + transcript::Manifest recursive_manifest = InnerComposer::create_unrolled_manifest(prover.key->num_public_inputs); + + info("Verifying the ultra (inner) proof with CIRCUIT TYPES (i.e. within a standard plonk arithmetic circuit):"); stdlib::recursion::recursion_output output = - stdlib::recursion::verify_proof( + stdlib::recursion::verify_proof( &outer_composer, verification_key, recursive_manifest, recursive_proof); + return { output, verification_key }; }; @@ -71,61 +172,54 @@ template class stdlib_verifier : public testing::Test { InnerComposer& inner_composer_b, OuterComposer& outer_composer) { + // See create_outer_circuit for explanation of these constexpr definitions. + constexpr bool is_ultra_to_ultra = std::is_same::value; + typedef typename std::conditional::type ProverOfInnerCircuit; + typedef + typename std::conditional::type + RecursiveSettings; + + ProverOfInnerCircuit prover; + if constexpr (is_ultra_to_ultra) { + prover = inner_composer_a.create_unrolled_prover(); + } else { + prover = inner_composer_a.create_unrolled_ultra_to_standard_prover(); + } - auto prover = inner_composer_a.create_unrolled_prover(); - - const auto verification_key_raw = inner_composer_a.compute_verification_key(); + const auto verification_key_native = inner_composer_a.compute_verification_key(); std::shared_ptr verification_key = - verification_key_pt::from_witness(&outer_composer, verification_key_raw); + verification_key_pt::from_witness(&outer_composer, verification_key_native); + waffle::plonk_proof recursive_proof_a = prover.construct_proof(); transcript::Manifest recursive_manifest = InnerComposer::create_unrolled_manifest(prover.key->num_public_inputs); stdlib::recursion::recursion_output previous_output = - stdlib::recursion::verify_proof( + stdlib::recursion::verify_proof( &outer_composer, verification_key, recursive_manifest, recursive_proof_a); - auto prover_b = inner_composer_b.create_unrolled_prover(); + if constexpr (is_ultra_to_ultra) { + prover = inner_composer_b.create_unrolled_prover(); + } else { + prover = inner_composer_b.create_unrolled_ultra_to_standard_prover(); + } const auto verification_key_b_raw = inner_composer_b.compute_verification_key(); std::shared_ptr verification_key_b = verification_key_pt::from_witness(&outer_composer, verification_key_b_raw); - waffle::plonk_proof recursive_proof_b = prover_b.construct_proof(); + + waffle::plonk_proof recursive_proof_b = prover.construct_proof(); stdlib::recursion::recursion_output output = - stdlib::recursion::verify_proof( + stdlib::recursion::verify_proof( &outer_composer, verification_key_b, recursive_manifest, recursive_proof_b, previous_output); return { output, verification_key }; } - static void create_alternate_inner_circuit(InnerComposer& composer, - const std::vector& public_inputs) - { - fr_ct a(public_witness_ct(&composer, public_inputs[0])); - fr_ct b(public_witness_ct(&composer, public_inputs[1])); - fr_ct c(public_witness_ct(&composer, public_inputs[2])); - - for (size_t i = 0; i < 32; ++i) { - a = (a * b) + b + a; - a = c.madd(b, a); - } - plonk::stdlib::pedersen::compress(a, a); - inner_curve::byte_array_ct to_hash(&composer, "different nonsense test data"); - stdlib::blake2s(to_hash); - - barretenberg::fr bigfield_data = fr::random_element(); - barretenberg::fr bigfield_data_a{ bigfield_data.data[0], bigfield_data.data[1], 0, 0 }; - barretenberg::fr bigfield_data_b{ bigfield_data.data[2], bigfield_data.data[3], 0, 0 }; - - inner_curve::bn254::fq_ct big_a(fr_ct(witness_ct(&composer, bigfield_data_a.to_montgomery_form())), - fr_ct(witness_ct(&composer, 0))); - inner_curve::bn254::fq_ct big_b(fr_ct(witness_ct(&composer, bigfield_data_b.to_montgomery_form())), - fr_ct(witness_ct(&composer, 0))); - ((big_a * big_b) + big_a) * big_b; - } - // creates a cicuit that verifies either a proof from composer a, or from composer b static circuit_outputs create_outer_circuit_with_variable_inner_circuit(InnerComposer& inner_composer_a, InnerComposer& inner_composer_b, @@ -134,8 +228,25 @@ template class stdlib_verifier : public testing::Test { const bool create_failing_proof = false, const bool use_constant_key = false) { - auto prover_a = inner_composer_a.create_unrolled_prover(); - auto prover_b = inner_composer_b.create_unrolled_prover(); + // See create_outer_circuit for explanation of these constexpr definitions. + constexpr bool is_ultra_to_ultra = std::is_same::value; + typedef typename std::conditional::type ProverOfInnerCircuit; + typedef + typename std::conditional::type + RecursiveSettings; + + ProverOfInnerCircuit prover_a; + ProverOfInnerCircuit prover_b; + if constexpr (is_ultra_to_ultra) { + prover_a = inner_composer_a.create_unrolled_prover(); + prover_b = inner_composer_b.create_unrolled_prover(); + } else { + prover_a = inner_composer_a.create_unrolled_ultra_to_standard_prover(); + prover_b = inner_composer_b.create_unrolled_ultra_to_standard_prover(); + } + const auto verification_key_raw_a = inner_composer_a.compute_verification_key(); const auto verification_key_raw_b = inner_composer_b.compute_verification_key(); @@ -149,6 +260,7 @@ template class stdlib_verifier : public testing::Test { verification_key = proof_type ? verification_key_pt::from_witness(&outer_composer, verification_key_raw_a) : verification_key_pt::from_witness(&outer_composer, verification_key_raw_b); } + if (!use_constant_key) { if (create_failing_proof) { verification_key->validate_key_is_in_set({ verification_key_raw_b, verification_key_raw_b }); @@ -156,12 +268,16 @@ template class stdlib_verifier : public testing::Test { verification_key->validate_key_is_in_set({ verification_key_raw_a, verification_key_raw_b }); } } + waffle::plonk_proof recursive_proof = proof_type ? prover_a.construct_proof() : prover_b.construct_proof(); + transcript::Manifest recursive_manifest = InnerComposer::create_unrolled_manifest(prover_a.key->num_public_inputs); + stdlib::recursion::recursion_output output = - stdlib::recursion::verify_proof( + stdlib::recursion::verify_proof( &outer_composer, verification_key, recursive_manifest, recursive_proof); + return { output, verification_key }; } @@ -183,6 +299,7 @@ template class stdlib_verifier : public testing::Test { P[0].y = barretenberg::fq(circuit_output.recursion_output.P0.y.get_value().lo); P[1].x = barretenberg::fq(circuit_output.recursion_output.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.recursion_output.P1.y.get_value().lo); + barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); @@ -193,25 +310,79 @@ template class stdlib_verifier : public testing::Test { circuit_output.recursion_output.add_proof_outputs_as_public_inputs(); - EXPECT_EQ(outer_composer.failed, false); - std::cout << "creating prover" << std::endl; - std::cout << "composer gates = " << outer_composer.get_num_gates() << std::endl; + EXPECT_EQ(outer_composer.failed(), false); + + info("creating prover for outer circuit"); + info("composer gates = ", outer_composer.get_num_gates()); auto prover = outer_composer.create_prover(); - std::cout << "created prover" << std::endl; + info("created prover for outer circuit"); - std::cout << "creating verifier" << std::endl; + info("creating verifier for outer proof"); auto verifier = outer_composer.create_verifier(); - std::cout << "validated. creating proof" << std::endl; + info("creating outer proof for outer circuit"); waffle::plonk_proof proof = prover.construct_proof(); - std::cout << "created proof" << std::endl; + info("created outer proof"); + + info("verifying the outer proof"); + bool result = verifier.verify_proof(proof); + info("Outer proof verification result: ", result); + + EXPECT_EQ(result, true); + } + + static void test_recursive_proof_composition_ultra_no_tables() + { + InnerComposer inner_composer = InnerComposer("../srs_db/ignition"); + OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); + + std::vector inner_inputs{ 3, 4, 5 }; + + create_inner_circuit_no_tables(inner_composer, inner_inputs); + + auto circuit_output = create_outer_circuit(inner_composer, outer_composer); + + g1::affine_element P[2]; + P[0].x = barretenberg::fq(circuit_output.recursion_output.P0.x.get_value().lo); + P[0].y = barretenberg::fq(circuit_output.recursion_output.P0.y.get_value().lo); + P[1].x = barretenberg::fq(circuit_output.recursion_output.P1.x.get_value().lo); + P[1].y = barretenberg::fq(circuit_output.recursion_output.P1.y.get_value().lo); + + barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( + P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + + EXPECT_EQ(inner_proof_result, barretenberg::fq12::one()); + + circuit_output.recursion_output.add_proof_outputs_as_public_inputs(); + + EXPECT_EQ(outer_composer.failed(), false); + info("creating prover for outer circuit"); + info("composer gates = ", outer_composer.get_num_gates()); + auto prover = outer_composer.create_prover(); + info("created prover for outer circuit"); + + info("creating verifier for outer proof"); + auto verifier = outer_composer.create_verifier(); + + info("creating outer proof for outer circuit"); + waffle::plonk_proof proof = prover.construct_proof(); + info("created outer proof"); + + info("verifying the outer proof"); bool result = verifier.verify_proof(proof); + info("Outer proof verification result: ", result); + EXPECT_EQ(result, true); } static void test_double_verification() { + if constexpr (std::is_same::value) + return; // We only care about running this test for turbo and ultra outer circuits, since in practice the + // only circuits which verify >1 proof are ultra or turbo circuits. Standard uses so many gates + // (16m) that it's a waste of time testing it. + InnerComposer inner_composer_a = InnerComposer("../srs_db/ignition"); InnerComposer inner_composer_b = InnerComposer("../srs_db/ignition"); @@ -439,9 +610,31 @@ template class stdlib_verifier : public testing::Test { bool result = verifier.verify_proof(proof); EXPECT_EQ(result, true); } + + static void test_inner_circuit() + { + if constexpr (!std::is_same::value) + return; // We only want to run this test once (since it's not actually dependent on the typed test + // parameter; which is the outer composer). We've only made it a typed test so that it can be + // included in this test suite. So to avoid running this test identically 3 times, we escape all but + // 1 permutation. + + InnerComposer inner_composer = InnerComposer("../srs_db/ignition"); + std::vector inner_inputs{ barretenberg::fr::random_element(), + barretenberg::fr::random_element(), + barretenberg::fr::random_element() }; + + create_inner_circuit(inner_composer, inner_inputs); + + auto prover = inner_composer.create_unrolled_prover(); + auto verifier = inner_composer.create_unrolled_verifier(); + auto proof = prover.construct_proof(); + auto verified = verifier.verify_proof(proof); + EXPECT_EQ(verified, true); + } }; -typedef testing::Types OuterComposerTypes; +typedef testing::Types OuterComposerTypes; TYPED_TEST_SUITE(stdlib_verifier, OuterComposerTypes); @@ -450,7 +643,12 @@ HEAVY_TYPED_TEST(stdlib_verifier, recursive_proof_composition) TestFixture::test_recursive_proof_composition(); }; -// Produces a huge 16m gate circuit with the StandardComposer. Really needed? +HEAVY_TYPED_TEST(stdlib_verifier, recursive_proof_composition_ultra_no_tables) +{ + TestFixture::test_recursive_proof_composition_ultra_no_tables(); +}; + +// CircleCI can't cope with this. // HEAVY_TYPED_TEST(stdlib_verifier, double_verification) // { // TestFixture::test_double_verification(); @@ -474,4 +672,9 @@ HEAVY_TYPED_TEST(stdlib_verifier, recursive_proof_composition_var_verif_key_fail HEAVY_TYPED_TEST(stdlib_verifier, recursive_proof_composition_const_verif_key) { TestFixture::test_recursive_proof_composition_with_constant_verification_key(); -} \ No newline at end of file +} + +HEAVY_TYPED_TEST(stdlib_verifier, test_inner_circuit) +{ + TestFixture::test_inner_circuit(); +} diff --git a/cpp/src/aztec/stdlib/recursion/verifier/verifier_turbo.test.cpp b/cpp/src/aztec/stdlib/recursion/verifier/verifier_turbo.test.cpp new file mode 100644 index 0000000000..3ae8d6482b --- /dev/null +++ b/cpp/src/aztec/stdlib/recursion/verifier/verifier_turbo.test.cpp @@ -0,0 +1,508 @@ +#include "verifier.hpp" +#include +#include +#include +#include +#include +#include +#include "../../hash/blake3s/blake3s.hpp" +#include "../../hash/pedersen/pedersen.hpp" +#include "program_settings.hpp" + +using namespace plonk; + +template class stdlib_verifier_turbo : public testing::Test { + using InnerComposer = waffle::TurboComposer; + + typedef stdlib::bn254 inner_curve; + typedef stdlib::bn254 outer_curve; + typedef plonk::stdlib::recursion::verification_key verification_key_pt; + typedef plonk::stdlib::recursion::recursive_turbo_verifier_settings recursive_settings; + typedef inner_curve::fr_ct fr_ct; + typedef inner_curve::public_witness_ct public_witness_ct; + typedef inner_curve::witness_ct witness_ct; + + struct circuit_outputs { + stdlib::recursion::recursion_output recursion_output; + std::shared_ptr verification_key; + }; + + static void create_inner_circuit(InnerComposer& composer, const std::vector& public_inputs) + { + fr_ct a(public_witness_ct(&composer, public_inputs[0])); + fr_ct b(public_witness_ct(&composer, public_inputs[1])); + fr_ct c(public_witness_ct(&composer, public_inputs[2])); + + for (size_t i = 0; i < 32; ++i) { + a = (a * b) + b + a; + a = a.madd(b, c); + } + plonk::stdlib::pedersen::compress(a, b); + typename inner_curve::byte_array_ct to_hash(&composer, "nonsense test data"); + stdlib::blake3s(to_hash); + + barretenberg::fr bigfield_data = fr::random_element(); + barretenberg::fr bigfield_data_a{ bigfield_data.data[0], bigfield_data.data[1], 0, 0 }; + barretenberg::fr bigfield_data_b{ bigfield_data.data[2], bigfield_data.data[3], 0, 0 }; + + typename inner_curve::fq_ct big_a(fr_ct(witness_ct(&composer, bigfield_data_a.to_montgomery_form())), + fr_ct(witness_ct(&composer, 0))); + typename inner_curve::fq_ct big_b(fr_ct(witness_ct(&composer, bigfield_data_b.to_montgomery_form())), + fr_ct(witness_ct(&composer, 0))); + big_a* big_b; + }; + + static circuit_outputs create_outer_circuit(InnerComposer& inner_composer, OuterComposer& outer_composer) + { + auto prover = inner_composer.create_unrolled_prover(); + const auto verification_key_raw = inner_composer.compute_verification_key(); + std::shared_ptr verification_key = + verification_key_pt::from_witness(&outer_composer, verification_key_raw); + waffle::plonk_proof recursive_proof = prover.construct_proof(); + transcript::Manifest recursive_manifest = + InnerComposer::create_unrolled_manifest(prover.key->num_public_inputs); + stdlib::recursion::recursion_output output = + stdlib::recursion::verify_proof( + &outer_composer, verification_key, recursive_manifest, recursive_proof); + return { output, verification_key }; + }; + + static circuit_outputs create_double_outer_circuit(InnerComposer& inner_composer_a, + InnerComposer& inner_composer_b, + OuterComposer& outer_composer) + { + + auto prover = inner_composer_a.create_unrolled_prover(); + + const auto verification_key_raw = inner_composer_a.compute_verification_key(); + std::shared_ptr verification_key = + verification_key_pt::from_witness(&outer_composer, verification_key_raw); + waffle::plonk_proof recursive_proof_a = prover.construct_proof(); + + transcript::Manifest recursive_manifest = + InnerComposer::create_unrolled_manifest(prover.key->num_public_inputs); + + stdlib::recursion::recursion_output previous_output = + stdlib::recursion::verify_proof( + &outer_composer, verification_key, recursive_manifest, recursive_proof_a); + + auto prover_b = inner_composer_b.create_unrolled_prover(); + + const auto verification_key_b_raw = inner_composer_b.compute_verification_key(); + std::shared_ptr verification_key_b = + verification_key_pt::from_witness(&outer_composer, verification_key_b_raw); + waffle::plonk_proof recursive_proof_b = prover_b.construct_proof(); + + stdlib::recursion::recursion_output output = + stdlib::recursion::verify_proof( + &outer_composer, verification_key_b, recursive_manifest, recursive_proof_b, previous_output); + + return { output, verification_key }; + } + + static void create_alternate_inner_circuit(InnerComposer& composer, + const std::vector& public_inputs) + { + fr_ct a(public_witness_ct(&composer, public_inputs[0])); + fr_ct b(public_witness_ct(&composer, public_inputs[1])); + fr_ct c(public_witness_ct(&composer, public_inputs[2])); + + for (size_t i = 0; i < 32; ++i) { + a = (a * b) + b + a; + a = c.madd(b, a); + } + plonk::stdlib::pedersen::compress(a, a); + inner_curve::byte_array_ct to_hash(&composer, "different nonsense test data"); + stdlib::blake3s(to_hash); + + barretenberg::fr bigfield_data = fr::random_element(); + barretenberg::fr bigfield_data_a{ bigfield_data.data[0], bigfield_data.data[1], 0, 0 }; + barretenberg::fr bigfield_data_b{ bigfield_data.data[2], bigfield_data.data[3], 0, 0 }; + + inner_curve::bn254::fq_ct big_a(fr_ct(witness_ct(&composer, bigfield_data_a.to_montgomery_form())), + fr_ct(witness_ct(&composer, 0))); + inner_curve::bn254::fq_ct big_b(fr_ct(witness_ct(&composer, bigfield_data_b.to_montgomery_form())), + fr_ct(witness_ct(&composer, 0))); + ((big_a * big_b) + big_a) * big_b; + } + + // creates a cicuit that verifies either a proof from composer a, or from composer b + static circuit_outputs create_outer_circuit_with_variable_inner_circuit(InnerComposer& inner_composer_a, + InnerComposer& inner_composer_b, + OuterComposer& outer_composer, + const bool proof_type, + const bool create_failing_proof = false, + const bool use_constant_key = false) + { + auto prover_a = inner_composer_a.create_unrolled_prover(); + auto prover_b = inner_composer_b.create_unrolled_prover(); + const auto verification_key_raw_a = inner_composer_a.compute_verification_key(); + const auto verification_key_raw_b = inner_composer_b.compute_verification_key(); + + std::shared_ptr verification_key; + if (use_constant_key) { + verification_key = proof_type + ? verification_key_pt::from_constants(&outer_composer, verification_key_raw_a) + : verification_key_pt::from_constants(&outer_composer, verification_key_raw_b); + + } else { + verification_key = proof_type ? verification_key_pt::from_witness(&outer_composer, verification_key_raw_a) + : verification_key_pt::from_witness(&outer_composer, verification_key_raw_b); + } + if (!use_constant_key) { + if (create_failing_proof) { + verification_key->validate_key_is_in_set({ verification_key_raw_b, verification_key_raw_b }); + } else { + verification_key->validate_key_is_in_set({ verification_key_raw_a, verification_key_raw_b }); + } + } + waffle::plonk_proof recursive_proof = proof_type ? prover_a.construct_proof() : prover_b.construct_proof(); + transcript::Manifest recursive_manifest = + InnerComposer::create_unrolled_manifest(prover_a.key->num_public_inputs); + stdlib::recursion::recursion_output output = + stdlib::recursion::verify_proof( + &outer_composer, verification_key, recursive_manifest, recursive_proof); + return { output, verification_key }; + } + + public: + static void test_recursive_proof_composition() + { + InnerComposer inner_composer = InnerComposer("../srs_db/ignition"); + OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); + std::vector inner_inputs{ barretenberg::fr::random_element(), + barretenberg::fr::random_element(), + barretenberg::fr::random_element() }; + + create_inner_circuit(inner_composer, inner_inputs); + + auto circuit_output = create_outer_circuit(inner_composer, outer_composer); + + g1::affine_element P[2]; + P[0].x = barretenberg::fq(circuit_output.recursion_output.P0.x.get_value().lo); + P[0].y = barretenberg::fq(circuit_output.recursion_output.P0.y.get_value().lo); + P[1].x = barretenberg::fq(circuit_output.recursion_output.P1.x.get_value().lo); + P[1].y = barretenberg::fq(circuit_output.recursion_output.P1.y.get_value().lo); + barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( + P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + + EXPECT_EQ(circuit_output.recursion_output.public_inputs[0].get_value(), inner_inputs[0]); + EXPECT_EQ(circuit_output.recursion_output.public_inputs[1].get_value(), inner_inputs[1]); + + EXPECT_EQ(inner_proof_result, barretenberg::fq12::one()); + + circuit_output.recursion_output.add_proof_outputs_as_public_inputs(); + + EXPECT_EQ(outer_composer.failed(), false); + std::cout << "creating prover" << std::endl; + std::cout << "composer gates = " << outer_composer.get_num_gates() << std::endl; + auto prover = outer_composer.create_prover(); + std::cout << "created prover" << std::endl; + + std::cout << "creating verifier" << std::endl; + auto verifier = outer_composer.create_verifier(); + + std::cout << "validated. creating proof" << std::endl; + waffle::plonk_proof proof = prover.construct_proof(); + std::cout << "created proof" << std::endl; + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } + + static void test_double_verification() + { + if constexpr (std::is_same::value) + return; // We only care about running this test for turbo and ultra outer circuits, since in practice the + // only circuits which verify >1 proof are ultra or turbo circuits. Standard uses so many gates + // (16m) that it's a waste of time testing it. + + InnerComposer inner_composer_a = InnerComposer("../srs_db/ignition"); + InnerComposer inner_composer_b = InnerComposer("../srs_db/ignition"); + + OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); + + std::vector inner_inputs{ barretenberg::fr::random_element(), + barretenberg::fr::random_element(), + barretenberg::fr::random_element() }; + + create_inner_circuit(inner_composer_a, inner_inputs); + create_inner_circuit(inner_composer_b, inner_inputs); + + auto circuit_output = create_double_outer_circuit(inner_composer_a, inner_composer_b, outer_composer); + + g1::affine_element P[2]; + P[0].x = barretenberg::fq(circuit_output.recursion_output.P0.x.get_value().lo); + P[0].y = barretenberg::fq(circuit_output.recursion_output.P0.y.get_value().lo); + P[1].x = barretenberg::fq(circuit_output.recursion_output.P1.x.get_value().lo); + P[1].y = barretenberg::fq(circuit_output.recursion_output.P1.y.get_value().lo); + barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( + P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + + EXPECT_EQ(circuit_output.recursion_output.public_inputs[0].get_value(), inner_inputs[0]); + EXPECT_EQ(circuit_output.recursion_output.public_inputs[1].get_value(), inner_inputs[1]); + + EXPECT_EQ(inner_proof_result, barretenberg::fq12::one()); + + printf("composer gates = %zu\n", outer_composer.get_num_gates()); + + std::cout << "creating prover" << std::endl; + auto prover = outer_composer.create_prover(); + std::cout << "created prover" << std::endl; + + std::cout << "creating verifier" << std::endl; + auto verifier = outer_composer.create_verifier(); + + std::cout << "validated. creating proof" << std::endl; + waffle::plonk_proof proof = prover.construct_proof(); + std::cout << "created proof" << std::endl; + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } + + // verifies a proof of a circuit that verifies one of two proofs. Test 'a' uses a proof over the first of the two + // variable circuits + static void test_recursive_proof_composition_with_variable_verification_key_a() + { + InnerComposer inner_composer_a = InnerComposer("../srs_db/ignition"); + InnerComposer inner_composer_b = InnerComposer("../srs_db/ignition"); + OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); + std::vector inner_inputs_a{ barretenberg::fr::random_element(), + barretenberg::fr::random_element(), + barretenberg::fr::random_element() }; + + std::vector inner_inputs_b{ barretenberg::fr::random_element(), + barretenberg::fr::random_element(), + barretenberg::fr::random_element() }; + + create_inner_circuit(inner_composer_a, inner_inputs_a); + create_alternate_inner_circuit(inner_composer_b, inner_inputs_b); + + auto circuit_output = + create_outer_circuit_with_variable_inner_circuit(inner_composer_a, inner_composer_b, outer_composer, true); + + g1::affine_element P[2]; + P[0].x = barretenberg::fq(circuit_output.recursion_output.P0.x.get_value().lo); + P[0].y = barretenberg::fq(circuit_output.recursion_output.P0.y.get_value().lo); + P[1].x = barretenberg::fq(circuit_output.recursion_output.P1.x.get_value().lo); + P[1].y = barretenberg::fq(circuit_output.recursion_output.P1.y.get_value().lo); + barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( + P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + + EXPECT_EQ(circuit_output.recursion_output.public_inputs[0].get_value(), inner_inputs_a[0]); + EXPECT_EQ(circuit_output.recursion_output.public_inputs[1].get_value(), inner_inputs_a[1]); + EXPECT_EQ(circuit_output.recursion_output.public_inputs[2].get_value(), inner_inputs_a[2]); + + EXPECT_EQ(inner_proof_result, barretenberg::fq12::one()); + + printf("composer gates = %zu\n", outer_composer.get_num_gates()); + + auto prover = outer_composer.create_prover(); + + auto verifier = outer_composer.create_verifier(); + + waffle::plonk_proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } + + // verifies a proof of a circuit that verifies one of two proofs. Test 'b' uses a proof over the second of the two + // variable circuits + static void test_recursive_proof_composition_with_variable_verification_key_b() + { + InnerComposer inner_composer_a = InnerComposer("../srs_db/ignition"); + InnerComposer inner_composer_b = InnerComposer("../srs_db/ignition"); + OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); + std::vector inner_inputs_a{ barretenberg::fr::random_element(), + barretenberg::fr::random_element(), + barretenberg::fr::random_element() }; + + std::vector inner_inputs_b{ barretenberg::fr::random_element(), + barretenberg::fr::random_element(), + barretenberg::fr::random_element() }; + + create_inner_circuit(inner_composer_a, inner_inputs_a); + create_alternate_inner_circuit(inner_composer_b, inner_inputs_b); + auto circuit_output = + create_outer_circuit_with_variable_inner_circuit(inner_composer_a, inner_composer_b, outer_composer, false); + g1::affine_element P[2]; + + P[0].x = barretenberg::fq(circuit_output.recursion_output.P0.x.get_value().lo); + P[0].y = barretenberg::fq(circuit_output.recursion_output.P0.y.get_value().lo); + P[1].x = barretenberg::fq(circuit_output.recursion_output.P1.x.get_value().lo); + P[1].y = barretenberg::fq(circuit_output.recursion_output.P1.y.get_value().lo); + + barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( + P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + + EXPECT_EQ(circuit_output.recursion_output.public_inputs[0].get_value(), inner_inputs_b[0]); + EXPECT_EQ(circuit_output.recursion_output.public_inputs[1].get_value(), inner_inputs_b[1]); + EXPECT_EQ(circuit_output.recursion_output.public_inputs[2].get_value(), inner_inputs_b[2]); + + EXPECT_EQ(inner_proof_result, barretenberg::fq12::one()); + + printf("composer gates = %zu\n", outer_composer.get_num_gates()); + + auto prover = outer_composer.create_prover(); + + auto verifier = outer_composer.create_verifier(); + + waffle::plonk_proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } + + static void test_recursive_proof_composition_with_variable_verification_key_failure_case() + { + InnerComposer inner_composer_a = InnerComposer("../srs_db/ignition"); + InnerComposer inner_composer_b = InnerComposer("../srs_db/ignition"); + OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); + std::vector inner_inputs_a{ barretenberg::fr::random_element(), + barretenberg::fr::random_element(), + barretenberg::fr::random_element() }; + + std::vector inner_inputs_b{ barretenberg::fr::random_element(), + barretenberg::fr::random_element(), + barretenberg::fr::random_element() }; + + create_inner_circuit(inner_composer_a, inner_inputs_a); + create_alternate_inner_circuit(inner_composer_b, inner_inputs_b); + + auto circuit_output = create_outer_circuit_with_variable_inner_circuit( + inner_composer_a, inner_composer_b, outer_composer, true, true); + + g1::affine_element P[2]; + P[0].x = barretenberg::fq(circuit_output.recursion_output.P0.x.get_value().lo); + P[0].y = barretenberg::fq(circuit_output.recursion_output.P0.y.get_value().lo); + P[1].x = barretenberg::fq(circuit_output.recursion_output.P1.x.get_value().lo); + P[1].y = barretenberg::fq(circuit_output.recursion_output.P1.y.get_value().lo); + barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( + P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + + EXPECT_EQ(circuit_output.recursion_output.public_inputs[0].get_value(), inner_inputs_a[0]); + EXPECT_EQ(circuit_output.recursion_output.public_inputs[1].get_value(), inner_inputs_a[1]); + EXPECT_EQ(circuit_output.recursion_output.public_inputs[2].get_value(), inner_inputs_a[2]); + + EXPECT_EQ(inner_proof_result, barretenberg::fq12::one()); + + printf("composer gates = %zu\n", outer_composer.get_num_gates()); + + auto prover = outer_composer.create_prover(); + + auto verifier = outer_composer.create_verifier(); + + waffle::plonk_proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, false); + } + + static void test_recursive_proof_composition_with_constant_verification_key() + { + InnerComposer inner_composer_a = InnerComposer("../srs_db/ignition"); + InnerComposer inner_composer_b = InnerComposer("../srs_db/ignition"); + OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); + std::vector inner_inputs_a{ barretenberg::fr::random_element(), + barretenberg::fr::random_element(), + barretenberg::fr::random_element() }; + + std::vector inner_inputs_b{ barretenberg::fr::random_element(), + barretenberg::fr::random_element(), + barretenberg::fr::random_element() }; + + create_inner_circuit(inner_composer_a, inner_inputs_a); + create_alternate_inner_circuit(inner_composer_b, inner_inputs_b); + + auto circuit_output = create_outer_circuit_with_variable_inner_circuit( + inner_composer_a, inner_composer_b, outer_composer, true, false, true); + + g1::affine_element P[2]; + P[0].x = barretenberg::fq(circuit_output.recursion_output.P0.x.get_value().lo); + P[0].y = barretenberg::fq(circuit_output.recursion_output.P0.y.get_value().lo); + P[1].x = barretenberg::fq(circuit_output.recursion_output.P1.x.get_value().lo); + P[1].y = barretenberg::fq(circuit_output.recursion_output.P1.y.get_value().lo); + barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( + P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + + EXPECT_EQ(circuit_output.recursion_output.public_inputs[0].get_value(), inner_inputs_a[0]); + EXPECT_EQ(circuit_output.recursion_output.public_inputs[1].get_value(), inner_inputs_a[1]); + EXPECT_EQ(circuit_output.recursion_output.public_inputs[2].get_value(), inner_inputs_a[2]); + + EXPECT_EQ(inner_proof_result, barretenberg::fq12::one()); + + printf("composer gates = %zu\n", outer_composer.get_num_gates()); + + auto prover = outer_composer.create_prover(); + + auto verifier = outer_composer.create_verifier(); + + waffle::plonk_proof proof = prover.construct_proof(); + + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); + } + + static void test_inner_circuit() + { + if constexpr (!std::is_same::value) + return; // We only want to run this test once (since it's not actually dependent on the typed test + // parameter; which is the outer composer). We've only made it a typed test so that it can be + // included in this test suite. So to avoid running this test identically 3 times, we escape all but + // 1 permutation. + + InnerComposer inner_composer = InnerComposer("../srs_db/ignition"); + std::vector inner_inputs{ barretenberg::fr::random_element(), + barretenberg::fr::random_element(), + barretenberg::fr::random_element() }; + + create_inner_circuit(inner_composer, inner_inputs); + + auto prover = inner_composer.create_unrolled_prover(); + auto verifier = inner_composer.create_unrolled_verifier(); + auto proof = prover.construct_proof(); + auto verified = verifier.verify_proof(proof); + EXPECT_EQ(verified, true); + } +}; + +typedef testing::Types OuterComposerTypes; + +TYPED_TEST_SUITE(stdlib_verifier_turbo, OuterComposerTypes); + +HEAVY_TYPED_TEST(stdlib_verifier_turbo, recursive_proof_composition) +{ + TestFixture::test_recursive_proof_composition(); +}; + +HEAVY_TYPED_TEST(stdlib_verifier_turbo, double_verification) +{ + TestFixture::test_double_verification(); +}; + +HEAVY_TYPED_TEST(stdlib_verifier_turbo, recursive_proof_composition_with_variable_verification_key_a) +{ + TestFixture::test_recursive_proof_composition_with_variable_verification_key_a(); +} + +HEAVY_TYPED_TEST(stdlib_verifier_turbo, recursive_proof_composition_with_variable_verification_key_b) +{ + TestFixture::test_recursive_proof_composition_with_variable_verification_key_b(); +} + +HEAVY_TYPED_TEST(stdlib_verifier_turbo, recursive_proof_composition_var_verif_key_fail) +{ + TestFixture::test_recursive_proof_composition_with_variable_verification_key_failure_case(); +} + +HEAVY_TYPED_TEST(stdlib_verifier_turbo, recursive_proof_composition_const_verif_key) +{ + TestFixture::test_recursive_proof_composition_with_constant_verification_key(); +} + +HEAVY_TYPED_TEST(stdlib_verifier_turbo, test_inner_circuit) +{ + TestFixture::test_inner_circuit(); +} diff --git a/cpp/src/aztec/stdlib/types/plookup.hpp b/cpp/src/aztec/stdlib/types/plookup.hpp deleted file mode 100644 index f5d9d6c677..0000000000 --- a/cpp/src/aztec/stdlib/types/plookup.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace plonk { -namespace stdlib { -namespace types { -namespace plookup { - -using namespace plonk; - -typedef waffle::PlookupComposer Composer; -typedef waffle::PlookupProver Prover; -typedef waffle::PlookupVerifier Verifier; -typedef stdlib::witness_t witness_ct; -typedef stdlib::public_witness_t public_witness_ct; -typedef stdlib::bool_t bool_ct; -typedef stdlib::byte_array byte_array_ct; -typedef stdlib::packed_byte_array packed_byte_array_ct; -typedef stdlib::field_t field_ct; -typedef stdlib::uint8 uint8_ct; -typedef stdlib::uint16 uint16_ct; -typedef stdlib::uint32 uint32_ct; -typedef stdlib::uint64 uint64_ct; -typedef stdlib::bit_array bit_array_ct; -typedef stdlib::bigfield fq_ct; -typedef stdlib::element biggroup_ct; -typedef stdlib::point point_ct; -typedef stdlib::pedersen pedersen; - -typedef stdlib::bn254 bn254; -namespace merkle_tree { -using namespace stdlib::merkle_tree; -typedef stdlib::merkle_tree::hash_path hash_path; -} // namespace merkle_tree - -namespace schnorr { -typedef stdlib::schnorr::signature_bits signature_bits; -} -} // namespace plookup -} // namespace types -} // namespace stdlib -} // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/types/standard.hpp b/cpp/src/aztec/stdlib/types/standard.hpp deleted file mode 100644 index 803502ad65..0000000000 --- a/cpp/src/aztec/stdlib/types/standard.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace plonk { -namespace stdlib { -namespace types { -namespace standard { - -using namespace plonk; - -typedef waffle::StandardComposer Composer; -typedef waffle::Prover Prover; -typedef waffle::UnrolledProver UnrolledProver; -typedef waffle::Verifier Verifier; -typedef stdlib::witness_t witness_ct; -typedef stdlib::public_witness_t public_witness_ct; -typedef stdlib::bool_t bool_ct; -typedef stdlib::byte_array byte_array_ct; -typedef stdlib::packed_byte_array packed_byte_array_ct; -typedef stdlib::field_t field_ct; -typedef stdlib::uint8 uint8_ct; -typedef stdlib::uint16 uint16_ct; -typedef stdlib::uint32 uint32_ct; -typedef stdlib::uint64 uint64_ct; -typedef stdlib::bit_array bit_array_ct; -typedef stdlib::bigfield fq_ct; -typedef stdlib::element biggroup_ct; -typedef stdlib::point point_ct; -typedef stdlib::pedersen pedersen; -typedef stdlib::group group_ct; - -typedef stdlib::bn254 bn254; - -// these are used in biggroup tests -typedef stdlib::bn254 bn254_1; -typedef stdlib::bn254 bn254_2; -typedef stdlib::bigfield big_fr_ct; -typedef stdlib::element bigfield_biggroup_ct; - -namespace merkle_tree { -using namespace stdlib::merkle_tree; -typedef stdlib::merkle_tree::hash_path hash_path; -} // namespace merkle_tree - -namespace schnorr { -typedef stdlib::schnorr::signature_bits signature_bits; -} -} // namespace standard -} // namespace types -} // namespace stdlib -} // namespace plonk \ No newline at end of file diff --git a/cpp/src/aztec/stdlib/types/turbo.hpp b/cpp/src/aztec/stdlib/types/turbo.hpp deleted file mode 100644 index 225aa42761..0000000000 --- a/cpp/src/aztec/stdlib/types/turbo.hpp +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace plonk { -namespace stdlib { -namespace types { -namespace turbo { - -using namespace plonk; - -typedef waffle::TurboComposer Composer; -typedef waffle::TurboProver Prover; -typedef waffle::UnrolledTurboProver UnrolledProver; -typedef waffle::TurboVerifier Verifier; -typedef waffle::UnrolledTurboVerifier UnrolledVerifier; -typedef stdlib::witness_t witness_ct; -typedef stdlib::public_witness_t public_witness_ct; -typedef stdlib::bool_t bool_ct; -typedef stdlib::byte_array byte_array_ct; -typedef stdlib::packed_byte_array packed_byte_array_ct; -typedef stdlib::field_t field_ct; -typedef stdlib::safe_uint_t suint_ct; -typedef stdlib::uint8 uint8_ct; -typedef stdlib::uint16 uint16_ct; -typedef stdlib::uint32 uint32_ct; -typedef stdlib::uint64 uint64_ct; -typedef stdlib::bit_array bit_array_ct; -typedef stdlib::bigfield fq_ct; -typedef stdlib::element biggroup_ct; -typedef stdlib::point point_ct; -typedef stdlib::pedersen pedersen; -typedef stdlib::group group_ct; - -typedef stdlib::bn254 bn254; - -namespace merkle_tree { -using namespace stdlib::merkle_tree; -typedef stdlib::merkle_tree::hash_path hash_path; - -} // namespace merkle_tree - -namespace schnorr { -typedef stdlib::schnorr::signature_bits signature_bits; -} -} // namespace turbo -} // namespace types -} // namespace stdlib -} // namespace plonk diff --git a/cpp/src/aztec/stdlib/types/types.hpp b/cpp/src/aztec/stdlib/types/types.hpp new file mode 100644 index 0000000000..243c258a89 --- /dev/null +++ b/cpp/src/aztec/stdlib/types/types.hpp @@ -0,0 +1,96 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace plonk::stdlib::types { + +using namespace plonk; +static constexpr size_t SYSTEM_COMPOSER = waffle::SYSTEM_COMPOSER; + +typedef std::conditional_t< + SYSTEM_COMPOSER == waffle::STANDARD, + waffle::StandardComposer, + std::conditional_t> + Composer; + +typedef std::conditional_t< + SYSTEM_COMPOSER == waffle::STANDARD, + waffle::Prover, + std::conditional_t> + Prover; + +typedef std::conditional_t< + SYSTEM_COMPOSER == waffle::STANDARD, + waffle::Verifier, + std::conditional_t> + Verifier; + +typedef std::conditional_t< + SYSTEM_COMPOSER == waffle::STANDARD, + waffle::UnrolledProver, + std::conditional_t> + UnrolledProver; + +typedef std::conditional_t< + SYSTEM_COMPOSER == waffle::STANDARD, + waffle::UnrolledVerifier, + std::conditional_t> + UnrolledVerifier; + +typedef stdlib::witness_t witness_ct; +typedef stdlib::public_witness_t public_witness_ct; +typedef stdlib::bool_t bool_ct; +typedef stdlib::byte_array byte_array_ct; +typedef stdlib::packed_byte_array packed_byte_array_ct; +typedef stdlib::field_t field_ct; +typedef stdlib::safe_uint_t suint_ct; +typedef stdlib::uint8 uint8_ct; +typedef stdlib::uint16 uint16_ct; +typedef stdlib::uint32 uint32_ct; +typedef stdlib::uint64 uint64_ct; +typedef stdlib::bit_array bit_array_ct; +typedef stdlib::bigfield fq_ct; +typedef stdlib::element biggroup_ct; +typedef stdlib::point point_ct; +typedef stdlib::pedersen pedersen; +typedef stdlib::group group_ct; +typedef stdlib::bn254 bn254; +typedef stdlib::secp256k1 secp256k1_ct; + +namespace merkle_tree { +using namespace stdlib::merkle_tree; +typedef stdlib::merkle_tree::hash_path hash_path; +} // namespace merkle_tree + +namespace schnorr { +typedef stdlib::schnorr::signature_bits signature_bits; +} // namespace schnorr + +// Ultra-composer specific types +typedef stdlib::rom_table rom_table_ct; + +typedef std::conditional_t, + recursion::recursive_ultra_verifier_settings> + recursive_inner_verifier_settings; + +} // namespace plonk::stdlib::types \ No newline at end of file diff --git a/cpp/srs_db/download_ignition.sh b/cpp/srs_db/download_ignition.sh index c81be49504..b7035ba60d 100755 --- a/cpp/srs_db/download_ignition.sh +++ b/cpp/srs_db/download_ignition.sh @@ -1,6 +1,9 @@ #!/bin/sh # Downloads the ignition trusted setup transcripts. # +# See here for details of the contents of the transcript.dat files: +# https://github.com/AztecProtocol/ignition-verification/blob/master/Transcript_spec.md +# # To download all transcripts. # ./download_ignition.sh # diff --git a/cpp/srs_db/download_ignition_lagrange.sh b/cpp/srs_db/download_ignition_lagrange.sh new file mode 100755 index 0000000000..855bb51c70 --- /dev/null +++ b/cpp/srs_db/download_ignition_lagrange.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# Downloads the Lagrange transcripts generated from ignition trusted setup. +# +# To download all transcripts upto 2^{24}. +# ./download_ignition_lagrange.sh +# +# To download select transcripts upto size 2^m, +# ./download_ignition_lagrange.sh m +# +# If a checksums file is available, it will be used to validate if a download is required +# and also check the validity of the downloaded transcripts. If not the script downloads +# whatever is requested but does not check the validity of the downloads. +set -e + +mkdir -p lagrange +cd lagrange +num_transcripts=${1:-24} +ARGS=$(seq 1 $num_transcripts) + +checksum() { + grep transcript_${1}.dat checksums | sha256sum -c + return $? +} + +download() { + curl https://aztec-ignition.s3-eu-west-2.amazonaws.com/MAIN%20IGNITION/lagrange/transcript_${1}.dat > transcript_${1}.dat +} + +for TRANSCRIPT in $ARGS; do + NUM=$(printf %2d $((1 << $TRANSCRIPT))) + if [ -f checksums ]; then + checksum $NUM && continue + download $NUM + checksum $NUM || exit 1 + else + download $NUM + fi +done diff --git a/cpp/ultra_dev_tests.sh b/cpp/ultra_dev_tests.sh new file mode 100755 index 0000000000..a7172761cd --- /dev/null +++ b/cpp/ultra_dev_tests.sh @@ -0,0 +1,14 @@ +#!/bin/zsh + +cd build; +cmake ..; +cmake --build . -j +for file in ./bin/plonk_tests ./bin/rollup_proofs_account_tests ./bin/rollup_proofs_claim_tests ./bin/rollup_proofs_inner_proof_data_tests ./bin/rollup_proofs_join_split_tests ./bin/rollup_proofs_notes_tests ./bin/srs_tests ./bin/stdlib_aes128_tests ./bin/stdlib_blake2s_tests ./bin/stdlib_blake3s_tests ./bin/stdlib_ecdsa_tests ./bin/stdlib_merkle_tree_tests ./bin/stdlib_primitives_tests ./bin/stdlib_schnorr_tests ./bin/stdlib_sha256_tests; +do ./$file; done; +./bin/stdlib_recursion_tests "--gtest_filter=*0*recursive_proof_composition"; # tests Turbo-Turbo and Ultra-Ultra +./bin/stdlib_pedersen_tests "--gtest_filter=*0*"; # only testing Ultra here +./bin/rollup_proofs_tx_rollup_tests "--gtest_filter=rollup_tests*1*1*" +./bin/rollup_proofs_tx_rollup_tests "--gtest_filter=rollup_tests*1*2*" +./bin/rollup_proofs_tx_rollup_tests "--gtest_filter=rollup_tests*2*2*" +./bin/rollup_proofs_root_rollup_tests "--gtest_filter=*root_rollup*1_real_2*"; +./bin/rollup_proofs_root_rollup_tests "--gtest_filter=*root_rollup*2x3s*"; \ No newline at end of file